Spring Security Archives - Piotr's TechBlog https://piotrminkowski.com/tag/spring-security/ Java, Spring, Kotlin, microservices, Kubernetes, containers Mon, 28 Oct 2024 16:28:55 +0000 en-US hourly 1 https://wordpress.org/?v=6.9.1 https://i0.wp.com/piotrminkowski.com/wp-content/uploads/2020/08/cropped-me-2-tr-x-1.png?fit=32%2C32&ssl=1 Spring Security Archives - Piotr's TechBlog https://piotrminkowski.com/tag/spring-security/ 32 32 181738725 Spring Boot with SAML2 and Keycloak https://piotrminkowski.com/2024/10/28/spring-boot-with-saml2-and-keycloak/ https://piotrminkowski.com/2024/10/28/spring-boot-with-saml2-and-keycloak/#comments Mon, 28 Oct 2024 14:27:24 +0000 https://piotrminkowski.com/?p=15413 This article will teach you how to use SAML2 authentication with Spring Boot and Keycloak. Security Assertion Markup Language (SAML) is a standard for exchanging authentication and authorization identities between an Identity Provider (IdP) and a Service Provider. It is an XML-based protocol that uses security tokens with information about a principal. Currently, it is […]

The post Spring Boot with SAML2 and Keycloak appeared first on Piotr's TechBlog.

]]>
This article will teach you how to use SAML2 authentication with Spring Boot and Keycloak. Security Assertion Markup Language (SAML) is a standard for exchanging authentication and authorization identities between an Identity Provider (IdP) and a Service Provider. It is an XML-based protocol that uses security tokens with information about a principal. Currently, it is less popular than OICD (OpenID Connect) but is not outdated yet. In fact, many organizations still use SAML for SSO.

In our example, Keycloak will act as an Identity Provider. Keycloak supports SAML 2.0. We can also use Spring Security mechanisms supporting SAML authentication on the service provider side (our sample Spring Boot application). There are several articles about Spring Boot and SAML, but relatively few of them are up to date and use Keycloak as the IdP.

If you are interested in Keycloak and Spring Security you can read my article about microservices with Spring Cloud Gateway, OAuth2, and Keycloak. You can also take a look at the another post about best practices for securing Spring Boot microservices available here.

Source Code

If you would like to try this exercise by yourself, you may always take a look at my source code. First, you need to clone the following GitHub repository. It contains several sample Java applications for a Spring Boot security showcase. You must go to the saml directory, to proceed with exercise. The sample Spring Boot aplication is available in the callme-saml directory. Then you should follow my further instructions.

Prerequisites

Before we start the development, we must install some tools on our laptops. Of course, we should have Maven and at least Java 17 installed (Java 21 preferred). We must also have access to the container engine like Docker or Podman to run the Keycloak instance. 

Run Keycloak

We will run Keycloak as the Docker container. The repository contains the docker-compose.yml file in the saml directory and the realm manifest in the saml/config directory. Docker Compose run Keycloak in the development mode and imports the realm file on startup. Thanks to that you won’t have to create many resources in Keycloak by yourself. However, I’m describing step by step what should be done. The docker-compose.yml manifest also set the default administrator password to admin, enables HTTPS, and uses the Red Hat build of Keycloak instead of the community edition.

services:
  keycloak:
#    image: quay.io/keycloak/keycloak:24.0
    image: registry.redhat.io/rhbk/keycloak-rhel9:24-17
    environment:
      - KEYCLOAK_ADMIN=admin
      - KEYCLOAK_ADMIN_PASSWORD=admin
      - KC_HTTP_ENABLED=true
      - KC_HTTPS_CERTIFICATE_KEY_FILE=/opt/keycloak/conf/localhost.key.pem
      - KC_HTTPS_CERTIFICATE_FILE=/opt/keycloak/conf/localhost.crt.pem
      - KC_HTTPS_TRUST_STORE_FILE=/opt/keycloak/conf/truststore.jks
      - KC_HTTPS_TRUST_STORE_PASSWORD=123456
    ports:
      - 8080:8080
      - 8443:8443
    volumes:
      - /Users/pminkows/IdeaProjects/sample-spring-security-microservices/oauth/localhost-key.pem:/opt/keycloak/conf/localhost.key.pem
      - /Users/pminkows/IdeaProjects/sample-spring-security-microservices/oauth/localhost-crt.pem:/opt/keycloak/conf/localhost.crt.pem
      - /Users/pminkows/IdeaProjects/sample-spring-security-microservices/oauth/truststore.jks:/opt/keycloak/conf/truststore.jks
      - ./config/:/opt/keycloak/data/import:ro
    command: start-dev --import-realm
YAML

Finally, we must run the following command to start the instance of Keycloak.

docker compose up
ShellSession

Then, we should have the running instance of Keycloak exposes on the localhost over the HTTPS 8443 port.

spring-boot-saml2-keycloak-startup

After logging to the Keycloak UI with the admin / admin crendentials you should see the spring-boot-keycloak realm.

In the spring-boot-keycloak realm details there is a link to the SAML2 provider metadata file. We can download the file and pass directly to the Spring Boot application or save the link address for the future use. Keycloak published the IdP metadata for the spring-boot-keycloak realm under the https://localhost:8443/realms/spring-boot-keycloak/protocol/saml/descriptor link. Let’s copy that address to clipboard and proceed to the Spring Boot app implementation.

spring-boot-saml2-idp

Create Spring Boot App with SAML2 Support

Let’s begin with the dependencies list. We must include the Spring Web and Spring Security modules. For the SAML2 support we must add the spring-security-saml2-service-provider dependency. That dependency uses the OpenSAML library, which is published in the dedicated Shibboleth Maven repository. Our application also requires Thymeleaf to provide a single web page with an authenticated principal details.

<dependencies>
  <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
  <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-security</artifactId>
  </dependency>
  <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-thymeleaf</artifactId>
  </dependency>
  <dependency>
     <groupId>org.thymeleaf.extras</groupId>
     <artifactId>thymeleaf-extras-springsecurity6</artifactId>
  </dependency>
  <dependency>
     <groupId>org.springframework.security</groupId>
     <artifactId>spring-security-saml2-service-provider</artifactId>
  </dependency>
</dependencies>

<repositories>
  <repository>
    <id>shibboleth-build-releases</id>
    <name>Shibboleth Build Releases Repository</name>
    <url>https://build.shibboleth.net/nexus/content/repositories/releases/</url>
  </repository>
</repositories>
XML

Our goal is to start with something basic. The application will publish a metadata endpoint using the saml2Metadata DSL method. We also enable authorization at the method level with the @EnableMethodSecurity annotation. We can access the resources the after authentication in Keycloak.

@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.csrf(AbstractHttpConfigurer::disable)
                .authorizeHttpRequests(authorize -> authorize.anyRequest()
                        .authenticated())
                .saml2Login(withDefaults())
                .saml2Metadata(withDefaults());
        return http.build();
    }

}
Java

Here’s the Spring Boot application.yml file. It changes the default app HTTP port to the 8081. The SAML2 configuration must be provided within the spring.security.saml2.relyingparty.registration.{registrationId}.* properties. We must set the address of the IdP metadata file in the assertingparty.metadata-uri property. We also need to set the entity-id and the SSO service address. Both those settings are exposed in the metadata IdP file. We should also provided certificates for signing requests and verifying SAML responses. The key and certificate all already present in the repository.

server.port: 8081

spring:
  security:
    saml2:
      relyingparty:
        registration:
          keycloak:
            identityprovider:
              entity-id: https://localhost:8443/realms/spring-boot-keycloak
              verification.credentials:
                - certificate-location: classpath:rp-certificate.crt
              singlesignon.url: https://localhost:8443/realms/spring-boot-keycloak/protocol/saml
              singlesignon.sign-request: false
            signing:
              credentials:
                - private-key-location: classpath:rp-key.key
                  certificate-location: classpath:rp-certificate.crt
            assertingparty:
              metadata-uri: https://localhost:8443/realms/spring-boot-keycloak/protocol/saml/descriptor
YAML

Lets’ run our application with the following command:

mvn spring-boot:run
ShellSession

After startup, we can display a metadata endpoint exposed with the saml2Metadata DSL method. The most important element in the file is the entityID. Let’s save it for the future usage.

<md:EntityDescriptor entityID="http://localhost:8081/saml2/service-provider-metadata/keycloak">
  <md:SPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
    <md:KeyDescriptor use="signing">
      <ds:KeyInfo>
        <ds:X509Data>
          <ds:X509Certificate>...</ds:X509Certificate>
        </ds:X509Data>
      </ds:KeyInfo>
    </md:KeyDescriptor>
    <md:AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="http://localhost:8081/login/saml2/sso/keycloak" index="1"/>
  </md:SPSSODescriptor>
</md:EntityDescriptor>
XML

Configure Keycloak SAML2 Client

Let’s switch to the Keycloak UI. We must create a SAML client. The client ID must be the same as the entityID retrieved in the previous section. We should also set the application root URL and valid redirects URIs. Notice that you don’t have do anything – Keycloak imports all requeired configuration at startup from the exported realm manifest.

spring-boot-saml2-client

Don’t forget to create a test user with password. We will use to autenticate against Keycloak in the step.

This is the optional step. We can add the client scope with some mappers. This force Keycloak to pass information about user group, email, and surname in the authentication response.

Let’s include the newly created scope to the client scopes.

We can also create the admins group and assign our test user to that group.

After providing the whole configuration we can make a first test. Our application is already running. It will print the authenticated user details on the main site after signing in. Here’s the MainController class.

@Controller
public class MainController {

    @GetMapping("/")
    public String getPrincipal(@AuthenticationPrincipal Saml2AuthenticatedPrincipal principal, Model model) {
        String emailAddress = principal.getFirstAttribute("email");
        model.addAttribute("emailAddress", emailAddress);
        model.addAttribute("userAttributes", principal.getAttributes());
        return "index";
    }

}
Java

Here’s the main application site. It uses the Thymeleaf extension for Spring Security.

<!doctype html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org"
      xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity6">

<head>
    <title>SAML 2.0 Login</title>
</head>

<body>
    <main role="main" class="container">
        <h1 class="mt-5">SAML 2.0 Login with Spring Security</h1>
        <p class="lead">You are successfully logged in as <span sec:authentication="name"></span></p>
        <h2 class="mt-2">User Identity Attributes</h2>
        <table class='table table-striped'>
            <thead>
            <tr>
                <th>Attribute</th>
                <th>Value</th>
            </tr>
            </thead>
            <tbody>
            <tr th:each="userAttribute : ${userAttributes}">
                <th th:text="${userAttribute.key}"></th>
                <td th:text="${userAttribute.value}"></td>
            </tr>
            </tbody>
        </table>
    </main>
</body>

</html>
HTML

Once we access the application main site over the http://localhost:8081 URL, Spring should redirect us to the Keycloak login site. Let’s provide our test user credentials.

Success! We are logged in to the application. Our Spring Boot app prints the details taken from SAML2 authentication token.

spring-boot-saml2-auth

REST Methods Authorization

Let’s consider the following @RestController in our application. We have already enabled authorization at the method level with the @EnableMethodSecurity annotation. Our controller creates two REST endpoints. The GET /greetings/user endpoint requires the ROLE_USER authority granted, while the GET /greetings/admin requires the ROLE_ADMIN authority granted.

@RestController
@RequestMapping("/greetings")
public class GreetingController {

    @GetMapping("/user")
    @PreAuthorize("hasAuthority('ROLE_USER')")
    public String greeting() {
        return "I'm SAML user!";
    }

    @GetMapping("/admin")
    @PreAuthorize("hasAuthority('ROLE_ADMINS')")
    public String admin() {
        return "I'm SAML admin!";
    }

}
Java

Let’s call the GET /greetings/user endpoint. It returns the expected response.

Then, let’s call the GET /greetings/admin endpoint. Unfortunately, we don’t have the access to that endpoint. That’s because it requires the user to have the ROLE_ADMINS authority.

Previosuly, we used a default implementation the SAML2 authentication provider in Spring Boot. It sets only the ROLE_USER as the granted authorities. We can override that behaviour with the setResponseAuthenticationConverter method SAML2 AuthenticationProvider implementation. Here’s the current implementation. Our method tries to find the member attrobute in the SAML2 response token. Then, it maps the group name taken from that attribute to the Spring Security GrantedAuthority name. Finally, it returns a new Saml2Authentication object with a new list of GrantedAuthority objects.

@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {

    private static final Logger LOG = LoggerFactory
        .getLogger(SecurityConfig.class);

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {

        OpenSaml4AuthenticationProvider provider = 
            new OpenSaml4AuthenticationProvider();

        provider.setResponseAuthenticationConverter(token -> {
            var auth = OpenSaml4AuthenticationProvider
                .createDefaultResponseAuthenticationConverter()
                .convert(token);
            LOG.info("AUTHORITIES: {}", auth.getAuthorities());

            var attrValues = token.getResponse().getAssertions().stream()
                    .flatMap(as -> as.getAttributeStatements().stream())
                    .flatMap(attrs -> attrs.getAttributes().stream())
                        .filter(attrs -> attrs.getName().equals("member"))
                        .findFirst().orElseThrow().getAttributeValues();

            if (!attrValues.isEmpty()) {
                var member = ((XSStringImpl) attrValues.getFirst()).getValue();
                LOG.info("MEMBER: {}", member);
                List<GrantedAuthority> authoritiesList = List.of(
                        new SimpleGrantedAuthority("ROLE_USER"),
                        new SimpleGrantedAuthority("ROLE_" + 
                            member.toUpperCase().replaceFirst("/", ""))
                );

                LOG.info("NEW AUTHORITIES: {}", authoritiesList);
                return new Saml2Authentication(
                    (AuthenticatedPrincipal) auth.getPrincipal(), 
                    auth.getSaml2Response(), 
                    authoritiesList);
            } else return auth;
        });

        http.csrf(AbstractHttpConfigurer::disable)
                .authorizeHttpRequests(authorize -> authorize.anyRequest()
                        .authenticated())
                .saml2Login(saml2 -> saml2
                        .authenticationManager(new ProviderManager(provider))
                )
                .saml2Metadata(withDefaults());
        return http.build();
    }

}
Java

Let’s run a new version of our application.

mvn spring-boot:run
ShellSession

Once we open the http://localhost:8081 site once again we can take a look at the logs. I highlighted the part of the SAML response message that contains the member attribute. As you on the bottom, we created a new granted authorities list containing ROLE_USER and ROLE_ADMINS.

spring-boot-saml2-logs

Fibnally, we can call the GET /greetings/admin endpoint once again. It works!

spring-boot-saml2-admin

Final Thoughts

This article shows how to simply start with Spring Boot, SAML2 and Keycloak. It also provides more advanced solution with the custom authentication provider implementation that maps a user group name to the granted authority name. Although SAML2 is rather a mature technology, there are no many examples with Spring Boot, SAML2 and Keycloak. I hope that my article will fill this gap 🙂

The post Spring Boot with SAML2 and Keycloak appeared first on Piotr's TechBlog.

]]>
https://piotrminkowski.com/2024/10/28/spring-boot-with-saml2-and-keycloak/feed/ 5 15413
Microservices with Spring Cloud Gateway, OAuth2 and Keycloak https://piotrminkowski.com/2024/03/01/microservices-with-spring-cloud-gateway-oauth2-and-keycloak/ https://piotrminkowski.com/2024/03/01/microservices-with-spring-cloud-gateway-oauth2-and-keycloak/#comments Fri, 01 Mar 2024 09:02:45 +0000 https://piotrminkowski.com/?p=15018 This article will teach you how to use Keycloak to enable OAuth2 for Spring Cloud Gateway and Spring Boot microservices. We will extend the topics described in my previous article and analyze some of the latest features provided within the Spring Security project. Our architecture consists of two Spring Boot microservices, an API gateway built […]

The post Microservices with Spring Cloud Gateway, OAuth2 and Keycloak appeared first on Piotr's TechBlog.

]]>
This article will teach you how to use Keycloak to enable OAuth2 for Spring Cloud Gateway and Spring Boot microservices. We will extend the topics described in my previous article and analyze some of the latest features provided within the Spring Security project.

Our architecture consists of two Spring Boot microservices, an API gateway built on top of Spring Cloud Gateway, and a Keycloak authorization server. Spring Cloud Gateway acts here as an OAuth2 Client and OAuth2 Resource Server. For any incoming request, it verifies an access token before forwarding traffic to the downstream services. It initializes an authorization code flow procedure with Keycloak for any unauthenticated request. Our scenario needs to include the communication between internal microservices. They are both hidden behind the API gateway. The caller app invokes an endpoint exposed by the callme app. The HTTP client used in that communication has to use the access token sent by the gateway.

spring-oauth2-keycloak-arch

Source Code

If you would like to try this exercise yourself, you may always take a look at my source code. In order to do that, you need to clone my GitHub repository. Then switch to the oauth directory. You will find two Spring Boot microservices there: callme and caller. Of course, there is the gateway app built on top of Spring Cloud Gateway. After that, you should just follow my instructions. Let’s begin.

Run and Configure Keycloak

We are running Keycloak as a Docker container. By default, Keycloak exposes API and a web console on the port 8080. We also need to set an admin username and password with environment variables. Here’s the command used to run the Keycloak container:

$ docker run -d --name keycloak -p 8080:8080 \
    -e KEYCLOAK_ADMIN=admin \
    -e KEYCLOAK_ADMIN_PASSWORD=admin \
    quay.io/keycloak/keycloak:23.0.7 start-dev
ShellSession

Once the container starts, we can go to the UI admin console available under the http://localhost:8080/admin address. We will create a new realm. The name is that realm is demo. Instead of creating the required things manually, we can import the JSON resource file that contains the whole configuration of the realm. You can find such a resource file in my GitHub repository here: oauth/gateway/src/test/resources/realm-export.json. However, in the next parts of that section, we will use the Keycloak dashboard to create objects step by step. In case you import the configuration from the JSON resource file, you can just skip to the next section.

Then, we need to add a single OpenID Connect client to the demo realm. The name of our client is spring-with-test-scope. We should enable client authentication and put the right address in the “Valid redirect URIs” field (it can be the wildcard for testing purposes).

spring-oauth2-keycloak-client

We need to save the name of the client and its secret. Those two settings have to be set on the application side.

Then, let’s create a new client scope with the TEST name.

Then, we have to add the TEST to the spring-with-test-scope client scopes.

We also need to create a user to authenticate against Keycloak. The name of our user is spring. In order to set the password, we need to switch to the “Credentials” tab. For my user, I choose the Spring_123 password.

spring-oauth2-keycloak-user

Once we finish with the configuration, we can export it to the JSON file (the same file we can use when creating a new realm). Such a file will be useful later, for building automated tests with Testcontainers.

Unfortunately, Keycloak doesn’t export realm users to the file. Therefore, we need to add the following JSON to the users section in the exported file.

{
  "username": "spring",
  "email": "piotr.minkowski@gmail.com",
  "firstName": "Piotr",
  "lastName": "Minkowski",
  "enabled": true,
  "credentials": [
    {
      "type": "password",
      "value": "Spring_123"
    }
  ],
  "realmRoles": [
    "default-roles-demo",
    "USER"
  ]
}
JSON

Create Spring Cloud Gateway with OAuth2 Support and Keycloak

As I mentioned before, our gateway app will act as an OAuth2 Client and OAuth2 Resource Server. In that case, we include both the Spring Boot Auth2 Client Starter and the spring-security-oauth2-resource-server dependency. We also need to include the spring-security-oauth2-jose to decode JWT tokens automatically. Of course, we need to include the Spring Cloud Gateway Starter. Finally, we add dependencies for automated testing with JUnit. We will use Testcontainers to run the Keycloak container during the JUnit test. It can be achieved with the com.github.dasniko:testcontainers-keycloak dependency.

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.security</groupId>
  <artifactId>spring-security-oauth2-resource-server</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.security</groupId>
  <artifactId>spring-security-oauth2-jose</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-test</artifactId>
  <scope>test</scope>
</dependency>
<dependency>
  <groupId>com.github.dasniko</groupId>
  <artifactId>testcontainers-keycloak</artifactId>
  <version>3.2.0</version>
 <scope>test</scope>
</dependency>
<dependency>
  <groupId>org.testcontainers</groupId>
  <artifactId>junit-jupiter</artifactId>
  <version>1.19.6</version>
  <scope>test</scope>
</dependency>
XML

Let’s begin with the Spring Security configuration. First, we need to annotate the Configuration bean with @EnableWebFluxSecurity. That’s because Spring Cloud Gateway uses the reactive version of the Spring web module. The oauth2Login() method is responsible for redirecting an unauthenticated request to the Keycloak login page. On the other hand, the oauth2ResourceServer() method verifies an access token before forwarding traffic to the downstream services.

@Configuration
@EnableWebFluxSecurity
public class SecurityConfig {

    @Bean
    public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
        http.authorizeExchange(auth -> auth.anyExchange().authenticated())
                .oauth2Login(withDefaults())
                .oauth2ResourceServer((oauth2) -> oauth2.jwt(Customizer.withDefaults()));
        http.csrf(ServerHttpSecurity.CsrfSpec::disable);
        return http.build();
    }

}
Java

That’s not all. We also need to provide several configuration settings with the spring.security.oauth2 prefix. The Spring OAuth2 Resource Server module will use the Keycloak JWKS endpoint to verify incoming JWT tokens. In the Spring OAuth2 Client section, we need to provide the address of the Keycloak issuer realm. Of course, we also need to provide the Keycloak client credentials, choose the authorization grant type and scope.

spring.security.oauth2:
  resourceserver:
    jwt:
      jwk-set-uri: http://localhost:8080/realms/demo/protocol/openid-connect/certs
  client:
    provider:
      keycloak:
        issuer-uri: http://localhost:8080/realms/demo
    registration:
      spring-with-test-scope:
        provider: keycloak
        client-id: spring-with-test-scope
        client-secret: IWLSnakHG8aNTWNaWuSj0a11UY4lzxd9
        authorization-grant-type: authorization_code
        scope: openid
YAML

The gateway exposes a single HTTP endpoint by itself. It uses OAuth2AuthorizedClient bean to return the current JWT access token.

@SpringBootApplication
@RestController
public class GatewayApplication {

   private static final Logger LOGGER = LoggerFactory
      .getLogger(GatewayApplication.class);

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

   @GetMapping(value = "/token")
   public Mono<String> getHome(@RegisteredOAuth2AuthorizedClient OAuth2AuthorizedClient authorizedClient) {
      return Mono.just(authorizedClient.getAccessToken().getTokenValue());
   }

}
Java

That’s all about OAuth2 configuration in that section. We also need to configure routing on the gateway in the Spring application.yml file. Spring Cloud Gateway can forward OAuth2 access tokens downstream to the services it is proxying using the TokenRelay GatewayFilter. It is possible to set it as a default filter for all incoming requests. Our gateway forwards traffic to both our callme and caller microservices. I’m not using any service discovery in that scenario. By default, the callme app listens on the 8040 port, while the caller app on the 8020 port.

spring:
  application:
    name: gateway
  cloud:
    gateway:
      default-filters:
        - TokenRelay=
      routes:
        - id: callme-service
          uri: http://localhost:8040
          predicates:
            - Path=/callme/**
        - id: caller-service
          uri: http://localhost:8020
          predicates:
            - Path=/caller/**
YAML

Verify Tokens in Microservices with OAuth2 Resource Server

The list of dependencies for the callme and caller is pretty similar. They are exposing HTTP endpoints using the Spring Web module. Since the caller app uses the WebClient bean we also need to include the Spring WebFlux dependency. Once again, we need to include the Spring OAuth2 Resource Server module and the spring-security-oauth2-jose dependency for decoding JWT tokens.

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.security</groupId>
  <artifactId>spring-security-oauth2-resource-server</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.security</groupId>
  <artifactId>spring-security-oauth2-jose</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-webflux</artifactId>
</dependency>
XML

Here’s the configuration of the app security. This time we need to use the @EnableWebSecurity annotation since we have a Spring Web module. The oauth2ResourceServer() method verifies an access token with the Keyclock JWKS endpoint.

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests(authorize -> authorize.anyRequest().authenticated())
                .oauth2ResourceServer((oauth2) -> oauth2.jwt(Customizer.withDefaults()));
        return http.build();
    }
}
Java

Here’s the OAuth2 Resource Server configuration for Keycloak in the Spring application.yml file:

spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          jwk-set-uri: http://localhost:8080/realms/demo/protocol/openid-connect/certs
YAML

Let’s take a look at the implementation of the REST controller class. It is a single ping method. That method may be accessed only by the client with the TEST scope. It returns a list of assigned scopes taken from the Authentication bean.

@RestController
@RequestMapping("/callme")
public class CallmeController {

    @PreAuthorize("hasAuthority('SCOPE_TEST')")
    @GetMapping("/ping")
    public String ping() {
        SecurityContext context = SecurityContextHolder.getContext();
        Authentication authentication = context.getAuthentication();
        return "Scopes: " + authentication.getAuthorities();
    }
}
Java

This method can be invoked directly by the external client through the API gateway. However, also the caller app calls that endpoint inside its own “ping” endpoint implementation.

@RestController
@RequestMapping("/caller")
public class CallerController {

    private WebClient webClient;

    public CallerController(WebClient webClient) {
        this.webClient = webClient;
    }

    @PreAuthorize("hasAuthority('SCOPE_TEST')")
    @GetMapping("/ping")
    public String ping() {
        SecurityContext context = SecurityContextHolder.getContext();
        Authentication authentication = context.getAuthentication();

        String scopes = webClient
                .get()
                .uri("http://localhost:8040/callme/ping")
                .retrieve()
                .bodyToMono(String.class)
                .block();
        return "Callme scopes: " + scopes;
    }
}
Java

If the WebClient calls the endpoint exposed by the second microservice, it also has to propagate the bearer token. We can easily achieve it with the ServletBearerExchangeFilterFunction as shown below. Thanks to that Spring Security will look up the current Authentication and extract the AbstractOAuth2Token credential. Then, it will propagate that token in the Authorization header automatically.

@SpringBootApplication
public class CallerApplication {

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

    @Bean
    public WebClient webClient() {
        return WebClient.builder()
                .filter(new ServletBearerExchangeFilterFunction())
                .build();
    }
    
}
Java

Testing with Running Applications

We can run all three Spring Boot apps using the same Maven command. Let’s begin with the gateway app:

$ cd oauth/gateway
$ mvn spring-boot:run
ShellSession

Once we run the first app, we can check out the logs if everything works fine. Here are the logs generated by the gateway app. As you see, it listens on the 8060 port.

spring-oauth2-keycloak-run-app

After that, we can run e.g. the caller app.

$ cd oauth/caller
$ mvn spring-boot:run
ShellSession

It listens on the 8020 port.

Of course, the order of starting apps doesn’t matter. As the last one, we can run the callme app.

$ cd oauth/callme
$ mvn spring-boot:run
ShellSession

Now, let’s call the caller app endpoint through the gateway. In that case, we need to go to the http://localhost:8060/caller/ping URL. The gateway app will redirect us to the Keycloak login page. We need to sign in there with the spring user and Spring_123 password.

spring-oauth2-keycloak-signin

After we sign in, everything happens automatically. Spring Cloud Gateway obtains the access token from Keycloak and then sends it to the downstream service. Once the caller app receives the request, it invokes the callme app using the WebClient instance. Here’s the result:

We can easily get the access token using the endpoint GET /token exposed by the gateway app.

Now, we can perform a similar call as before, but with the curl command. We need to copy the token string and put it inside the Authorization header as a bearer token.

$ curl http://localhost:8060/callme/ping \
    -H "Authorization: Bearer <TOKEN>" -v
ShellSession

Here’s my result:

spring-oauth2-keycloak-curl

Now, let’s do a similar thing, but in a fully automated way with JUnit and Testcontainers.

Spring OAuth2 with Keycloak Testcontainers

We need to switch to the gateway module once again. We will implement tests that run the API gateway app, connect it to the Keycloak instance, and route the authorized traffic in the target endpoint. Here’s the @RestController in the src/test/java directory that simulates the callme app endpoint:

@RestController
@RequestMapping("/callme")
public class CallmeController {

    @PreAuthorize("hasAuthority('SCOPE_TEST')")
    @GetMapping("/ping")
    public String ping() {
        return "Hello!";
    }
}
Java

Here’s the required configuration to run the tests. We are starting the gateway app on the 8060 port and using the WebTestClient instance for calling it. In order to automatically configure Keycloak we will import the demo realm configuration stored in the realm-export.json. Since Testcontainers use random port numbers we need to override some Spring OAuth2 configuration settings. We also override the Spring Cloud Gateway route, to forward the traffic to our test implementation of the callme app controller instead of the real service. That’s all. We can proceed to the tests implementation.

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
@Testcontainers
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class GatewayApplicationTests {

   static String accessToken;

   @Autowired
   WebTestClient webTestClient;

   @Container
   static KeycloakContainer keycloak = new KeycloakContainer()
            .withRealmImportFile("realm-export.json")
            .withExposedPorts(8080);

   @DynamicPropertySource
   static void registerResourceServerIssuerProperty(DynamicPropertyRegistry registry) {
      registry.add("spring.security.oauth2.client.provider.keycloak.issuer-uri",
                () -> keycloak.getAuthServerUrl() + "/realms/demo");
      registry.add("spring.security.oauth2.resourceserver.jwt.jwk-set-uri",
                () -> keycloak.getAuthServerUrl() + "/realms/demo/protocol/openid-connect/certs");
      registry.add("spring.cloud.gateway.routes[0].uri",
                () -> "http://localhost:8060");
      registry.add("spring.cloud.gateway.routes[0].id", () -> "callme-service");
      registry.add("spring.cloud.gateway.routes[0].predicates[0]", () -> "Path=/callme/**");
   }

   // TEST IMPLEMENTATION ...

}
Java

Here’s our first test. Since it doesn’t contain any token it should be redirected into the Keycloak authorization mechanism.

@Test
@Order(1)
void shouldBeRedirectedToLoginPage() {
   webTestClient.get().uri("/callme/ping")
             .exchange()
             .expectStatus().is3xxRedirection();
}
Java

In the second test, we use the WebClient instance to interact with the Keycloak container. We need to authenticate against Kecloak with the spring user and the spring-with-test-scope client. Keycloak will generate and return an access token. We will save its value for the next test.

@Test
@Order(2)
void shouldObtainAccessToken() throws URISyntaxException {
   URI authorizationURI = new URIBuilder(keycloak.getAuthServerUrl() + "/realms/demo/protocol/openid-connect/token").build();
   WebClient webclient = WebClient.builder().build();
   MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
   formData.put("grant_type", Collections.singletonList("password"));
   formData.put("client_id", Collections.singletonList("spring-with-test-scope"));
   formData.put("username", Collections.singletonList("spring"));
   formData.put("password", Collections.singletonList("Spring_123"));

   String result = webclient.post()
                .uri(authorizationURI)
                .contentType(MediaType.APPLICATION_FORM_URLENCODED)
                .body(BodyInserters.fromFormData(formData))
                .retrieve()
                .bodyToMono(String.class)
                .block();
   JacksonJsonParser jsonParser = new JacksonJsonParser();
   accessToken = jsonParser.parseMap(result)
                .get("access_token")
                .toString();
   assertNotNull(accessToken);
}
Java

Finally, we run a similar test as in the first step. However, this time, we provide an access token inside the Authorization header. The expected response is 200 OK and the “Hello!” payload, which is returned by the test instance of the CallmeController bean.

@Test
@Order(3)
void shouldReturnToken() {
   webTestClient.get().uri("/callme/ping")
                .header("Authorization", "Bearer " + accessToken)
                .exchange()
                .expectStatus().is2xxSuccessful()
                .expectBody(String.class).isEqualTo("Hello!");
}
Java

Let’s run all the tests locally. As you see, they are all successfully finished.

Final Thoughts

After publishing my previous article about Spring Cloud Gateway and Keycloak I received a lot of comments and questions with a request for some clarifications. I hope that this article answers some of them. We focused more on automation and service-to-service communication than just on the OAuth2 support in the Spring Cloud Gateway. We considered a case where a gateway acts as the OAuth2 client and resource server at the same time. Finally, we used Testcontainers to verify our scenario with Spring Cloud Gateway and Keycloak.

The post Microservices with Spring Cloud Gateway, OAuth2 and Keycloak appeared first on Piotr's TechBlog.

]]>
https://piotrminkowski.com/2024/03/01/microservices-with-spring-cloud-gateway-oauth2-and-keycloak/feed/ 18 15018
Spring Microservices Security Best Practices https://piotrminkowski.com/2021/05/26/spring-microservices-security-best-practices/ https://piotrminkowski.com/2021/05/26/spring-microservices-security-best-practices/#comments Wed, 26 May 2021 09:59:07 +0000 https://piotrminkowski.com/?p=9769 In this article, I’ll describe several best practices for building microservices with Spring Boot and Spring Security. I’m going to focus only on the aspects related to security. If you are interested in the general list of best practices for building microservices with Spring Boot read my article Spring Boot Best Practices for Microservices. On […]

The post Spring Microservices Security Best Practices appeared first on Piotr's TechBlog.

]]>
In this article, I’ll describe several best practices for building microservices with Spring Boot and Spring Security. I’m going to focus only on the aspects related to security. If you are interested in the general list of best practices for building microservices with Spring Boot read my article Spring Boot Best Practices for Microservices. On the other hand, if you plan to run your applications on Kubernetes, you might be interested in the article Best Practices For Microservices on Kubernetes.

Before we start with a list of security “golden rules”, let’s analyze a typical microservices architecture. We will focus on components important for building a secure solution.

The picture visible below illustrates a typical microservices architecture built with Spring Cloud. There is an API gateway built on top of Spring Cloud Gateway. Since it is an entry point to our system, we will enable some important security mechanisms on it. There are several microservices hidden behind the gateway. There is also a discovery server, which allows localizing IP addresses using the name of services. And finally, there are some components that do not take part in communication directly. It is just a proposition of a few selected tools. You may choose other solutions providing the same features. Vault is a tool for securely storing and accessing secrets. Keycloak is an open-source identity and access management solution. Spring Cloud Config Server provides an HTTP API for external configuration. It may integrate with several third-party tools including Vault.

spring-security-best-practices-arch

Let’s begin. Here’s our list of Spring Security best practices.

1. Enable rate limiting on the API gateway

An API gateway is an important pattern in microservice-based architecture. It acts as a single entry point into the whole system. It is responsible not only for request routing but also for several other things including security. Consequently, one of the most essential components of security we should enable on the gateway is rate limiting. It protects your API against DoS attacks, which can tank a server with unlimited HTTP requests.

Spring provides its own implementation of the API gateway pattern called Spring Cloud Gateway. On the other hand, Spring Cloud Gateway comes with a built-in implementation of a rate-limiting component. To sum up, you just need to include a single dependency to build a gateway application. Then you have to provide some configuration settings to enable rate limiting for a single route.

In order to enable a rate limiter on a gateway, we need to use a component called RequestRateLimiter GatewayFilter factory. It uses a RateLimiter implementation to determine if the current request is allowed to proceed. Otherwise, the gateway returns a status of HTTP 429 - Too Many Requests. The RequestRateLimiter implementation uses Redis as a backend.

Let’s take a look at a typical configuration of routes handled by Spring Cloud Gateway. There are three parameters that can be used to configure the rate limiter: replenishRate, burstCapacity, and requestedTokens. The replenishRate property is how many requests per second a single user may send. The burstCapacity property is the maximum number of requests a user can send in a single second. With the requestedTokens property, we may set the cost of a single token.

spring:
  cloud:
    gateway:
      routes:
        - id: account-service
          uri: http://localhost:8090
          predicates:
            - Path=/account/**
          filters:
            - RewritePath=/account/(?<path>.*), /$\{path}
            - name: RequestRateLimiter
              args:
                redis-rate-limiter.replenishRate: 1
                redis-rate-limiter.burstCapacity: 60
                redis-rate-limiter.requestedTokens: 15

Then we should define a KeyResolver bean. A rate limiter defines all parameters per a single key returned by the resolver.

@Bean
KeyResolver authUserKeyResolver() {
   return exchange -> ReactiveSecurityContextHolder.getContext()
            .map(ctx -> ctx.getAuthentication().getPrincipal().toString());
}

For more details, you may refer to the article Secure Rate Limiting with Spring Cloud Gateway.

2. Generate and propagate certificates dynamically

Should we use SSL in microservice-to-microservice communication? Of course yes. But the question is how will you handle certificates used by your microservices. There are several best practices related to SSL certificate management. For example, you should not issue certificates for long time periods. You should also automatically renew or refresh them.

There are some tools that can help in following best practices. One of the most popular of them is Vault from Hashicorp. It provides the PKI secrets engine, which is responsible for generating dynamic X.509 certificates. The simplest way to try Vault is to run it locally on a Docker container.

$ docker run --cap-add=IPC_LOCK -d --name vault -p 8200:8200 vault

Then, we may enable and configure the PKI engine in some simple steps. You can do it using Vault CLI or UI. For some more detailed information about it read my article SSL with Spring WebFlux and Vault PKI. For now, let’s enable PKI with TTL and then configure CA using CLI as shown below.

$ vault secrets enable pki
$ vault secrets tune -max-lease-ttl=8760h pki
$ vault write pki/root/generate/internal \
    common_name=my-website.com \
    ttl=8760h

In the next step, we will use Spring VaultTemplate to issue a certificate dynamically. The fragment of code visible below shows how to create a certificate request with 12h TTL and localhost as a Common Name. Firstly, let’s build such a request using the VaultCertificateRequest object. Then we will invoke the issueCertificate method on the VaultPkiOperations object. The generated CertificateBundle contains both a certificate and a private key.

private CertificateBundle issueCertificate() throws Exception {
   VaultPkiOperations pkiOperations = vaultTemplate.opsForPki("pki");
   VaultCertificateRequest request = VaultCertificateRequest.builder()
        .ttl(Duration.ofHours(12))
        .commonName("localhost")
        .build();
   VaultCertificateResponse response = pkiOperations
      .issueCertificate("default", request);
   CertificateBundle certificateBundle = response.getRequiredData();
   return certificateBundle;
}

Finally, we just need to use the method for generating a certificate in Vault on runtime. The default behavior of our web server needs to be overridden. To do that, we need to create a Spring @Component that implements WebServerFactoryCustomizer. Depending on the web server we need to customize a different WebServerFactory. Typically, for Spring MVC it is Tomcat and Netty for Spring WebFlux. Inside the customize method, we are generating a certificate and store it inside the keystore (cert + private key) and truststore (CA).

@Component
public class GatewayServerCustomizer implements 
         WebServerFactoryCustomizer<NettyReactiveWebServerFactory> {
   @SneakyThrows
   @Override
   public void customize(NettyReactiveWebServerFactory factory) {
      String keyAlias = "vault";
      CertificateBundle bundle = issueCertificate();
      KeyStore keyStore = bundle.createKeyStore(keyAlias);
      String keyStorePath = saveKeyStoreToFile("server-key.pkcs12", keyStore);
      Ssl ssl = new Ssl();
      ssl.setEnabled(true);
      ssl.setClientAuth(Ssl.ClientAuth.NEED);
      ssl.setKeyStore(keyStorePath);
      ssl.setKeyAlias(keyAlias);
      ssl.setKeyStoreType(keyStore.getType());
      ssl.setKeyPassword("");
      ssl.setKeyStorePassword("123456");
      X509Certificate caCert = bundle.getX509IssuerCertificate();
      log.info("CA-SerialNumber: {}", caCert.getSerialNumber());
      KeyStore trustStore = KeyStore.getInstance("pkcs12");
      trustStore.load(null, null);
      trustStore.setCertificateEntry("ca", caCert);
      String trustStorePath = saveKeyStoreToFile("server-trust.pkcs12", trustStore);
      ssl.setTrustStore(trustStorePath);
      ssl.setTrustStorePassword("123456");
      ssl.setTrustStoreType(trustStore.getType());
      factory.setSsl(ssl);
      factory.setPort(8443);
   }
}

3. Use SSL in microservices communication

Since using SSL on the edge of a microservices-based system is obvious, inter-service communication is sometimes considered to be non-secure. My recommendation is always the same. Better use SSL or not 🙂 But we should think about securing at least components that store sensitive data. One of them will probably be a config server. That’s obviously one of the Spring Security best practices. For Spring Boot microservices we can use a component called Spring Cloud Config Server. Since it is built on top of Spring MVC we may easily enable a secure connection on the server-side.

server:
  port: ${PORT:8888}
  ssl:
    enabled: true
    client-auth: need
    key-store: classpath: server-key.jks
    key-store-password: 123456
    key-alias: configserver
    trust-store: classpath: server-trust.jks
    trust-store-password: 123456

On the client side, we use a component called Spring Cloud Config Client. Since it is responsible for connecting with the server, we also need to handle SSL there. To do that we need to override the RestTemplate SSL configuration on the ConfigServicePropertySourceLocator bean. The fragment of code visible below uses a self-signed certificate, but we can easily implement here a strategy described in the previous section.

@Configuration
public class SSLConfigServiceBootstrapConfiguration {
   @Autowired
   ConfigClientProperties properties;
   @Bean
   public ConfigServicePropertySourceLocator configServicePropertySourceLocator() throws Exception {
      final char[] password = "123456".toCharArray();
      final ClassPathResource resource = new ClassPathResource("account.jks");
      SSLContext sslContext = SSLContexts.custom()
         .loadKeyMaterial(resource.getFile(), password, password)
         .loadTrustMaterial(resource.getFile(), password, new TrustSelfSignedStrategy()).build();
      CloseableHttpClient httpClient = HttpClients.custom()
         .setSSLContext(sslContext)
         .setSSLHostnameVerifier((s, sslSession) -> true)
         .build();
      HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
      ConfigServicePropertySourceLocator configServicePropertySourceLocator = new ConfigServicePropertySourceLocator(properties);
      configServicePropertySourceLocator.setRestTemplate(new RestTemplate(requestFactory));
      return configServicePropertySourceLocator;
   }
}

In the latest version of Spring Cloud Config, we can enable TLS traffic encryption in configuration. We just need to define the right settings using properties with a prefix spring.cloud.config.tls.*.

What about encrypting communication between applications and a discovery server? You can choose between several available discovery servers supported in Spring Cloud. But let’s assume we use Eureka. Similarly to Spring Cloud Config, we use a high-level client to communicate with a server. So, in that case, we need to define a bean DiscoveryClientOptionalArgs, and also override SSL settings on the HTTP client there. The Eureka client uses the Jersey HTTP client, so we need to create an instance of EurekaJerseyClientBuilder to override the SSL configuration.

@Bean
public DiscoveryClient.DiscoveryClientOptionalArgs discoveryClientOptionalArgs() throws Exception {
   DiscoveryClient.DiscoveryClientOptionalArgs args = new DiscoveryClient.DiscoveryClientOptionalArgs();
   final char[] password = "123456".toCharArray();
   final ClassPathResource resource = new ClassPathResource("account.jks");
   SSLContext sslContext = SSLContexts.custom()
	.loadKeyMaterial(resource.getFile(), password, password)
	.loadTrustMaterial(resource.getFile(), password, new TrustSelfSignedStrategy()).build();
   EurekaJerseyClientBuilder builder = new EurekaJerseyClientBuilder();	 
   builder.withClientName("account-client");
   builder.withMaxTotalConnections(10);
   builder.withMaxConnectionsPerHost(10);
   builder.withCustomSSL(sslContext);
   args.setEurekaJerseyClient(builder.build());
   return args;
}

Finally, we may configure HTTPS in communication between microservices. Since we use RestTemplate or WebClient instances directly on the client side it is relatively easy to implement secure communication in that case.

4. Keep configuration data encrypted

The current one of the best practices for Spring microservices security is related to a configuration server. We should encrypt at least sensitive data like passwords or secrets stored there. Spring Cloud Config Server provides a built-in mechanism for that. But we can also use Vault as a backend store for Spring Cloud Config Server, where all data is encrypted by default.

We will start with a default encrypt mechanism provided by Spring Cloud Config Server. Firstly, we need to enable it in the configuration properties.

spring:
  cloud:
    config:
      server:
        encrypt:
          enabled: true

Then, we have to configure a key store responsible for encrypting our sensitive data.

encrypt:
  keyStore:
    location: classpath:/config.jks
    password: 123456
    alias: config
    secret: 123456

Finally, we can set encrypted data instead of plain string with {cipher} prefix.

spring:  
  application:
    name: account-service
  security:
    user:
      password: '{cipher}AQBhpDVYHANrg59OGY7ioSbMdOrH7ZA0vfa2VqIvfxJK5vQp...'

Alternatively, you can use it as a configuration data backend. To do that you should enable a Spring profile called vault.

spring.profiles.active=vault

Then, we may add an example secret.

$ vault write secret/hello value=world
$ vault read secret/hello

5. Restrict access to the API resources

In the previous sections, we discussed such topics as authentication, traffic, and data encryption. But another important aspect of securing your applications is authorization and access to the API resources. If you think about web app authorization, the first approach that probably comes to your mind is OAuth 2.0 or OpenID Connect. OAuth 2.0 is the industry-standard protocol for authorization. Of course, it is supported by Spring Security. There are also multiple OAuth2 providers you can integrate your application with. One of them is Keycloak. I will use it in the example in this article. Firstly, let’s run Keycloak on a Docker container. By default, it exposes API and a web console on the port 8080.

$ docker run -d --name keycloak -p 8888:8080 \
   -e KEYCLOAK_USER=spring \
   -e KEYCLOAK_PASSWORD=spring123 \
   jboss/keycloak

We are going to enable and configure OAuth 2.0 support on the API gateway. Besides spring-cloud-starter-gateway dependency, we need to include spring-boot-starter-oauth2-client and spring-cloud-starter-security to activate the TokenRelay filter. Then we have to provide the Spring Security configuration settings for the OAuth2 client.

spring:
  security:
    oauth2:
      client:
        provider:
          keycloak:
            token-uri: http://localhost:8080/auth/realms/master/protocol/openid-connect/token
            authorization-uri: http://localhost:8080/auth/realms/master/protocol/openid-connect/auth
            userinfo-uri: http://localhost:8080/auth/realms/master/protocol/openid-connect/userinfo
            user-name-attribute: preferred_username
        registration:
          keycloak-with-test-scope:
            provider: keycloak
            client-id: spring-with-test-scope
            client-secret: c6480137-1526-4c3e-aed3-295aabcb7609
            authorization-grant-type: authorization_code
            redirect-uri: "{baseUrl}/login/oauth2/code/keycloak"
          keycloak-without-test-scope:
            provider: keycloak
            client-id: spring-without-test-scope
            client-secret: f6fc369d-49ce-4132-8282-5b5d413eba23
            authorization-grant-type: authorization_code
            redirect-uri: "{baseUrl}/login/oauth2/code/keycloak"

In the last step, we need to configure the Spring Security filter. Since Spring Cloud Gateway is built on top of Spring WebFlux, we need to annotate the configuration bean with @EnableWebFluxSecurity. Inside the filterChain method we are going to enable authorization for all the exchanges. We will also set OAuth2 as a default login method and finally disable CSRF.

@Configuration
@EnableWebFluxSecurity
public class SecurityConfig {
   @Bean
   public SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
      http.authorizeExchange(exchanges -> exchanges.anyExchange().authenticated())
         .oauth2Login(withDefaults());
      http.csrf().disable();
      return http.build();
   }
}

As I mentioned before, we will have a token relay pattern between the gateway and microservices. A Token Relay is where an OAuth2 consumer acts as a Client and forwards the incoming token to outgoing resource requests. So, now let’s enable global method security and OAuth2 resources server for the downstream services.

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
   protected void configure(HttpSecurity http) throws Exception {
      http.authorizeRequests(authorize -> authorize.anyRequest().authenticated())
         .oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
   }
}

After that, it is possible to configure role-based access using @PreAuthorize and @PostAuthorize. Let’s take a look at the implementation of the REST controller class. It is a single ping method. That method may be accessed only by the client with the TEST scope. For more implementation details you may refer to the article Spring Cloud Gateway OAuth2 with Keycloak.

@RestController
@RequestMapping("/callme")
public class CallmeController {
   @PreAuthorize("hasAuthority('SCOPE_TEST')")
   @GetMapping("/ping")
   public String ping() {
      SecurityContext context = SecurityContextHolder.getContext();
      Authentication authentication = context.getAuthentication();
      return "Scopes: " + authentication.getAuthorities();
   }
}

6. Dynamically generate credentials to the external systems

Does your application connect to external systems like databases or message brokers? How do you store the credentials used by your application? Of course, we can always encrypt sensitive data, but if we work with many microservices having separate databases it may not be a very comfortable solution. Here comes Vault with another handy mechanism. Its database secrets engine generates database credentials dynamically based on configured roles. We may also take advantage of dynamically generated credentials for RabbitMQ, Nomad, and Consul.

Firstly, let’s enable the Vault database engine, which is disabled by default.

$ vault secrets enable database

Let’s assume our application connects to the Postgres database. Therefore, we need to configure a Vault plugin for PostgreSQL database and then provide connection settings and credentials.

$ vault write database/config/postgres \
  plugin_name=postgresql-database-plugin \
  allowed_roles="default" \
  connection_url="postgresql://{{username}}:{{password}}@localhost:5432?sslmode=disable" \
  username="postgres" \
  password="postgres123456"

Then we need to create a database role. The name of the role should be the same as the name passed in field allowed_roles in the previous step. We also have to set a target database name and SQL statement that creates users with privileges.

$ vault write database/roles/default db_name=postgres \
  creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}';GRANT SELECT, UPDATE, INSERT ON ALL TABLES IN SCHEMA public TO \"{{name}}\";GRANT USAGE,  SELECT ON ALL SEQUENCES IN SCHEMA public TO \"{{name}}\";" \
  default_ttl="1h" max_ttl="24h"

Thanks to Spring Cloud Vault project we can easily integrate any Spring Boot application with the Vault databases engine. Two dependencies need to be included in Maven pom.xml to enable that support. Of course, we also need dependencies for the JPA and Postgres driver.

<dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-vault-config</artifactId>
</dependency>
<dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-vault-config-databases</artifactId>
</dependency>
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
   <groupId>org.postgresql</groupId>
   <artifactId>postgresql</artifactId>
</dependency>

The only thing we have to do is to configure integration with Vault via Spring Cloud Vault. The following configuration settings should be placed in bootstrap.yml (no application.yml). You may consider running the application on Nomad.

spring:
  cloud:
    vault:
      uri: http://localhost:8200
      token: ${VAULT_TOKEN}
      postgresql:
        enabled: true
        role: default
        backend: database
  datasource:
    url: jdbc:postgresql://localhost:5432/posgtres

The important part of the configuration visible above is under the property spring.cloud.vault.postgresql. Following Spring Cloud documentation “Username and password are stored in spring.datasource.username and spring.datasource.password so using Spring Boot will pick up the generated credentials for your DataSource without further configuration”. For more details about integration between Spring Cloud and Vault database engine, you may refer to my article Secure Spring Cloud Microservices with Vault and Nomad.

7. Always be up to date

This one of the best practices may be applied anywhere not only as a rule to Spring microservices security. We usually use open-source libraries in our applications, so it is important to include the latest versions of them. They may contain critical updates for publicly disclosed vulnerabilities contained within a project’s dependencies. There are also several dependency scanners like Snyk or OWASP.

Final thoughts

That is my private list of best practices for Spring Boot microservices security. Of course, most of them are not related just to a single framework, and we apply them for any other framework or toolkit. Do you have your own list of Spring Security best practices? Don’t afraid to share it in the comments.

The post Spring Microservices Security Best Practices appeared first on Piotr's TechBlog.

]]>
https://piotrminkowski.com/2021/05/26/spring-microservices-security-best-practices/feed/ 18 9769
Secure Rate Limiting with Spring Cloud Gateway https://piotrminkowski.com/2021/05/21/secure-rate-limiting-with-spring-cloud-gateway/ https://piotrminkowski.com/2021/05/21/secure-rate-limiting-with-spring-cloud-gateway/#comments Fri, 21 May 2021 11:12:04 +0000 https://piotrminkowski.com/?p=9733 In this article, you will learn how to enable rate limiting for an authenticated user with Spring Cloud Gateway. Why it is important? API gateway is an entry point to your microservices system. Therefore, you should provide there a right level of security. Rate limiting can prevent your API against DoS attacks and limit web scraping. You can easily […]

The post Secure Rate Limiting with Spring Cloud Gateway appeared first on Piotr's TechBlog.

]]>
In this article, you will learn how to enable rate limiting for an authenticated user with Spring Cloud Gateway. Why it is important? API gateway is an entry point to your microservices system. Therefore, you should provide there a right level of security. Rate limiting can prevent your API against DoS attacks and limit web scraping.

You can easily configure rate limiting with Spring Cloud Gateway. For a basic introduction to this feature, you may refer to my article Rate Limiting in Spring Cloud Gateway with Redis. Similarly, today we will also use Redis as a backend for a rate limiter. Moreover, we will configure an HTTP basic authentication. Of course, you can provide some more advanced authentication mechanisms like an X509 certificate or OAuth2 login. If you think about it, read my article Spring Cloud Gateway OAuth2 with Keycloak.

Source Code

If you would like to try it by yourself, you may always take a look at my source code. In order to do that you need to clone my repository sample-spring-cloud-gateway. Then you should go to the src/test/java directory, and just follow my instructions in the next sections.

1. Dependencies

Let’s start with dependencies. Since we will create an integration test, we need some additional libraries. Firstly, we will use the Testcontainers library. It allows us to run Docker containers during the JUnit test. We will use it for running Redis and a mock server, which is responsible for mocking a downstream service. Of course, we need to include a starter with Spring Cloud Gateway and Spring Data Redis. To implement an HTTP basic authentication we also need to include Spring Security. Here’s a full list of required dependencies in Maven pom.xml.

<dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-test</artifactId>
   <scope>test</scope>
</dependency>
<dependency>
   <groupId>org.testcontainers</groupId>
   <artifactId>mockserver</artifactId>
   <scope>test</scope>
</dependency>
<dependency>
   <groupId>org.mock-server</groupId>
   <artifactId>mockserver-client-java</artifactId>
   <scope>test</scope>
</dependency>

2. Configure an HTTP Basic Authentication

In order to configure an HTTP basic authentication, we need to create the @Configuration bean annotated with @EnableWebFluxSecurity. That’s because Spring Cloud Gateway is built on top of Spring WebFlux and Netty. Also, we will create a set of test users with MapReactiveUserDetailsService.

@Configuration
@EnableWebFluxSecurity
public class SecurityConfig {

   @Bean
   public SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
      http.authorizeExchange(exchanges -> 
         exchanges.anyExchange().authenticated())
            .httpBasic();
      http.csrf().disable();
      return http.build();
   }

   @Bean
   public MapReactiveUserDetailsService users() {
      UserDetails user1 = User.builder()
            .username("user1")
            .password("{noop}1234")
            .roles("USER")
            .build();
      UserDetails user2 = User.builder()
            .username("user2")
            .password("{noop}1234")
            .roles("USER")
            .build();
      UserDetails user3 = User.builder()
            .username("user3")
            .password("{noop}1234")
            .roles("USER")
            .build();
      return new MapReactiveUserDetailsService(user1, user2, user3);
   }
}

3. Configure Spring Cloud Gateway Rate Limiter key

A request rate limiter feature needs to be enabled using the component called GatewayFilter. This filter takes an optional keyResolver parameter. The KeyResolver interface allows you to create pluggable strategies derive the key for limiting requests. In our case, it will be a user login. Once a user has been successfully authenticated, its login is stored in the Spring SecurityContext. In order to retrieve the context for a reactive application, we should use ReactiveSecurityContextHolder.

@Bean
KeyResolver authUserKeyResolver() {
   return exchange -> ReactiveSecurityContextHolder.getContext()
           .map(ctx -> ctx.getAuthentication()
              .getPrincipal().toString());
}

4. Test Scenario

In the test scenario, we are going to simulate incoming traffic. Every single request needs to have a Authorization header with the user credentials. A single user may send 4 requests per minute. After exceeding that limit Spring Cloud Gateway will return the HTTP code HTTP 429 - Too Many Requests. The traffic is addressed to the downstream service. Therefore, we are running a mock server using Testcontainers.

spring-gateway-rate-limiting-arch

5. Testing Spring Cloud Gateway secure rate limiter

Finally, we may proceed to the test implementation. I will use JUnit4 since I used it before for the other examples in the sample repository. We have three parameters used for rate limiter configuration: replenishRate, burstCapacity and requestedTokens. Since we also allow less than 1 request per second we need to set the right values for burstCapacity and requestedTokens. In short, the requestedTokens property sets how many tokens a request costs. On the other hand, the burstCapacity property is the maximum number of requests (or cost) that is allowed for a user.

During the test we randomly set the username between user1, user2 and user3. The test is repeated 20 times.

@SpringBootTest(webEnvironment = 
   SpringBootTest.WebEnvironment.DEFINED_PORT,
                properties = {"rateLimiter.secure=true"})
@RunWith(SpringRunner.class)
public class GatewaySecureRateLimiterTest {

   private static final Logger LOGGER = 
      LoggerFactory.getLogger(GatewaySecureRateLimiterTest.class);
   private Random random = new Random();

   @Rule
   public TestRule benchmarkRun = new BenchmarkRule();

   @ClassRule
   public static MockServerContainer mockServer = 
      new MockServerContainer();
   @ClassRule
   public static GenericContainer redis = 
      new GenericContainer("redis:5.0.6").withExposedPorts(6379);

   @Autowired
   TestRestTemplate template;

   @BeforeClass
   public static void init() {
      System.setProperty("spring.cloud.gateway.routes[0].id", "account-service");
      System.setProperty("spring.cloud.gateway.routes[0].uri", "http://" + mockServer.getHost() + ":" + mockServer.getServerPort());
      System.setProperty("spring.cloud.gateway.routes[0].predicates[0]", "Path=/account/**");
      System.setProperty("spring.cloud.gateway.routes[0].filters[0]", "RewritePath=/account/(?<path>.*), /$\\{path}");
      System.setProperty("spring.cloud.gateway.routes[0].filters[1].name", "RequestRateLimiter");
      System.setProperty("spring.cloud.gateway.routes[0].filters[1].args.redis-rate-limiter.replenishRate", "1");
      System.setProperty("spring.cloud.gateway.routes[0].filters[1].args.redis-rate-limiter.burstCapacity", "60");
      System.setProperty("spring.cloud.gateway.routes[0].filters[1].args.redis-rate-limiter.requestedTokens", "15");
      System.setProperty("spring.redis.host", redis.getHost());
      System.setProperty("spring.redis.port", "" + redis.getMappedPort(6379));
      new MockServerClient(mockServer.getContainerIpAddress(), mockServer.getServerPort())
            .when(HttpRequest.request()
                    .withPath("/1"))
            .respond(response()
                    .withBody("{\"id\":1,\"number\":\"1234567890\"}")
                    .withHeader("Content-Type", "application/json"));
   }

   @Test
   @BenchmarkOptions(warmupRounds = 0, concurrency = 1, benchmarkRounds = 20)
   public void testAccountService() {
      String username = "user" + (random.nextInt(3) + 1);
      HttpHeaders headers = createHttpHeaders(username,"1234");
      HttpEntity<String> entity = new HttpEntity<String>(headers);
      ResponseEntity<Account> r = template
         .exchange("/account/{id}", HttpMethod.GET, entity, Account.class, 1);
      LOGGER.info("Received({}): status->{}, payload->{}, remaining->{}",
            username, r.getStatusCodeValue(), r.getBody(), r.getHeaders().get("X-RateLimit-Remaining"));
    }

   private HttpHeaders createHttpHeaders(String user, String password) {
      String notEncoded = user + ":" + password;
      String encodedAuth = Base64.getEncoder().encodeToString(notEncoded.getBytes());
      HttpHeaders headers = new HttpHeaders();
      headers.setContentType(MediaType.APPLICATION_JSON);
      headers.add("Authorization", "Basic " + encodedAuth);
      return headers;
   }

}

Let’s run the test. Thanks to the junit-benchmarks library we may configure the number of rounds for the test. Each time I’m logging the response from the gateway that includes username, HTTP status, payload, and a header X-RateLimit-Remaining that shows a number of remaining tokens. Here’s the result.

The post Secure Rate Limiting with Spring Cloud Gateway appeared first on Piotr's TechBlog.

]]>
https://piotrminkowski.com/2021/05/21/secure-rate-limiting-with-spring-cloud-gateway/feed/ 7 9733
Spring Cloud Gateway OAuth2 with Keycloak https://piotrminkowski.com/2020/10/09/spring-cloud-gateway-oauth2-with-keycloak/ https://piotrminkowski.com/2020/10/09/spring-cloud-gateway-oauth2-with-keycloak/#comments Fri, 09 Oct 2020 09:47:51 +0000 https://piotrminkowski.com/?p=8950 Spring Cloud Gateway OAuth2 support is a key part of the microservices security process. Of course, the main reason for using an API gateway pattern is to hide services from the external client. However, when we set about hiding our services, we didn’t secure them. In this article, I’m going to show you how to […]

The post Spring Cloud Gateway OAuth2 with Keycloak appeared first on Piotr's TechBlog.

]]>
Spring Cloud Gateway OAuth2 support is a key part of the microservices security process. Of course, the main reason for using an API gateway pattern is to hide services from the external client. However, when we set about hiding our services, we didn’t secure them. In this article, I’m going to show you how to set up Spring Cloud Gateway OAuth2 with Spring Security and Keycloak.

Spring Cloud Gateway is a very useful product. You may take an advantage of the many interesting features it provides. One of them is rate-limiting. You may read more about that in the article Rate Limiting in Spring Cloud Gateway with Redis. It is also worth learning about a circuit breaking and fault tolerance. You may find interesting pieces of information about it in the articles Circuit Breaking in Spring Cloud Gateway with Resilience4j and Timeouts and Retries in Spring Cloud Gateway.

Source code

If you would like to try it by yourself, you may always take a look at my source code. In order to do that you need to clone my repository sample-spring-security-microservices. Then you should go to the gateway directory, and just follow my instructions 🙂 If you are interested in more details about Spring Security you should read its documentation.

Enable OAuth2 in Spring Cloud Gateway

To enable OAuth2 support for the Spring Cloud Gateway application we need to add some dependencies. Of course, the spring-cloud-starter-gateway dependency is required to enable the gateway feature. We also need to include spring-boot-starter-oauth2-client enabling Spring Security’s client support for OAuth 2.0 Authorization Framework and OpenID Connect Core 1.0. Finally, we have to add spring-cloud-starter-security to activate the TokenRelay filter.

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-security</artifactId>
</dependency>

In the next step, we need to provide the configuration settings for the OAuth2 client. Because we are integrating with Keycloak we should set the name of registrationId (spring.security.oauth2.client.provider.[registrationId]) to keycloak. Then we need to set the uris of token, authorization and userinfo endpoints. On the other hand, we can set a value for a single issuer endpoint. The last important property in that section is user-name-attribute. Keycloak is returning user login inside the preferred_username attribute.

We will define two different clients for authorization. The first of them spring-cloud-gateway contains the scope allowed by our test method, while the second spring-cloud-gateway-2 does not.

spring:
  security:
    oauth2:
      client:
        provider:
          keycloak:
            token-uri: http://localhost:8080/auth/realms/master/protocol/openid-connect/token
            authorization-uri: http://localhost:8080/auth/realms/master/protocol/openid-connect/auth
            userinfo-uri: http://localhost:8080/auth/realms/master/protocol/openid-connect/userinfo
            user-name-attribute: preferred_username
        registration:
          keycloak-with-test-scope:
            provider: keycloak
            client-id: spring-with-test-scope
            client-secret: c6480137-1526-4c3e-aed3-295aabcb7609
            authorization-grant-type: authorization_code
            redirect-uri: "{baseUrl}/login/oauth2/code/keycloak"
          keycloak-without-test-scope:
            provider: keycloak
            client-id: spring-without-test-scope
            client-secret: f6fc369d-49ce-4132-8282-5b5d413eba23
            authorization-grant-type: authorization_code
            redirect-uri: "{baseUrl}/login/oauth2/code/keycloak"

In the last step, we need to configure Spring Security. Since Spring Cloud Gateway is built on top of Spring WebFlux, we need to annotate the configuration bean with @EnableWebFluxSecurity. Inside the springSecurityFilterChain method we are going to enable authorization for all the exchanges. We will also set OAuth2 as a default login method and finally disable CSRF.

@Configuration
@EnableWebFluxSecurity
public class SecurityConfig {

   @Bean
   public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
      http.authorizeExchange(exchanges -> exchanges.anyExchange().authenticated())
         .oauth2Login(withDefaults());
      http.csrf().disable();
      return http.build();
   }

}

Run and configure Keycloak

We are running Keycloak on a Docker container. By default, Keycloak exposes API and a web console on port 8080. However, that port number must be different than the Spring Cloud Gateway application port, so we are overriding it with 8888. We also need to set a username and password to the admin console.

$ docker run -d --name keycloak -p 8888:8080 \
   -e KEYCLOAK_USER=spring \
   -e KEYCLOAK_PASSWORD=spring123 \
   jboss/keycloak

Then we need to create two clients with the same names as defined inside the gateway configuration. Both of them need to have confidential in the “Access Type” section, a valid redirection URI set. We may use a simple wildcard while setting the redirection address as shown below.

The client spring-with-test-scope will have the scope TEST assigned. In contrast, the second client spring-without-test-scope will not have the scope TEST assigned.

spring-cloud-gateway-oauth2-clientscope

Enable OAuth2 Resourse in Spring Cloud Gateway

Now, we may proceed to the implementation of the downstream application. In order to run it, you need to switch to the callme directory in the source code. First, we need to include some Maven dependencies. The spring-boot-starter-web starter provides web support for Spring Boot application. With spring-boot-starter-security we enable Spring Security for our microservice. The spring-security-oauth2-resource-server contains Spring Security’s support for OAuth 2.0 Resource Servers. It is also used to protect APIs via OAuth 2.0 Bearer Tokens. Finally, the spring-security-oauth2-jose module contains Spring Security’s support for the JOSE (Javascript Object Signing and Encryption) framework. The JOSE framework provides a method to securely transfer claims between parties. It supports JWT and JWS (JSON Web Signature).

<dependencies>
   <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
   </dependency>
   <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-security</artifactId>
   </dependency>
   <dependency>
      <groupId>org.springframework.security</groupId>
      <artifactId>spring-security-oauth2-resource-server</artifactId>
   </dependency>
   <dependency>
      <groupId>org.springframework.security</groupId>
      <artifactId>spring-security-oauth2-jose</artifactId>
   </dependency>
</dependencies>

In the next step, we need to configure a connection to the authorization server. A resource server will use the property spring.security.oauth2.resourceserver.jwt.issuer-uri to discover the authorization server public keys and then validate incoming JWT tokens.

spring:
  application:
    name: callme
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: http://localhost:8080/auth/realms/master

We should also provide a Spring Security configuration. First, we need to annotate the Configuration bean with @EnableWebSecurity. Then, we should enable annotation-based security for the controller methods. It allows simple role-based access with @PreAuthorize and @PostAuthorize. In order to enable a method security feature we need to use annotation @EnableGlobalMethodSecurity. Finally, we just need to configure Spring Security to authorize all the incoming requests and validate JWT tokens.

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

   protected void configure(HttpSecurity http) throws Exception {
      http.authorizeRequests(authorize -> authorize.anyRequest().authenticated())
            .oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
   }
}

Finally, let’s take a look at the implementation of the REST controller class. It a single ping method. That method may be accessed only by the client with the TEST scope. It returns a list of assigned scopes from the Authentication bean.

@RestController
@RequestMapping("/callme")
public class CallmeController {

   @PreAuthorize("hasAuthority('SCOPE_TEST')")
   @GetMapping("/ping")
   public String ping() {
      SecurityContext context = SecurityContextHolder.getContext();
      Authentication authentication = context.getAuthentication();
      return "Scopes: " + authentication.getAuthorities();
   }
}

Configure routing on Spring Cloud Gateway

The last step before proceeding to the tests is to configure routing on the Spring Cloud Gateway application. Since the downstream service (callme) is running on port 8040 we need to set the uri to http://127.0.0.1:8040. In order to forward the access token to the callme-service we have to enable a global filter TokenRelay. Just to be sure that everything works as expected, we will remove the Cookie with the session id. The session id is generated on the gateway after performing OAuth2Login.

spring:
  application:
    name: gateway
  cloud:
    gateway:
      default-filters:
        - TokenRelay
      routes:
        - id: callme-service
          uri: http://127.0.0.1:8040
          predicates:
            - Path=/callme/**
          filters:
            - RemoveRequestHeader=Cookie

Finally, let’s take a look at the gateway main class. I added there two useful endpoints. First of them GET / is returning the HTTP session id. The second of them GET /token will return the current JWT access token. After the successful Spring Cloud Gateway OAuth2 login, you will see the result from the index method.

@SpringBootApplication
@RestController
public class GatewayApplication {

   private static final Logger LOGGER = LoggerFactory.getLogger(GatewayApplication.class);

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

   @GetMapping(value = "/token")
   public Mono<String> getHome(@RegisteredOAuth2AuthorizedClient OAuth2AuthorizedClient authorizedClient) {
      return Mono.just(authorizedClient.getAccessToken().getTokenValue());
   }

   @GetMapping("/")
   public Mono<String> index(WebSession session) {
      return Mono.just(session.getId());
   }

}

Spring Cloud Gateway OAuth2 testing scenario

First, let’s take a look at the picture that illustrates our use case. We are calling POST /login endpoint on the gateway (1). After receiving the login request Spring Cloud Gateway try to obtain the access token from the authorization server (2). Then Keycloak is returning the JWT access token. As a result, Spring Cloud Gateway is calling the userinfo endpoint (3). After receiving the response it is creating a web session and Authentication bean. Finally, the gateway application is returning a session id to the external client (4). The external client is using a cookie with session-id to authorize requests. It calls GET ping from the callme application (5). The gateway application is forwarding the request to the downstream service (6). However, it removes the cookie and replaces it with a JWT access token. The callme application verifies an incoming token (7). Finally, it returns 200 OK response if the client is allowed to call endpoint (8). Otherwise, it returns 403 Forbidded.

spring-cloud-gateway-oauth2-login

We may start testing in the web browser. First, let’s call the login endpoint. We have to available clients keycloak-with-test-scope and keycloak-without-test-scope. We will use the client keycloak-with-test-scope.

spring-cloud-gateway-oauth2-login

Then, the gateway redirects us to the Keycloak login page. We can use the credentials provided during the creation of the Keycloak container.

After a successful login, the gateway will perform the OAuth2 authorization procedure. Finally, it redirects us to the main page. The main page is just a method index inside the controller. It is returning the current session id.

We can also use another endpoint implemented on the gateway – GET /token. It is returning the current JWT access token.

$ curl http://localhost:8080/token -H "Cookie: SESSION=9bf852f1-6e00-42f8-a9a2-3cbdced33993"
eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI0RWpwdkVtQ1ZDZ1VDUm41Y2NJeXRiank0RnR0RXpBRXVrMURoZDRTT0RFIn0.eyJleHAiOjE2MDIyMzM5MTksImlhdCI6MTYwMjIz
MzAxOSwiYXV0aF90aW1lIjoxNjAyMjMzMDE5LCJqdGkiOiIyYWQzYjczNy1mZTdhLTQ3NGUtODhhYy01MGZjYzEzOTlhYTQiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvYXV0aC9yZWFsbXMv
bWFzdGVyIiwiYXVkIjpbIm1hc3Rlci1yZWFsbSIsImFjY291bnQiXSwic3ViIjoiOWVhMDAyYmQtOTQ4Ni00Njk0LWFhYzUtN2IyY2QwNzc2MTZiIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoic3ByaW5n
LWNsb3VkLWdhdGV3YXkiLCJzZXNzaW9uX3N0YXRlIjoiMDRhNzQ4YzUtOTA1My00ZmZmLWJjYzctNWY5MThjMzYwZGE4IiwiYWNyIjoiMSIsInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJjcmVhdGUt
cmVhbG0iLCJST0xFX1RFTExFUiIsIm9mZmxpbmVfYWNjZXNzIiwiYWRtaW4iLCJURUxMRVIiLCJ1bWFfYXV0aG9yaXphdGlvbiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7Im1hc3Rlci1yZWFsbSI6eyJy
b2xlcyI6WyJ2aWV3LWlkZW50aXR5LXByb3ZpZGVycyIsInZpZXctcmVhbG0iLCJtYW5hZ2UtaWRlbnRpdHktcHJvdmlkZXJzIiwiaW1wZXJzb25hdGlvbiIsImNyZWF0ZS1jbGllbnQiLCJtYW5hZ2Ut
dXNlcnMiLCJxdWVyeS1yZWFsbXMiLCJ2aWV3LWF1dGhvcml6YXRpb24iLCJxdWVyeS1jbGllbnRzIiwicXVlcnktdXNlcnMiLCJtYW5hZ2UtZXZlbnRzIiwibWFuYWdlLXJlYWxtIiwidmlldy1ldmVu
dHMiLCJ2aWV3LXVzZXJzIiwidmlldy1jbGllbnRzIiwibWFuYWdlLWF1dGhvcml6YXRpb24iLCJtYW5hZ2UtY2xpZW50cyIsInF1ZXJ5LWdyb3VwcyJdfSwiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5h
Z2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJwcm9maWxlIGVtYWlsIFRFU1QiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsInByZWZlcnJl
ZF91c2VybmFtZSI6InBpb21pbiJ9.X8XfIHiAiR1YMjiJza75aC294qLwi83RrUU2phorM7FP4phq3h-lx80Zu5xqTrMqMC1-RbHBnX-oUTbs4ViS3DziZlDvoRajdkrh6UTiK5oWgoRW-4qsH5L4X1W
bRfoBZgyHFRSnhaCO4CLgjCyEgeLUR5A-JWY-OMYQIOAxxHB2GwE3MNFfLWeqpmS1AWU8fL0giFFXFDfa1_XZEKgnqe1S75Ps_z8B1sfNfvNpz8daJ8omzXrt6I6TSa0FE3iiZ7Qx18mtkbx-iPuFqDD
RT6DGU-Hing9LnGuOt3Yas-WYdN7PKBigvIZv0LyvRFcilRJQBjOdVfEddL3OQ0rmEg

Just to check, you can decode a JWT token on the https://jwt.io site.

spring-cloud-gateway-jwt-decoded

Finally, let’s call the endpoint exposed by the callme application. We are setting session Cookie in the request header. The endpoint is returning a list of scopes assigned to the current user. Only user with scope TEST is allowed to call the method.

$ curl http://localhost:8080/callme/ping -H "Cookie: SESSION=9bf852f1-6e00-42f8-a9a2-3cbdced33993"
Scopes: [SCOPE_profile, SCOPE_email, SCOPE_TEST]

Conclusion

In this article we were discussing important aspects related to microservices security. I showed you how to enable Spring Cloud Gateway OAuth2 support and integrate it with Keycloak. We were implementing such mechanisms like OAuth2 login, token relay, and OAuth2 resource server. The token relay mechanisms will be completely migrated from Spring Cloud Security to Spring Cloud Gateway. Enjoy 🙂

The post Spring Cloud Gateway OAuth2 with Keycloak appeared first on Piotr's TechBlog.

]]>
https://piotrminkowski.com/2020/10/09/spring-cloud-gateway-oauth2-with-keycloak/feed/ 58 8950
Part 2: Microservices security with OAuth2 https://piotrminkowski.com/2017/12/01/microservices-security-with-spring-cloud-oauth2/ https://piotrminkowski.com/2017/12/01/microservices-security-with-spring-cloud-oauth2/#comments Fri, 01 Dec 2017 16:29:21 +0000 https://piotrminkowski.wordpress.com/?p=6295 I have been writing about security with OAuth2 and Spring Cloud in some articles before. This article is the continuation of samples previously described in the following posts: Microservices security with Oauth2 (https://piotrminkowski.com/2017/02/22/microservices-security-with-oauth2/) Advanced Microservices Security with OAuth2 (https://piotrminkowski.com/2017/03/30/advanced-microservices-security-with-oauth2/) Today I’m going to show you a more advanced sample than before, where all authentication and […]

The post Part 2: Microservices security with OAuth2 appeared first on Piotr's TechBlog.

]]>
I have been writing about security with OAuth2 and Spring Cloud in some articles before. This article is the continuation of samples previously described in the following posts:

Today I’m going to show you a more advanced sample than before, where all authentication and OAuth2 data is stored on the database. We also find out how to secure microservices, especially considering an inter-communication between them with a Feign client. I hope this article will provide guidance and help you with designing and implementing secure solutions with OAuth2 and Spring Cloud. Let’s begin.

There are four services running inside our sample system, which is visualized in the figure below. There is nothing unusual here. We have a discovery server where our sample microservices account-service and customer-service are registered. Those microservices are both protected with OAuth2 authorization. Authorization is managed by auth-server. It stores not only OAuth2 tokens, but also users authentication data. The whole process is implemented using Spring Security OAuth2 and Spring Cloud libraries.

spring-cloud-oauth2-1

1. Start database

All the authentication credentials and tokens are stored in the MySQL database. So, the first step is to start MySQL. The most comfortable way to achieve it is through a Docker container. The command visible below in addition to the starting database also creates the schema and user oauth2.

$ docker run -d --name mysql -e MYSQL_DATABASE=oauth2 -e MYSQL_USER=oauth2 -e MYSQL_PASSWORD=oauth2 -e MYSQL_ALLOW_EMPTY_PASSWORD=yes -p 33306:3306 mysql

2. Configure data source in application

MySQL is now available on port host 192.168.99.100 if you run Docker on Windows and port 33306. Datasource properties should be set in application.yml of auth-server. Spring Boot is also able to run some SQL scripts on selected datasource after an application startup. It’s good news for us, because we have to create some tables on the schema dedicated for OAuth2 process.


spring:
  application:
    name: auth-server
  datasource:
    url: jdbc:mysql://192.168.99.100:33306/oauth2?useSSL=false
    username: oauth2
    password: oauth2
    driver-class-name: com.mysql.jdbc.Driver
    schema: classpath:/script/schema.sql
    data: classpath:/script/data.sql

3. Create schema in MySQL

Despite appearances, it is not so simple to find the SQL script with tables that needs to be created when using Spring Security for OAuth2. Here’s that script, which is available under /src/main/resources/script/schema.sql in the auth-server module. We have to create six tables:

  • oauth_client_details
  • oauth_client_token
  • oauth_access_token
  • oauth_refresh_token
  • oauth_code
  • oauth_approvals
drop table if exists user_authority;
drop table if exists authority;
drop table if exists `user`;
CREATE TABLE `user` (
  username VARCHAR(50) NOT NULL PRIMARY KEY,
  email VARCHAR(50),
  password VARCHAR(500),
  activated BOOLEAN DEFAULT FALSE,
  activationkey VARCHAR(50) DEFAULT NULL,
  resetpasswordkey VARCHAR(50) DEFAULT NULL
);

drop table if exists authority;
CREATE TABLE authority (
  name VARCHAR(50) NOT NULL PRIMARY KEY
);

CREATE TABLE user_authority (
  username VARCHAR(50) NOT NULL,
  authority VARCHAR(50) NOT NULL,
  FOREIGN KEY (username) REFERENCES user (username),
  FOREIGN KEY (authority) REFERENCES authority (name),
  UNIQUE INDEX user_authority_idx_1 (username, authority)
);

drop table if exists oauth_client_details;
create table oauth_client_details (
  client_id VARCHAR(255) PRIMARY KEY,
  resource_ids VARCHAR(255),
  client_secret VARCHAR(255),
  scope VARCHAR(255),
  authorized_grant_types VARCHAR(255),
  web_server_redirect_uri VARCHAR(255),
  authorities VARCHAR(255),
  access_token_validity INTEGER,
  refresh_token_validity INTEGER,
  additional_information VARCHAR(4096),
  autoapprove VARCHAR(255)
);
 
drop table if exists oauth_client_token;
create table oauth_client_token (
  token_id VARCHAR(255),
  token LONG VARBINARY,
  authentication_id VARCHAR(255) PRIMARY KEY,
  user_name VARCHAR(255),
  client_id VARCHAR(255)
);

drop table if exists oauth_access_token;
CREATE TABLE oauth_access_token (
  token_id VARCHAR(256) DEFAULT NULL,
  token BLOB,
  authentication_id VARCHAR(256) DEFAULT NULL,
  user_name VARCHAR(256) DEFAULT NULL,
  client_id VARCHAR(256) DEFAULT NULL,
  authentication BLOB,
  refresh_token VARCHAR(256) DEFAULT NULL
);

drop table if exists oauth_refresh_token;
CREATE TABLE oauth_refresh_token (
  token_id VARCHAR(256) DEFAULT NULL,
  token BLOB,
  authentication BLOB
);

drop table if exists oauth_code;
create table oauth_code (
  code VARCHAR(255), authentication LONG VARBINARY
);
 
drop table if exists oauth_approvals;
create table oauth_approvals (
  userId VARCHAR(255),
  clientId VARCHAR(255),
  scope VARCHAR(255),
  status VARCHAR(10),
  expiresAt DATETIME,
  lastModifiedAt DATETIME
);

4. Add some test data to database

There is also the second SQL script /src/main/resources/script/data.sql with some insert commands for the test purpose. The most important thing is to add some client id/client secret pairs.

INSERT INTO `oauth_client_details` (`client_id`, `client_secret`, `scope`, `authorized_grant_types`, `access_token_validity`, `additional_information`) VALUES ('account-service', 'secret', 'read', 'authorization_code,password,refresh_token,implicit', '900', '{}');
INSERT INTO `oauth_client_details` (`client_id`, `client_secret`, `scope`, `authorized_grant_types`, `access_token_validity`, `additional_information`) VALUES ('customer-service', 'secret', 'read', 'authorization_code,password,refresh_token,implicit', '900', '{}');
INSERT INTO `oauth_client_details` (`client_id`, `client_secret`, `scope`, `authorized_grant_types`, `access_token_validity`, `additional_information`) VALUES ('customer-service-write', 'secret', 'write', 'authorization_code,password,refresh_token,implicit', '900', '{}');

5. Building OAuth2 Authorization Server

Now, the most important thing in this article – authorization server configuration. The configuration class should be annotated with @EnableAuthorizationServer. Then we need to overwrite some methods from extended AuthorizationServerConfigurerAdapter class. The first important thing here is to set the default token storage to a database by providing bean JdbcTokenStore with default data source as a parameter. Although all tokens are now stored in a database we still want to generate them in JWT format. That’s why the second bean JwtAccessTokenConverter has to be provided in that class. By overriding different configure methods inherited from the base class we can set a default storage for OAuth2 client details and require the authorization server to always verify the API key submitted in HTTP headers.

@Configuration
@EnableAuthorizationServer
public class OAuth2Config extends AuthorizationServerConfigurerAdapter {

   @Autowired
   private DataSource dataSource;
   @Autowired
   private AuthenticationManager authenticationManager;

   @Override
   public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
      endpoints.authenticationManager(this.authenticationManager).tokenStore(tokenStore())
.accessTokenConverter(accessTokenConverter());
   }

   @Override
   public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
      oauthServer.checkTokenAccess("permitAll()");
   }

   @Bean
   public JwtAccessTokenConverter accessTokenConverter() {
      return new JwtAccessTokenConverter();
   }

   @Override
   public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
      clients.jdbc(dataSource);
   }

   @Bean
   public JdbcTokenStore tokenStore() {
      return new JdbcTokenStore(dataSource);
   }

}

The main OAuth2 grant type, which is used in the current sample is Resource owner credentials grant. In that type of grant client application sends user login and password to authenticate against OAuth2 server. A POST request sent by the client contains the following parameters:

  • grant_type – with the value ‘password’
  • client_id – with the client’s ID
  • client_secret – with the client’s secret
  • scope – with a space-delimited list of requested scope permissions
  • username – with the user’s username
  • password – with the user’s password

The authorization server will respond with a JSON object containing the following parameters:

  • token_type – with the value ‘Bearer’
  • expires_in – with an integer representing the TTL of the access token
  • access_token – the access token itself
  • refresh_token – a refresh token that can be used to acquire a new access token when the original expires

Spring application provides a custom authentication mechanism by implementing UserDetailsService interface and overriding its method loadUserByUsername. In our sample application user credentials and authorities are also stored in the database, so we inject UserRepository bean to the custom UserDatailsService class.

@Component("userDetailsService")
public class UserDetailsServiceImpl implements UserDetailsService {

    private final Logger log = LoggerFactory.getLogger(UserDetailsServiceImpl.class);

    @Autowired
    private UserRepository userRepository;

    @Override
    @Transactional
    public UserDetails loadUserByUsername(final String login) {

        log.debug("Authenticating {}", login);
        String lowercaseLogin = login.toLowerCase();

        User userFromDatabase;
        if(lowercaseLogin.contains("@")) {
            userFromDatabase = userRepository.findByEmail(lowercaseLogin);
        } else {
            userFromDatabase = userRepository.findByUsernameCaseInsensitive(lowercaseLogin);
        }

        if (userFromDatabase == null) {
            throw new UsernameNotFoundException("User " + lowercaseLogin + " was not found in the database");
        } else if (!userFromDatabase.isActivated()) {
            throw new UserNotActivatedException("User " + lowercaseLogin + " is not activated");
        }

        Collection<GrantedAuthority> grantedAuthorities = new ArrayList<>();
        for (Authority authority : userFromDatabase.getAuthorities()) {
            GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(authority.getName());
            grantedAuthorities.add(grantedAuthority);
        }

        return new org.springframework.security.core.userdetails.User(userFromDatabase.getUsername(), userFromDatabase.getPassword(), grantedAuthorities);
    }
    
}

That’s practically all what should be written about the auth-service module. Let’s move on to the client microservices.

6. Building microservices with Spring Cloud

The REST API is very simple. It does nothing more than returning some data. However, there is one interesting thing in that implementation. That is preauthorization based on OAuth token scope, which is annotated on the API methods with @PreAuthorize("#oauth2.hasScope('read')").

@RestController
public class AccountController {

   @GetMapping("/{id}")
   @PreAuthorize("#oauth2.hasScope('read')")
   public Account findAccount(@PathVariable("id") Integer id) {
      return new Account(id, 1, "123456789", 1234);
   }

   @GetMapping("/")
   @PreAuthorize("#oauth2.hasScope('read')")
   public List<Account> findAccounts() {
      return Arrays.asList(new Account(1, 1, "123456789", 1234), 
	        new Account(2, 1, "123456780", 2500),
            new Account(3, 1, "123456781", 10000));
   }

}

Preauthorization is disabled by default. To enable it for API methods we should use @EnableGlobalMethodSecurity annotation. We should also declare that such a preauthorization would be based on OAuth2 token scope.

@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class OAuth2ResourceServerConfig extends GlobalMethodSecurityConfiguration {

   @Override
   protected MethodSecurityExpressionHandler createExpressionHandler() {
      return new OAuth2MethodSecurityExpressionHandler();
   }

}

7. Spring Cloud Feign client with OAuth2

The API method findAccounts implemented in AccountController is invoked by customer-service through a Feign client.


@FeignClient(name = "account-service", configuration = AccountClientConfiguration.class)
public interface AccountClient {

   @GetMapping("/")
   List<Account> findAccounts();

}

If you call account service endpoint via Feign client you get the following exception.

feign.FeignException: status 401 reading AccountClient#findAccounts(); content:{"error":"unauthorized","error_description":"Full authentication is required to access this resource"}

Why? Of course, account-service is protected with OAuth2 token authorization, but the Feign client does not send an authorization token in the request header. That approach may be customized by defining a custom configuration class for a Feign client. It allows you to declare a request interceptor. In that case we can use an implementation for OAuth2 provided by OAuth2FeignRequestInterceptor from Spring Cloud OAuth2 library. We prefer password

public class AccountClientConfiguration {

   @Value("${security.oauth2.client.access-token-uri}")
   private String accessTokenUri;
   @Value("${security.oauth2.client.client-id}")
   private String clientId;
   @Value("${security.oauth2.client.client-secret}")
   private String clientSecret;
   @Value("${security.oauth2.client.scope}")
   private String scope;

   @Bean
   RequestInterceptor oauth2FeignRequestInterceptor() {
      return new OAuth2FeignRequestInterceptor(new DefaultOAuth2ClientContext(), resource());
   }

   @Bean
   Logger.Level feignLoggerLevel() {
      return Logger.Level.FULL;
   }

   private OAuth2ProtectedResourceDetails resource() {
      ResourceOwnerPasswordResourceDetails resourceDetails = new ResourceOwnerPasswordResourceDetails();
      resourceDetails.setUsername("piomin");
      resourceDetails.setPassword("piot123");
      resourceDetails.setAccessTokenUri(accessTokenUri);
      resourceDetails.setClientId(clientId);
      resourceDetails.setClientSecret(clientSecret);
      resourceDetails.setGrantType("password");
      resourceDetails.setScope(Arrays.asList(scope));
      return resourceDetails;
   }

}

8. Testing Spring Cloud microservices

Finally, we may perform some tests. Let’s build a sample project using mvn clean install command. If you run all the services with the default settings they would be available under addresses:

  • Config Serverhttp://localhost:9999/
  • Discovery Serverhttp://localhost:8761/
  • Account Servicehttp://localhost:8082/
  • Customer Servicehttp://localhost:8083/

The test method is visible below. We use OAuth2RestTemplate with ResourceOwnerPasswordResourceDetails to perform resource owner credentials grant operation and call GET /{id} API method from customer-service with OAuth2 token send in the request header.

@Test
public void testClient() {
   ResourceOwnerPasswordResourceDetails resourceDetails = new ResourceOwnerPasswordResourceDetails();
   resourceDetails.setUsername("piomin");
   resourceDetails.setPassword("piot123");
   resourceDetails.setAccessTokenUri("http://localhost:9999/oauth/token");
   resourceDetails.setClientId("customer-service");
   resourceDetails.setClientSecret("secret");
   resourceDetails.setGrantType("password");
   resourceDetails.setScope(Arrays.asList("read"));
   DefaultOAuth2ClientContext clientContext = new DefaultOAuth2ClientContext();
   OAuth2RestTemplate restTemplate = new OAuth2RestTemplate(resourceDetails, clientContext);
   restTemplate.setMessageConverters(Arrays.asList(new MappingJackson2HttpMessageConverter()));
   final Customer customer = restTemplate.getForObject("http://localhost:8083/{id}", Customer.class, 1);
   System.out.println(customer);
}

The post Part 2: Microservices security with OAuth2 appeared first on Piotr's TechBlog.

]]>
https://piotrminkowski.com/2017/12/01/microservices-security-with-spring-cloud-oauth2/feed/ 9 6295
Advanced Microservices Security with OAuth2 https://piotrminkowski.com/2017/03/30/advanced-microservices-security-with-oauth2/ https://piotrminkowski.com/2017/03/30/advanced-microservices-security-with-oauth2/#respond Thu, 30 Mar 2017 09:09:26 +0000 https://piotrminkowski.wordpress.com/?p=1997 In one of my previous posts, I described the basic sample illustrating microservices security with Spring Security and OAuth2. You could read there how to create and use authorization and resource server, basic authentication, and bearer token with Spring Boot. Now, I would like to introduce a more advanced sample with SSO OAuth2 behind Zuul […]

The post Advanced Microservices Security with OAuth2 appeared first on Piotr's TechBlog.

]]>
In one of my previous posts, I described the basic sample illustrating microservices security with Spring Security and OAuth2. You could read there how to create and use authorization and resource server, basic authentication, and bearer token with Spring Boot. Now, I would like to introduce a more advanced sample with SSO OAuth2 behind Zuul gateway. The architecture of the latest sample is rather similar to the previous sample like you can see in the picture below. The difference is in implementation details.

oauth2

Requests to the microservices and an authorization server are proxied by the gateway. The first request is redirected to the login page. We need to authenticate. User authentication data is stored in the MySQL database. After login, there is also stored user HTTP session data using the Spring Session library. Then you should perform the next steps to obtain the OAuth2 authorization token by calling authorization server endpoints via a gateway. Finally, you can call a concrete microservice providing OAuth2 token as a bearer in the Authorization HTTP request header.

If you are interested in technical details of the presented solution you can read my article on DZone. There is also available sample application source code on GitHub.

The post Advanced Microservices Security with OAuth2 appeared first on Piotr's TechBlog.

]]>
https://piotrminkowski.com/2017/03/30/advanced-microservices-security-with-oauth2/feed/ 0 1997
Microservices security with Oauth2 https://piotrminkowski.com/2017/02/22/microservices-security-with-oauth2/ https://piotrminkowski.com/2017/02/22/microservices-security-with-oauth2/#respond Wed, 22 Feb 2017 08:53:54 +0000 https://piotrminkowski.wordpress.com/?p=987 Preface One of the most important aspects to consider when exposing a public access API consisting of many microservices is security. Spring has some interesting features and frameworks which makes configuration of our microservices security easier. In this article I’m going to show you how to use Spring Cloud and OAuth 2 to provide token […]

The post Microservices security with Oauth2 appeared first on Piotr's TechBlog.

]]>
Preface

One of the most important aspects to consider when exposing a public access API consisting of many microservices is security. Spring has some interesting features and frameworks which makes configuration of our microservices security easier. In this article I’m going to show you how to use Spring Cloud and OAuth 2 to provide token access security behind API gateway.

Theory

OAuth2 standard is currently used by all the major websites that allow you to access their resources through the shared API. It is an open authorization standard allowing users to share their private resources stored in one page to another page without having to go into the service of their credentials. These are basic terms related to OAuth 2.

  • Resource Owner – dispose of access to the resource
  • Resource Server – server that stores the owner’s resources that can be shared using special token
  • Authorization Server – manages the allocation of keys, tokens and other temporary resource access codes. It also has to ensure that access is granted to the relevant person
  • Access Token – the key that allows access to a resource
  • Authorization Grant – grants permission for access. There are different ways to confirm access: authorization code, implicit, resource owner password credentials, and client credentials

You can read more about this standard here and in this digitalocean article. The flow of this protocol has three main steps. In the beginning we authorization request is sent to the Resource Owner. After response from Resource Owner we send authorization grant request to Authorization Server and receive access token. Finally, we send this access token to Resource Server and if it is valid the API serves the resource to the application.

Our solution

The picture below shows the architecture of our sample. We have API Gateway (Zuul) which proxies our requests to authorization server and two instances of account microservice. Authorization server is some kind of infrastructure service which provides OAuth 2 security mechanisms. We also have discovery service (Eureka) where all of our microservices are registered.

sec-micro

Gateway

For our sample we won’t provide any security on API gateway. It just has to proxy requests from clients to authorization server and account microservices. In the Zuul’s gateway configuration visible below we set sensitiveHeaders property on empty value to enable Authorization HTTP header forward. By default Zuul cut that header while forwarding our request to the target API which is incorrect because of the basic authorization demanded by our services behind gateway.

zuul:
  routes:
    uaa:
      path: /uaa/**
      sensitiveHeaders:
      serviceId: auth-server
    account:
      path: /account/**
      sensitiveHeaders:
      serviceId: account-service

Main class inside gateway source code is very simple. It only has to enable Zuul proxy feature and discovery client for collecting services from the Eureka registry.

@SpringBootApplication
@EnableZuulProxy
@EnableDiscoveryClient
public class GatewayServer {

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

}

Authorization Server

Our authorization server is as simple as possible. It is based on default Spring security configuration. Client authorization details are stored in an in-memory repository. Of course in the production mode you would like to use other implementations instead of in-memory repositories like JDBC datasource and token store. You can read more about Spring authorization mechanisms in Spring Security Reference and Spring Boot Security. Here’s a fragment of configuration from application.yml. We provided user basic authentication data and basic security credentials for the /token endpoint: client-id and client-secret. The user credentials are the normal Spring Security user details.

security:
  user:
    name: root
    password: password
  oauth2:
    client:
      client-id: acme
      client-secret: secret

Here’s the main class of our authentication server with @EnableAuthorizationServer. We also exposed one REST endpoint with user authentication details for account service and enabled Eureka registration and discovery for clients.

@SpringBootApplication
@EnableAuthorizationServer
@EnableDiscoveryClient
@EnableResourceServer
@RestController
public class AuthServer {

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

   @RequestMapping("/user")
   public Principal user(Principal user) {
       return user;
   }

}

Application – account microservice

Our sample microservice has only one endpoint for @GET request which always returns the same account. In the main class resource server and Eureka discovery are enabled. Service configuration is trivial. Sample application source code is available on GitHub.

@SpringBootApplication
@EnableDiscoveryClient
@EnableResourceServer
public class AccountService {

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

}
 

Here’s security configuration for account-service.

security:
  user:
    name: root
    password: password
  oauth2:
    resource:
      loadBalanced: true
      userInfoUri: http://localhost:9999/user

Testing

We only need a web browser and REST client (for example Chrome Advanced REST client) to test our solution. Let’s start from sending authorization requests to a resource owner. We can call OAuth 2 authorize endpoint via Zuul gateway in the web browser.

http://localhost:8765/uaa/oauth/authorize?response_type=token&client_id=acme&redirect_uri=http://example.com&scope=openid&state=48532

After sending this request we should see the page below. Select Approve and click Authorize for requests and access token from the authorization server. If the application identity is authenticated and the authorization grant is valid an access token to the application should be returned in the HTTP response.

oauth2

And the final step is to call the account endpoint using an access token. We had to put it into the Authorization header as bearer token. The sample application logging level for security operation is set to TRACE so you can easily find out what happened if something goes wrong.

call

Conclusion

To be honest I’m not very familiar with security issues in applications. So one very important thing for me is the simplicity of security solution I decided to use. In Spring Security we have almost all needed mechanisms out of the box. It also provides components which can be easily extendable for more advanced requirements. You should treat this article as a brief introduction to more advanced solutions using Spring Cloud and Spring Security projects. For more advanced example read this article: Part 2: Microservices security with OAuth 2

The post Microservices security with Oauth2 appeared first on Piotr's TechBlog.

]]>
https://piotrminkowski.com/2017/02/22/microservices-security-with-oauth2/feed/ 0 987