Skip to content

Latest commit

 

History

History
470 lines (385 loc) · 14.3 KB

File metadata and controls

470 lines (385 loc) · 14.3 KB

Spring Cloud Config Demo

Spring Boot is a very popular framework for creating microservices quickly and easily.

In this demo, I’ll show you how to:

  • Create a central configuration server using Spring Boot.

  • Create two separate microservices/applications using Spring Boot, which read their configuration from the server.

  • Secure the applications using OAuth 2.0 and Okta.

  • Demonstrate how to (securely) refresh a service’s configuration without redeploying.

Prerequisites:

Create a Spring Cloud Config Server

You can create a Spring Cloud Config Server using Spring Initialzr.

  1. Click this link or go to start.spring.io and select the following options in your browser:

    • Group: com.okta.dev

    • Artifact: config-server

    • Name: cloud-config-server

    • Description: Configuration Server

    • Package: com.okta.dev.configserver

    • Dependencies: Spring Security, Spring Web, Config Server

  2. Click Generate to download the project files. Unzip the file and import the project files into your favorite IDE.

  3. Update src/main/resources/application.properties to have the following key-value pairs:

    server.port=8888
    spring.cloud.config.server.native.search-locations=file://${user.home}/config
    spring.security.user.name=configUser
    spring.security.user.password=configPass
  4. Open your application’s main class and add the @EnableConfigServer annotation:

    import org.springframework.cloud.config.server.EnableConfigServer;
    
    @EnableConfigServer
    @SpringBootApplication
    public class CloudConfigServerApplication { ... }

Create an OpenID Connect Application

  1. Use the Okta CLI to create a new OIDC app:

    okta apps create
  2. Select Web > Other. Then, change the Redirect URI to:

    http://localhost:8001/login/oauth2/code/okta,http://localhost:8002/login/oauth2/code/okta
  3. Accept the defaults for the Logout Redirect URIs.

Configure Security for Your Microservices Architecture

In the search-locations directory, add several files:

service-one.yml
server:
  port: 8001

okta:
  oauth2:
    issuer: https://YOUR_OKTA_DOMAIN/oauth2/default
    clientId: YOUR_CLIENT_ID
    clientSecret: YOUR_CLIENT_SECRET
service-one-profile1.yml
hello:
  message: "Service One Profile One"
service-one-profile2.yml
hello:
  message: "Service One Profile Two"
service-two.yml
server:
  port: 8002

okta:
  oauth2:
    issuer: https://YOUR_OKTA_DOMAIN/oauth2/default
    clientId: YOUR_CLIENT_ID
    clientSecret: YOUR_CLIENT_SECRET
service-two-profile1.yml
hello:
  message: "Service Two Profile One"
service-two-profile2.yml
hello:
  message: "Service Two Profile Two"

The filenames are important and must be in a certain pattern for your microservices to pick them up:

/{application}/{profile}[/{label}]
/{application}-{profile}.yml
/{label}/{application}-{profile}.yml
/{application}-{profile}.properties
/{label}/{application}-{profile}.properties

Where:

  • {application} is the name of your microservice specified via your microservice’s spring.application.name property. In this case, service-one and service-two.

  • {profile} matches the list of profiles your microservice is running via the spring.profiles.active property. In this case, profile1 and profile2.

  • {label} is an additional descriptor usually corresponding to a version control branch, e.g. dev or stg. It can be manually set via the spring.cloud.config.label property in the microservice’s bootstrap.properties file or set on the command line (-Dspring.cloud.config.label).

Enter your config server’s project directory and run the application:

./mvnw spring-boot:run -Dspring-boot.run.profiles=native

The native profile tells the application to serve configuration files from the filesystem directory you populated above.

Create Spring Boot Microservice #1

  1. Open the Spring Initializr and select the following options or click here.

    • Group: com.okta.dev

    • Artifact: service-one

    • Name: service-one

    • Description: Microservice One

    • Package: com.okta.dev.service-one

    • Dependencies: Spring Web, Okta, Config Client, Actuator

  2. Click Generate and import the project files into your favorite IDE.

  3. Update src/main/resources/application.properties with the following key-value pairs:

    spring.application.name=service-one
    spring.config.import=configserver:
    spring.cloud.config.uri=http://localhost:8888
    spring.cloud.config.username=configUser
    spring.cloud.config.password=configPass
  4. To secure your microservice using Okta and OAuth 2.0, open your microservice’s main class and add the following configuration class:

    @Configuration
    public static class ApplicationSecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Override
        public void configure(HttpSecurity http) throws Exception {
            http
                .authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .oauth2Login();
        }
    }
  5. Add a basic REST controller, which will respond with a message defined in your service’s configuration file (hosted on the config server):

    @RestController
    @RequestMapping("/secure")
    public static class SecureController {
    
        @Value("${hello.message}")
        private String helloMessage;
    
        @GetMapping
        public String secure(Principal principal) {
            return helloMessage;
        }
    }
  6. Enter your config server’s project directory and run the application with profile1 set:

    cd /path/to/service-one
    ./mvnw spring-boot:run -Dspring-boot.run.profiles=profile1
  7. Open a browser and navigate to http://localhost:8001/secure. After successfully authenticating, you should see the following message:

    Service One Profile One

    This is the same message defined in the service-one-profile.yml file you created earlier. Neat!

  8. Switch your microservice’s active profile to profile2 and observe a different message. Stop your application and re-run with profile2 active:

    ./mvnw spring-boot:run -Dspring-boot.run.profiles=profile2
  9. Navigate to http://localhost:8001/secure. You should now see the message defined in service-one-profile2.yml:

    Service One Profile Two

Refresh the Configuration in Your Spring Cloud Config Server

Spring Cloud Config provides the ability to "live" reload your service’s configuration without stopping or re-deploying.

  1. Stop service-one and add the @RefreshScope annotation to your REST controller:

    import org.springframework.cloud.context.config.annotation.RefreshScope;
    ...
    
    @RefreshScope
    @RestController
    @RequestMapping("/secure")
    public static class SecureController { ... }

    When this annotation is applied to a Spring component (i.e., a @Component, @Service, @RestController, etc.), the component is re-created when a configuration refresh occurs, in this case giving an updated value for ${hello.message}.

    You can refresh an application’s configuration by including the Spring Boot Actuator dependency, exposing the /actuator/refresh endpoint, and sending an empty POST request.

  2. The Spring Boot Actuator has already been included in your microservice’s dependencies. Edit your configuration files to expose the refresh endpoint:

    service-one.yml
    server:
      port: 8001
    
    okta:
      oauth2:
        issuer: https://YOUR_OKTA_DOMAIN/oauth2/default
        clientId: YOUR_CLIENT_ID
        clientSecret: YOUR_CLIENT_SECRET
    
    management:
      endpoints:
        web:
          exposure:
            include: "refresh"
    service-two.yml
    server:
      port: 8002
    
    okta:
      oauth2:
        issuer: https://YOUR_OKTA_DOMAIN/oauth2/default
        clientId: YOUR_CLIENT_ID
        clientSecret: YOUR_CLIENT_SECRET
    
    management:
      endpoints:
        web:
          exposure:
            include: "refresh"
  3. Add a security class inside your main application class to secure the endpoint with basic authentication:

    @Configuration
    public static class ActuatorSecurityConfig extends WebSecurityConfigurerAdapter {
        @Override
        public void configure(HttpSecurity http) throws Exception {
            http
                .csrf().disable()
                .antMatcher("/actuator/*")
                .authorizeRequests()
                .antMatchers("/actuator/*").authenticated()
                .and()
                .httpBasic();
        }
    
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth.inMemoryAuthentication()
                .withUser("serviceOneUser")
                .password("{noop}serviceOnePassword")
                .roles("USER");
        }
    }

    Almost finished! Since your application is already authenticated with OIDC using Okta, you need to make these two security configuration classes play nicely with each other.

  4. Add the @Order annotations to both so ActuatorSecurityConfig takes precedence. This will allow you to refresh the configuration via /actuator/refresh without triggering the OAuth 2.0 flow.

    @Order(1)
    @Configuration
    public static class ActuatorSecurityConfig extends WebSecurityConfigurerAdapter { ... }
    
    @Order(2)
    @Configuration
    public static class ApplicationSecurityConfig extends WebSecurityConfigurerAdapter { ... }
  5. Start your application using profile1:

    ./mvnw spring-boot:run -Dspring-boot.run.profiles=profile1
  6. Navigate to http://localhost:8001/secure and note the message still says Service One Profile One.

  7. Open your configuration file at /path/to/config/folder/service-one-profile1.yml and edit the message:

    service-one-profile1.yml
    hello:
      message: "Things have changed"
  8. Save the file and refresh the page at http://localhost:8001/secure. Note that the message has not changed yet and still says Service One Profile One. To have your application receive the updated configuration, you must call the /actuator/refresh endpoint:

    curl -u serviceOneUser:serviceOnePassword -X POST http://localhost:8001/actuator/refresh
  9. Refresh the page at http://localhost:8001/secure, and you should see the updated message!

Create Spring Boot Microservice #2

Create a second Spring Boot application, acting as a second microservice, which will also have its configuration provided by your configuration server.

  1. Use the Spring Initializr with the following options or click this link.

    • Group: com.okta.dev

    • Artifact: service-two

    • Name: service-two

    • Description: Microservice Two

    • Package: com.okta.dev.service-two

    • Dependencies: Spring Web, Okta, Config Client, Actuator

  2. Click Generate and import the project files into your favorite IDE.

  3. Update src/main/resources/application.properties with the following properties:

    spring.application.name=service-two
    spring.config.import=configserver:
    spring.cloud.config.uri=http://localhost:8888
    spring.cloud.config.username=configUser
    spring.cloud.config.password=configPass

    Note the value for spring.application.name is different.

  4. Make the same changes to your main application class as above:

    public class ServiceTwoApplication {
    
        @Order(1)
        @Configuration
        public static class ActuatorSecurityConfig extends WebSecurityConfigurerAdapter {
            @Override
            public void configure(HttpSecurity http) throws Exception { ... }
    
            @Override
            protected void configure(AuthenticationManagerBuilder auth) throws Exception {
                auth.inMemoryAuthentication()
                    .withUser("serviceTwoUser")
                    .password("{noop}serviceTwoPassword")
                    .roles("USER");
            }
         }
    
        @Order(2)
        @Configuration
        public static class ApplicationSecurityConfig extends WebSecurityConfigurerAdapter { ... }
    
        @RefreshScope
        @RestController
        @RequestMapping("/secure")
        public static class SecureController {
    
            @Value("${hello.message}")
            private String helloMessage;
    
            @GetMapping
            public String secure(Principal principal) {
                return helloMessage;
            }
        }
    }

    Notice the different credentials for the in-memory user: serviceTwoUser / serviceTwoPassword.

  5. Run the application:

    cd /path/to/service-two
    ./mvnw spring-boot:run -Dspring-boot.run.profiles=profile1
  6. Navigate to http://localhost:8002/secure and authenticate with Okta. When you are redirected back to your application you will see the welcome message for service-two:

    Service Two Profile One

You’re done! You’ve created two microservices, secured by Okta and OAuth 2.0, which receive their configuration settings from a shared Spring Cloud Config server. Very cool! 😎

Learn More About Spring Cloud Config and Microservices