[JAVA] Create Spring Cloud Config Server with security with Spring Boot 2.0

What to make this time

スクリーンショット 2018-03-09 14.56.50.png

BASIC authentication is applied between curl and Client and between Client and Config Server. Originally, OAuth 2.0 is probably better, but that's another opportunity.

The sample code is uploaded to GitHub.

environment

At the time of writing, Spring Cloud Finchley has not yet been officially released. Please note that the specifications may change in the future.

For the Config Server itself, see Maki-san's blog. However, please note that this is an article before the official release, so there are some differences from the current one.

Create Config repository

You can use Git or SVN as the repository. This time I will use GitHub. Click here for URL-> https://github.com/MasatoshiTada/app-config Please fork and use!

application.properties This setting is used for all applications and all profiles.

application.properties


message=Hello application!
common=common

application-xxx.properties This setting is used by all applications, but only by a specific profile ("xxx" is the profile name).

application-dev.properties


message=Hello application-dev!
common=common-dev

application-qa.properties


message=Hello application-qa!
common=common-qa

client.properties This setting is used for all profiles only in the application whose name is "client".

client.properties


message=Hello client!
server.port=8081
#Used for refreshing settings/Activate the refresh endpoint
management.endpoints.web.exposure.include=refresh
#Set the Spring Security log level to trace to make it easier to see the internal execution.
logging.level.org.springframework.security=trace

The third line, management.endpoints.web.exposure.include = refresh, is a new setting from Spring Boot 2.0. In Spring Boot 2.0, only the endpoints described in this setting are enabled (multiple comma separated).

client-xxx.properties This setting is used only for specific profiles only in applications whose name is "client" ("xxx" is the profile name).

client-dev.properties


message=Hello client-dev!
server.port=8082

client-qa.properties


message=Hello client-qa!
server.port=8083

foo.properties This setting is used for all profiles only in applications whose name is "foo".

foo.properties


message=Hello foo!
server.port=9001

foo-xxx.properties This setting is used only for specific profiles only in applications whose name is "foo" ("xxx" is the profile name).

foo-dev.properties


message=Hello foo-dev!
server.port=9002

foo-qa.properties


message=Hello foo-qa!
server.port=9003

Setting priority

If another file has a setting with the same name, it will be prioritized in the following order (the higher one will be prioritized).

  1. Application name-profile name.properties
  2. Application name.properties
  3. application-profile name.properties
  4. application.properties

Create Config Server

(1) Let's make a template with Spring Initializr. Decide "Group" and "Artifact" appropriately, and add "Config Server" and "Security" in "Dependencies".

スクリーンショット 2018-03-09 13.08.27.png

(2) Add @EnableConfigServer to the class that has the main () method.

@SpringBootApplication
@EnableConfigServer //Attach this
public class ConfigServerApplication {

    public static void main(String[] args) {
        SpringApplication.run(ConfigServerApplication.class, args);
    }
}

(3) Create a Java Config class for Spring Security. Create one user with any user name and password and enable BASIC authentication.

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // "client"Create a user
        //Do not use NoOpPasswordEncoder in production, use BCryptPasswordEncoder instead
        auth.inMemoryAuthentication()
                .passwordEncoder(NoOpPasswordEncoder.getInstance())
                .withUser("client").password("password").roles("CLIENT");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //Enable BASIC authentication so that all URLs must be authenticated before they can be accessed
        http.httpBasic();
        http.authorizeRequests()
                .anyRequest().authenticated();
    }
}

As mentioned in the comments, NoOpPasswordEncoder does not encode passwords at all and should not be used in production (already deprecated in Spring Security 5). This time I'm using it for learning only.

(4) Describe the settings in application.properties.

#URL of Git repository
spring.cloud.config.server.git.uri=https://github.com/MasatoshiTada/app-config.git

#This Config Server port number
server.port=8888

Make a client

(1) Let's make a template with Spring Initializr. Decide "Group" and "Artifact" appropriately, and add "Web", "Config Client", "Actuator" and "Security" in "Dependencies".

スクリーンショット 2018-03-09 13.09.59.png

(2) Create a Java Config class for Spring Security. Create two users with an arbitrary user name and password (separate roles and give them an arbitrary role name).

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // "ACTUATOR"When"USER"Whenいう2つのロールのユーザーを作る
        //Don't use NoOpPasswordEncoder in production!
        auth.inMemoryAuthentication()
                .passwordEncoder(NoOpPasswordEncoder.getInstance())
                .withUser("actuator").password("password").roles("ACTUATOR").and()
                .withUser("user").password("password").roles("USER");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //Enable BASIC authentication
        http.httpBasic();
        //Actuator endpoints can only be accessed by the ACTUATOR role,
        //Others can be accessed by any role as long as you are logged in
        http.authorizeRequests()
                .requestMatchers(EndpointRequest.toAnyEndpoint()).hasRole("ACTUATOR")
                .anyRequest().authenticated();

        //Do not disable CSRF in production!
        http.csrf().disable();
    }
}

This time we have disabled CSRF to avoid making the sample too complicated. Please do not disable it in the production environment, and obtain a token by some means before accessing with curl.

(3) Describe the settings in bootstrap.properties.

#Application name for this client
spring.application.name=client

#Config Server URL
spring.cloud.config.uri=http://localhost:8888/

#Username and password to access Config Server
#(Created with Java Config of Config Server)
spring.cloud.config.username=client
spring.cloud.config.password=password

bootstrap.properties describes the boot settings and is only used in the Spring Cloud environment.

(4) Create an appropriate controller that returns the value obtained from Config Server. Append @RefreshScope to the class.

@RefreshScope  // "POST /actuator/refresh"Will destroy the instance of this class
@RestController
@RequestMapping("/hello")
public class HelloController {

    private final String message;
    private final String common;

    //Get the value obtained from Config Server with the constructor
    public HelloController(@Value("${message}") String message, @Value("${common}") String common) {
        this.message = message;
        this.common = common;
    }

    @GetMapping
    public Map<String, String> hello() {
        HashMap<String, String> map = new HashMap<>();
        map.put("message", message);
        map.put("common", common);
        return map;
    }
}

Start Config Server and get settings with curl

(1) Start only Config Server. (2) You can get the value of application.properties by accessing / application / default.

$ curl -X GET -u client:password http://localhost:8888/application/default | jq
{
  "name": "application",
  "profiles": [
    "default"
  ],
  "label": null,
  "version": "e87b3c3ff0239394963e3bddf0a2982db5064339",
  "state": null,
  "propertySources": [
    {
      "name": "https://github.com/MasatoshiTada/app-config.git/application.properties",
      "source": {
        "message": "Hello application!",
        "common": "common"
      }
    }
  ]
}

(2) You can get the values of application.properties and application-dev.properties by accessing / application / dev.

$ curl -X GET -u client:password http://localhost:8888/application/dev | jq
{
  "name": "application",
  "profiles": [
    "dev"
  ],
  "label": null,
  "version": "e87b3c3ff0239394963e3bddf0a2982db5064339",
  "state": null,
  "propertySources": [
    {
      "name": "https://github.com/MasatoshiTada/app-config.git/application-dev.properties",
      "source": {
        "message": "Hello application-dev!",
        "common": "common-dev"
      }
    },
    {
      "name": "https://github.com/MasatoshiTada/app-config.git/application.properties",
      "source": {
        "message": "Hello application!",
        "common": "common"
      }
    }
  ]
}

Although not described, you can get the values of application.properties and application-qa.properties by accessing / application / qa.

(3) You can get the values of application.properties and client.properties by accessing / client / default.

$ curl -X GET -u client:password http://localhost:8888/client/default | jq
{
  "name": "client",
  "profiles": [
    "default"
  ],
  "label": null,
  "version": "e87b3c3ff0239394963e3bddf0a2982db5064339",
  "state": null,
  "propertySources": [
    {
      "name": "https://github.com/MasatoshiTada/app-config.git/client.properties",
      "source": {
        "message": "Hello client!",
        "server.port": "8081",
        "management.endpoints.web.exposure.include": "refresh",
        "logging.level.org.springframework.security": "trace"
      }
    },
    {
      "name": "https://github.com/MasatoshiTada/app-config.git/application.properties",
      "source": {
        "message": "Hello application!",
        "common": "common"
      }
    }
  ]
}

Although not described, you can get the values of application.properties and foo.properties by accessing / foo / default.

(4) By accessing / client / dev, you can get the values of application.properties, application-dev.properties, client.properties, client-dev.properties.

$ curl -X GET -u client:password http://localhost:8888/client/dev | jq
{
  "name": "client",
  "profiles": [
    "dev"
  ],
  "label": null,
  "version": "e87b3c3ff0239394963e3bddf0a2982db5064339",
  "state": null,
  "propertySources": [
    {
      "name": "https://github.com/MasatoshiTada/app-config.git/client-dev.properties",
      "source": {
        "message": "Hello client-dev!",
        "server.port": "8082"
      }
    },
    {
      "name": "https://github.com/MasatoshiTada/app-config.git/application-dev.properties",
      "source": {
        "message": "Hello application-dev!",
        "common": "common-dev"
      }
    },
    {
      "name": "https://github.com/MasatoshiTada/app-config.git/client.properties",
      "source": {
        "message": "Hello client!",
        "server.port": "8081",
        "management.endpoints.web.exposure.include": "refresh",
        "logging.level.org.springframework.security": "trace"
      }
    },
    {
      "name": "https://github.com/MasatoshiTada/app-config.git/application.properties",
      "source": {
        "message": "Hello application!",
        "common": "common"
      }
    }
  ]
}

Not listed, --You can get the values of application.properties, application-qa.properties, client.properties, client-qa.properties by accessing / client / qa. --You can get the values of application.properties, application-dev.properties, foo.properties, and foo-dev.properties by accessing / foo / dev. --You can get the values of application.properties, application-qa.properties, foo.properties, and foo-qa.properties by accessing / foo / qa.

Keep the Config Server running after the above checks.

Getting the value from the client

(1) Start the client without specifying any profile. (2) At startup, the client accesses Config Server with the URL tried in the previous chapter based on the application name specified in spring.application.name and the profile name being executed (in this case, / client. / default). Also, BASIC authentication is used for this access (user name client, password password). (3) Access localhost: 8081 / hello with curl.

$ curl -X GET -u user:password http://localhost:8081/hello | jq
{
  "common": "common",
  "message": "Hello client!"
}

"common" is the value of application.properties. "message" is also defined in application.properties, but the value of client.properties is used from the priority rule. The port number 8081 that is running is also the value of client.properties.

(4) Stop the client once and restart it with the dev profile. The profile is specified by a command line argument with "-" (in addition, it can be specified by environment variables, JVM system properties, etc.)

client $ mvn clean package
client $ java -jar target/client-0.0.1-SNAPSHOT.jar --spring.profiles.active=dev
client $ curl -X GET -u user:password http://localhost:8082/hello | jq
{
  "common": "common-dev",
  "message": "Hello client-dev!"
}

"common" is the value of application-dev.properties. "message" is the value of client-dev.properties. The port number 8082 that is running is also the value of client-dev.properties.

Change the value during execution

(1) Stop the currently running client and restart it without specifying a profile. (2) Use curl to access the / hello endpoint. The value of message is "Hello client!".

$ curl -X GET -u user:password http://localhost:8081/hello | jq
{
  "common": "common",
  "message": "Hello client!"
}

(3) On GitHub, rewrite the value of message in client.properties.

message=Hello client!!!!!!!!!!!!!!!!!!!!!!!!!

(4) Access the / hello endpoint with curl again. The value of message remains "Hello client!" And has not changed. This is because the value changed in GitHub has not been reflected on the client side yet.

$ curl -X GET -u user:password http://localhost:8081/hello | jq
{
  "common": "common",
  "message": "Hello client!"
}

(5) Access / actuator / refresh with curl. The client then accesses the Config Server, which gets the value from GitHub and passes it to the client.

$ curl -X POST -u actuator:password http://localhost:8081/actuator/refresh | jq
[
  "config.client.version",
  "message"
]

Note that the username specified with -u is ʻactuator. This is a user created in Java Config on the client and a user in the ʻACTUATOR role who has permission to access/ actuator / **.

(6) When refreshed, the instance of the bean with @RefreshScope added (in this case HelloController) is destroyed. The instance is recreated the first time the bean is needed after a refresh (in this case, when you access / hello with curl), and the new settings after the change are used.

$ curl -X GET -u user:password http://localhost:8081/hello | jq
{
  "common": "common",
  "message": "Hello client!!!!!!!!!!!!!!!!!!!!!!!!!"
}

Recommended Posts

Create Spring Cloud Config Server with security with Spring Boot 2.0
Create a web api server with spring boot
Create microservices with Spring Boot
Create a simple demo site with Spring Security with Spring Boot 2.1
Google Cloud Platform with Spring Boot 2.0.0
Achieve BASIC authentication with Spring Boot + Spring Security
Create a website with Spring Boot + Gradle (jdk1.8.x)
Create a simple search app with Spring Boot
Hash passwords with Spring Boot + Spring Security (with salt, with stretching)
Try LDAP authentication with Spring Security (Spring Boot) + OpenLDAP
Create CRUD apps with Spring Boot 2 + Thymeleaf + MyBatis
Create Spring Boot environment with Windows + VS Code
[Introduction to Spring Boot] Authentication function with Spring Security
Create a Spring Boot development environment with docker
Spring Security usage memo: Cooperation with Spring MVC and Boot
Implement a simple Rest API with Spring Security with Spring Boot 2.0
Create Restapi with Spring Boot ((1) Until Run of App)
Generate barcode with Spring Boot
Hello World with Spring Boot
Java Config with Spring MVC
Get started with Spring boot
Hello World with Spring Boot!
Run LIFF with Spring Boot
SNS login with Spring Boot
Login function with Spring Security
Spring Boot starting with Docker
Hello World with Spring Boot
Set cookies with Spring Boot
Use Spring JDBC with Spring Boot
Add module with Spring Boot
Getting Started with Spring Boot
Try using Spring Boot Security
Send email with spring boot
Use Basic Authentication with Spring Boot
Implemented authentication function with Spring Security ②
gRPC on Spring Boot with grpc-spring-boot-starter
Hot deploy with Spring Boot development
Spring Boot Tutorial Using Spring Security Authentication
Spring Boot programming with VS Code
Until "Hello World" with Spring Boot
Inquiry application creation with Spring Boot
Implemented authentication function with Spring Security ①
Get validation results with Spring Boot
Oauth2 authentication with Spring Cloud Gateway
(Intellij) Hello World with Spring Boot
Check date correlation with Spring Boot
I tried GraphQL with Spring Boot
[Java] LINE integration with Spring Boot
Beginning with Spring Boot 0. Use Spring CLI
I tried Flyway with Spring Boot
Ubuntu Server 20.04.1 Autoinstall with USB boot
Spring Boot gradle build with Docker
Implement a simple Web REST API server with Spring Boot + MySQL
I implemented an OAuth client with Spring Boot / Security (LINE login)
Part 1: Try using OAuth 2.0 Login supported by Spring Security 5 with Spring Boot
How to create your own Controller corresponding to / error with Spring Boot
Processing at application startup with Spring Boot
Create Spring Boot development environment on Vagrant
How to read Body of Request multiple times with Spring Boot + Spring Security
Create a Spring Boot app development project with the cURL + tar command
Hello World with Eclipse + Spring Boot + Maven