security Archives - Piotr's TechBlog https://piotrminkowski.com/tag/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 security Archives - Piotr's TechBlog https://piotrminkowski.com/tag/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
SBOM with Spring Boot https://piotrminkowski.com/2024/09/05/sbom-with-spring-boot/ https://piotrminkowski.com/2024/09/05/sbom-with-spring-boot/#comments Thu, 05 Sep 2024 16:09:48 +0000 https://piotrminkowski.com/?p=15361 This article will teach you, how to leverage SBOM support in Spring Boot to implement security checks for your apps. A Software Bill of Materials (SBOM) lists all your app codebase’s open-source and third-party components. As a result, it allows us to perform vulnerability scanning, license checks, and risk analysis. Spring Boot 3.3 introduces built-in […]

The post SBOM with Spring Boot appeared first on Piotr's TechBlog.

]]>
This article will teach you, how to leverage SBOM support in Spring Boot to implement security checks for your apps. A Software Bill of Materials (SBOM) lists all your app codebase’s open-source and third-party components. As a result, it allows us to perform vulnerability scanning, license checks, and risk analysis. Spring Boot 3.3 introduces built-in support for generating SBOMs during app build and exposing them through the actuator endpoint. In this article, we will analyze the app based on the latest version of Spring Boot and another one using the outdated version of the libraries. You will see how to use the snyk CLI to verify the generated SBOM files.

It is the first article on my blog after a long holiday break. I hope you enjoy it. If you are interested in Spring Boot you can find several other posts about it on my blog. I can recommend my article about another fresh Spring Boot feature related to security, that describes SSL certs hot reload on Kubernetes.

Source Code

If you would like to try this exercise by yourself, you may always take a look at my source code. Today you will have to clone two sample Git repositories. The first one contains automatically updated source code of microservices based on the latest version of the Spring Boot framework. The second repository contains an archived version of microservices based on the earlier, unsupported version of Spring Boot. Once you clone both of these repositories, you just need to follow my instructions.

By the way, you can verify SBOMs generated for your Spring Boot apps in various ways. I decided to use snyk CLI for that. Alternatively, you can use the web version of the Snyk SBOM checker available here. In order to install the snyk CLI on your machine you need to follow its documentation. I used homebrew to install it on my macOS:

$ brew tap snyk/tap
$ brew install snyk
ShellSession

Enable SBOM Support in Spring Boot

By default, Spring Boot supports the CycloneDX format for generating SBOMs. In order to enable it, we need to include the cyclonedx-maven-plugin Maven plugin in your project root pom.xml.

<build>
  <plugins>
    <plugin>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-maven-plugin</artifactId>
    </plugin>
    <plugin>
      <groupId>org.cyclonedx</groupId>
      <artifactId>cyclonedx-maven-plugin</artifactId>
    </plugin>
  </plugins>
</build>
XML

There are several microservices defined in the same Git repository. They are all using the same root pom.xml. Each of them defines its list of dependencies. For this exercise, we need to have at least the Spring Boot Web and Actuator starters. However, let’s take a look at the whole list of dependencies for the employee-service (one of our sample microservices):

<dependencies>
  <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
  </dependency>
  <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-config</artifactId>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
  </dependency>
  <dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-tracing-bridge-otel</artifactId>
  </dependency>
  <dependency>
    <groupId>io.opentelemetry</groupId>
    <artifactId>opentelemetry-exporter-zipkin</artifactId>
  </dependency>
  <dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-micrometer</artifactId>
  </dependency>
  <dependency>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-starter-webmvc-api</artifactId>
    <version>2.6.0</version>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
  </dependency>
  <dependency>
    <groupId>org.instancio</groupId>
    <artifactId>instancio-junit</artifactId>
    <version>4.8.1</version>
    <scope>test</scope>
  </dependency>
</dependencies>
XML

After including the cyclonedx-maven-plugin plugin we need to execute the mvn package command in the repository root directory:

$ mvn clean package -DskipTests
ShellSession

The plugin will generate SBOM files for all the existing microservices and place them in the target/classes/META-INF/sbom directory for each Maven module.

spring-boot-sbom-maven

The generated SBOM file will always be placed inside the JAR file as well. Let’s take a look at the location of the SBOM file inside the employee-service uber JAR.

In order to expose the actuator SBOM endpoint we need to include the following configuration property. Since our configuration is stored by the Spring Cloud Config server, we need to put such a property in the YAML files inside the config-service/src/main/resources/config directory.

management:
  endpoints:
    web:
      exposure:
        include: health,sbom
ShellSession

Then, let’s start the config-service with the following Maven command:

$ cd config-service
$ mvn clean spring-boot:run
ShellSession

After that, we can start our sample microservice. It loads the configuration properties from the config-service. It listens on the dynamically generated port number. For me, it is 53498. In order to see the contents of the generated SBOM file, we need to call the GET /actuator/sbom/application path.

Generate and Verify SBOMs with the Snyk CLI

The exact structure of the SBOM file is not very important from our perspective. We need a tool that allows us to verify components and dependencies published inside that file. As I mentioned before, we can use the snyk CLI for that. We will examine the file generated in the repository root directory. Here’s the snyk command that allows us to print all the detected vulnerabilities in the SBOM file:

$ snyk sbom test \
   --file=target/classes/META-INF/sbom/application.cdx.json \
   --experimental
ShellSession

Here’s the report created as the output of the command executed above. As you see, there are two detected issues related to the included dependencies. Of course, I’m not including those dependencies directly in the Maven pom.xml. They were automatically included by the Spring Boot starters used by the microservices. By the way, I was even not aware that Spring Boot includes kotlin-stdlib even if I’m not using any Kotlin library directly in the app.

spring-boot-sbom-snyk

Although there are two issues detected in the report, it doesn’t look very bad. Now, let’s try to analyze something more outdated. I have already mentioned my old repository with microservices: sample-spring-microservices. It is already in the archived status and uses Spring Boot in the 1.5 version. If we don’t want to modify anything there, we can also use snyk CLI to generate SBOM instead of the Maven plugin. Since built-in support for SBOM comes with Spring Boot 3.3 there is no sense in including a plugin for the apps with the 1.5 version. Here’s the snyk command that generates SBOM for all the projects inside the repository and exports it to the application.cdx.json file:

$ snyk sbom --format=cyclonedx1.4+json --all-projects > application.cdx.json
ShellSession

Then, let’s examine the SBOM file using the same command as before:

$ snyk sbom test --file=application.cdx.json --experimental
ShellSession

Now, the results are much more pessimistic. There are 211 detected issues, including 6 critical.

Final Thoughts

SBOMs allow organizations to identify and address potential security risks more effectively. Spring Boot support for generating SBOM files simplifies the process when incorporating them into the organizational software development life cycle.

The post SBOM with Spring Boot appeared first on Piotr's TechBlog.

]]>
https://piotrminkowski.com/2024/09/05/sbom-with-spring-boot/feed/ 2 15361
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
Micronaut OAuth2 and security with Keycloak https://piotrminkowski.com/2020/09/21/micronaut-oauth2-and-security-with-keycloak/ https://piotrminkowski.com/2020/09/21/micronaut-oauth2-and-security-with-keycloak/#comments Mon, 21 Sep 2020 12:47:41 +0000 https://piotrminkowski.com/?p=8848 Micronaut OAuth2 module supports both the authorization code grant and the password credentials grant flows. In this article, you will learn how to integrate your Micronaut application with the OAuth2 authorization server like Keycloak. We will implement the password credentials grant scenario with Micronaut OAuth2. Before starting with Micronaut Security you should learn about the […]

The post Micronaut OAuth2 and security with Keycloak appeared first on Piotr's TechBlog.

]]>
Micronaut OAuth2 module supports both the authorization code grant and the password credentials grant flows. In this article, you will learn how to integrate your Micronaut application with the OAuth2 authorization server like Keycloak. We will implement the password credentials grant scenario with Micronaut OAuth2.
Before starting with Micronaut Security you should learn about the basics. Therefore, I suggest reading the article Micronaut Tutorial: Beans and scopes. After that, you may read about building REST-based applications in the article Micronaut Tutorial: Server application.

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-micronaut-security. Then go to the sample-micronaut-oauth2 directory, and just follow my instructions 🙂 If you are interested in more details about Micronaut Security you should read the documentation.

Introduction to OAuth2 with Micronaut

Micronaut supports authentication with OAuth 2.0 servers, including the OpenID standard. You can choose between available providers like Okta, Auth0, AWS Cognito, Keycloak, or Google. By default, Micronaut provides the login handler. You can access by calling the POST /login endpoint. In that case, the Micronaut application tries to obtain an access token from the OAuth2 provider. The only thing you need to implement by yourself is a bean responsible for mapping an access token to the user details. After that, the Micronaut application is returning a token to the caller. To clarify, you can take a look at the picture below.

micronaut-oauth2-login-architecture

Include Micronaut Security dependencies

In the first step, we need to include Micronaut modules for REST, security, and OAuth2. Since Keycloak is generating JTW tokens, we should also add the micronaut-security-jwt dependency. Of course, our application uses some other modules, but those five are required.

<dependency>
   <groupId>io.micronaut.security</groupId>
   <artifactId>micronaut-security</artifactId>
</dependency>
<dependency>
   <groupId>io.micronaut.security</groupId>
   <artifactId>micronaut-security-oauth2</artifactId>
</dependency>
<dependency>
   <groupId>io.micronaut.security</groupId>
   <artifactId>micronaut-security-jwt</artifactId>
</dependency>
<dependency>
   <groupId>io.micronaut</groupId>
   <artifactId>micronaut-http-server-netty</artifactId>
</dependency>
<dependency>
   <groupId>io.micronaut</groupId>
   <artifactId>micronaut-http-client</artifactId>
</dependency>

That’s not all that we need to configure in Maven pom.xml. In the next step, we have to enable annotation processing for the Micronaut Security module.

<plugin>
   <groupId>org.apache.maven.plugins</groupId>
   <artifactId>maven-compiler-plugin</artifactId>
   <version>3.8.1</version>
   <configuration>
      <source>${java.version}</source>
      <target>${java.version}</target>
      <compilerArgs>
         <arg>-parameters</arg>
      </compilerArgs>
      <annotationProcessorPaths>
         <path>
            <groupId>io.micronaut</groupId>
            <artifactId>micronaut-inject-java</artifactId>
            <version>${micronaut.version}</version>
         </path>
         <path>
            <groupId>io.micronaut.security</groupId>
            <artifactId>micronaut-security</artifactId>
         </path>
         <path>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.12</version>
         </path>
      </annotationProcessorPaths>
   </configuration>
   <executions>
      <execution>
         <id>test-compile</id>
         <goals>
            <goal>testCompile</goal>
         </goals>
         <configuration>
            <compilerArgs>
               <arg>-parameters</arg>
            </compilerArgs>
            <annotationProcessorPaths>
               <path>
                  <groupId>io.micronaut</groupId>
                  <artifactId>micronaut-inject-java</artifactId>
                  <version>${micronaut.version}</version>
               </path>
               <path>
                  <groupId>io.micronaut.security</groupId>
                  <artifactId>micronaut-security</artifactId>
               </path>
               <path>
                  <groupId>org.projectlombok</groupId>
                  <artifactId>lombok</artifactId>
                  <version>1.18.12</version>
               </path>
            </annotationProcessorPaths>
         </configuration>
      </execution>
   </executions>
</plugin>

Using Micronaut OAuth2 for securing endpoints

Let’s discuss a typical implementation of the REST controller with Micronaut. Micronaut Security provides a set of annotations for setting permissions. We may use JSR-250 annotations. These are @PermitAll, @DenyAll, or @RolesAllowed. In addition, Micronaut provides @Secured annotation. It also allows you to limit access to controllers and their methods. For example, we may use @Secured(SecurityRule.IS_ANONYMOUS) as a replacement for @PermitAll.

The controller class is very simple. I’m using two roles: admin and viever. The third endpoint is allowed for all users.

@Controller("/secure")
@Secured(SecurityRule.IS_AUTHENTICATED)
public class SampleController {

   @Get("/admin")
   @Secured({"admin"})
   public String admin() {
      return "You are admin!";
   }

   @Get("/view")
   @Secured({"viewer"})
   public String view() {
      return "You are viewer!";
   }

   @Get("/anonymous")
   @Secured(SecurityRule.IS_ANONYMOUS)
   public String anonymous() {
      return "You are anonymous!";
   }
}

Running 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 Micronaut 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=micronaut -e KEYCLOAK_PASSWORD=micronaut123 jboss/keycloak

Create client on Keycloak

First, we need to create a client with a given name. Let’s say this name is micronaut. The client credentials are used during the authorization process. It is important to choose confidential in the “Access Type” section and enable option “Direct Access Grants”.

micronaut-oauth2-client-id

Then we may switch to the “Credentials” tab, and copy the client secret.

Integration between Micronaut OAuth2 and Keycloak

In the next steps, we will use two HTTP endpoints exposed by Keycloak. First of them, token_endpoint allows you to generate new access tokens. The second endpoint introspection_endpoint is used to retrieve the active state of a token. In other words, you can use it to validate access or refresh token. The third endpoint jwks allows you to validate JWT signatures.

We need to provide several configuration properties. In the first step, we are setting the login handler implementation to idtoken. We will also enable the login controller with the micronaut.security.endpoints.login.enabled property. Of course, we need to provide the client id, client secret, and token endpoint address. The property grant-type enables the password credentials grant flow. All these configuration settings are required during the login action. After login Micronaut Security is returning a cookie with JWT access token. We will use that cookie in the next requests. Micronaut uses the Keycloak JWKS endpoint to validate each token.

micronaut:
  application:
    name: sample-micronaut-oauth2
  security:
    authentication: idtoken
    endpoints:
      login:
        enabled: true
    redirect:
      login-success: /secure/anonymous
    token:
      jwt:
        enabled: true
        signatures.jwks.keycloak:
          url: http://localhost:8888/auth/realms/master/protocol/openid-connect/certs
    oauth2.clients.keycloak:
      grant-type: password
      client-id: micronaut
      client-secret: 7dd4d516-e06d-4d81-b5e7-3a15debacebf
      authorization:
        url: http://localhost:8888/auth/realms/master/protocol/openid-connect/auth
      token:
        url: http://localhost:8888/auth/realms/master/protocol/openid-connect/token
        auth-method: client-secret-post

With Micronaut Security we need to provide an implementation of OauthUserDetailsMapper. It is responsible for transform from the TokenResponse into a UserDetails. Our implementation of OauthUserDetailsMapper is using the Keycloak introspect endpoint. It validates an access token and returns the information about user. We need the username and roles.

@Getter
@Setter
public class KeycloakUser {
   private String email;
   private String username;
   private List<String> roles;
}

I’m using the Micronaut low-level HTTP client for communication with Keycloak. It needs to send client credentials for authorization in the Authorization header. Keycloak is validating the input token. The KeycloakUserDetailsMapper returns UserDetails object, that contains username, list of roles, and token. The token should be set as the openIdToken attribute.

@Named("keycloak")
@Singleton
@Slf4j
public class KeycloakUserDetailsMapper implements OauthUserDetailsMapper {

   @Property(name = "micronaut.security.oauth2.clients.keycloak.client-id")
   private String clientId;
   @Property(name = "micronaut.security.oauth2.clients.keycloak.client-secret")
   private String clientSecret;

   @Client("http://localhost:8888")
   @Inject
   private RxHttpClient client;

   @Override
   public Publisher<UserDetails> createUserDetails(TokenResponse tokenResponse) {
      return Publishers.just(new UnsupportedOperationException());
   }

   @Override
   public Publisher<AuthenticationResponse> createAuthenticationResponse(
         TokenResponse tokenResponse, @Nullable State state) {
      Flowable<HttpResponse<KeycloakUser>> res = client
            .exchange(HttpRequest.POST("/auth/realms/master/protocol/openid-connect/token/introspect",
            "token=" + tokenResponse.getAccessToken())
            .contentType(MediaType.APPLICATION_FORM_URLENCODED)
            .basicAuth(clientId, clientSecret), KeycloakUser.class);
      return res.map(user -> {
         log.info("User: {}", user.body());
         Map<String, Object> attrs = new HashMap<>();
         attrs.put("openIdToken", tokenResponse.getAccessToken());
         return new UserDetails(user.body().getUsername(), user.body().getRoles(), attrs);
      });
   }

}

Creating users and roles on Keycloak

Our application uses two roles: viewer and admin. Therefore, we will create two test users on Keycloak. Each of them has a single role assigned. Here’s the full list of test users.

micronaut-oauth2-users

Of course, we also need to define roles. In the picture below, I highlighted the roles used by our application.

Before proceeding to the tests, we need to do one thing. We have to edit the client scope responsible for displaying a list of roles. To do that go to the section “Client Scopes”, and then find the roles scope. After editing it, you should switch to the “Mappers” tab. Finally, you need to find and edit the “realm roles” entry. I highlighted it in the picture below. In the next section, I’ll show you how Micronaut OAuth2 retrieves roles from the introspection endpoint.

keycloak-clientclaim

Testing Micronaut OAuth2 process

After starting the Micronaut application we can call the endpoint POST /login. It expects a request in a JSON format. We should send there the username and password. Our test user is test_viewer with the 123456 password. Micronaut application sends a redirect to the site configured with parameter micronaut.security.redirect.login-succcess. It also returns JWT access token in the Set-Cookie header.

curl -v http://localhost:8080/login -H "Content-Type: application/json" -d "{\"username\":\"test_viewer\",\"password\": \"123456\"}"
*   Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> POST /login HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.55.1
> Accept: */*
> Content-Type: application/json
> Content-Length: 47
>
* upload completely sent off: 47 out of 47 bytes
< HTTP/1.1 303 See Other
< Location: /secure/anonymous
< set-cookie: JWT=eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJBOUIweGhFckUtbk1nTmMxVUg5ZnU0ellNcFZncDRBc1dQNFgyVnk2ZnNjIn0.eyJleHAiOjE2MDA2OTE2ND
UsImlhdCI6MTYwMDY4OTg0NSwianRpIjoiNmQzMmJkMjMtMjIwOC00NDBjLTlmZTYtNGQ4NTBlOTdmMjQ1IiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4ODg4L2F1dGgvcmVhbG1zL21hc3RlciIsIm
F1ZCI6ImFjY291bnQiLCJzdWIiOiJmNDE4MjhmNi1kNTk3LTQxY2ItOTA4MS00NmMyZDdhNGQ3NmIiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJtaWNyb25hdXQiLCJzZXNzaW9uX3N0YXRlIjoiM2JjNz
c0YWMtZjk3OC00MzhhLTk3NDktMDY2ZTcwMmIyMzMzIiwiYWNyIjoiMSIsInJlc291cmNlX2FjY2VzcyI6eyJhY2NvdW50Ijp7InJvbGVzIjpbIm1hbmFnZS1hY2NvdW50IiwibWFuYWdlLWFjY291bn
QtbGlua3MiLCJ2aWV3LXByb2ZpbGUiXX19LCJzY29wZSI6ImVtYWlsIHByb2ZpbGUiLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwicm9sZXMiOlsidmlld2VyIiwib2ZmbGluZV9hY2Nlc3MiLCJ1bWFfYX
V0aG9yaXphdGlvbiJdLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJ0ZXN0X3ZpZXdlciIsImVtYWlsIjoidGVzdF92aWV3ZXJAZXhhbXBsZS5jb20ifQ.bb5uiGe8jp5eaEs3ql_k_A56xBKzBaSduBbG0_s
olj82BGQ3d8wJp0LMqPe86gj4RvOEPQD31CetGM5T2c6AluvPkBw_5Bh_5ZyD28Ueh-TvmY76yoBYF2r__zCJh8yKKN78xTx0Qp_qRM6M6T57Ke9lOE0O87CmlWR8tUSzTE4azSOksxyX_PRW2jtE8GV
Un8SlJMyjgA5iYOhmbTsINSiMTtMEWk3ofAoYJquk6vis_ZG4_vTRYsKD1GQ-7Kk0Y7d1_l1YLhfOajgxrKMQm-QIovNS0aThgvijto4ibjHBm3HRigQAi3fbOJo9Yj8F9uXs-tdaKe6JZGGV_G0eCA;
 Max-Age=1799; Expires=Mon, 21 Sep 2020 12:34:04 GMT; Path=/; HTTPOnly
< Date: Mon, 21 Sep 2020 12:04:05 GMT
< connection: keep-alive
< transfer-encoding: chunked
<
* Connection #0 to host localhost left intact

After receiving the login request Micronaut OAuth2 calls the token endpoint on Keycloak. Then, after receiving the token from Keycloak, it invokes the KeycloakUserDetailsMapper bean. The mapper calls another Keycloak endpoint – this time it is the introspect endpoint. You can verify the further steps by looking at the application logs.

Once, we received the response with the access token, we can set it in the Cookie header. The token is valid for 1800 seconds. Here’s the request to the GET secure/view endpoint.

curl -v http://localhost:8080/secure/view -H "Cookie: JWT=..."
*   Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> GET /secure/view HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.55.1
> Accept: */*
> Cookie: JWT=...
>
< HTTP/1.1 200 OK
< Date: Mon, 21 Sep 2020 12:25:14 GMT
< content-type: application/json
< content-length: 15
< connection: keep-alive
<
You are viewer!* 

We can also call the endpoint GET /secure/admin. Since, it is not allowed for the test_viewer user, you will receive the reposnse HTTP 403 Forbidden.

curl -v http://localhost:8080/secure/view -H "Cookie: JWT=..."
*   Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> GET /secure/view HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.55.1
> Accept: */*
> Cookie: JWT=...
>
< HTTP/1.1 403 Forbidden
< connection: keep-alive
< transfer-encoding: chunked
 

The post Micronaut OAuth2 and security with Keycloak appeared first on Piotr's TechBlog.

]]>
https://piotrminkowski.com/2020/09/21/micronaut-oauth2-and-security-with-keycloak/feed/ 2 8848
Quarkus OAuth2 and security with Keycloak https://piotrminkowski.com/2020/09/16/quarkus-oauth2-and-security-with-keycloak/ https://piotrminkowski.com/2020/09/16/quarkus-oauth2-and-security-with-keycloak/#respond Wed, 16 Sep 2020 07:27:40 +0000 https://piotrminkowski.com/?p=8811 Quarkus OAuth2 support is based on the WildFly Elytron Security project. In this article, you will learn how to integrate your Quarkus application with the OAuth2 authorization server like Keycloak. Before starting with Quarkus security it is worth to find out how to build microservices in Quick guide to microservices with Quarkus on OpenShift, and […]

The post Quarkus OAuth2 and security with Keycloak appeared first on Piotr's TechBlog.

]]>
Quarkus OAuth2 support is based on the WildFly Elytron Security project. In this article, you will learn how to integrate your Quarkus application with the OAuth2 authorization server like Keycloak.

Before starting with Quarkus security it is worth to find out how to build microservices in Quick guide to microservices with Quarkus on OpenShift, and how to easily deploy your application on Kubernetes in Guide to Quarkus on Kubernetes.

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-quarkus-applications. Then go to the employee-secure-service directory, and just follow my instructions 🙂 The good idea is to read the article Guide to Quarkus with Kotlin before you move on.

Using Quarkus OAuth2 for securing endpoints

In the first step, we need to include Quarkus modules for REST and OAuth2. Of course, our applications use some other modules, but those two are required.

<dependency>
   <groupId>io.quarkus</groupId>
   <artifactId>quarkus-elytron-security-oauth2</artifactId>
</dependency>
<dependency>
   <groupId>io.quarkus</groupId>
   <artifactId>quarkus-resteasy-jackson</artifactId>
</dependency>

Let’s discuss a typical implementation of the REST controller with Quarkus. Quarkus OAuth2 provides a set of annotations for setting permissions. We can allow to call an endpoint by any user with @PermitAll annotation. The annotation @DenyAll indicates that the given endpoint cannot be accessed by anyone. We can also define a list of roles allowed for calling a given endpoint with @RolesAllowed.

The controller contains different types of CRUD methods. I defined three roles: viewer, manager, and admin. The viewer role allows calling only GET methods. The manager role allows calling GET and POST methods. Finally, the admin role allows calling all the methods. You can see the final implementation of the controller class below.

@Path("/employees")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
class EmployeeResource(val repository: EmployeeRepository) {

    @POST
    @Transactional
    @RolesAllowed(value = ["manager", "admin"])
    fun add(employee: Employee): Response {
        repository.persist(employee)
        return Response.ok(employee).status(201).build()
    }

    @DELETE
    @Path("/{id}")
    @Transactional
    @RolesAllowed("admin")
    fun delete(@PathParam id: Long) {
        repository.deleteById(id)
    }

    @GET
    @PermitAll
    fun findAll(): List<Employee> = repository.listAll()

    @GET
    @Path("/{id}")
    @RolesAllowed(value = ["manager", "admin", "viewer"])
    fun findById(@PathParam id: Long): Employee?
            = repository.findById(id)

    @GET
    @Path("/first-name/{firstName}/last-name/{lastName}")
    @RolesAllowed(value = ["manager", "admin", "viewer"])
    fun findByFirstNameAndLastName(@PathParam firstName: String,
                          @PathParam lastName: String): List<Employee>
            = repository.findByFirstNameAndLastName(firstName, lastName)

    @GET
    @Path("/salary/{salary}")
    @RolesAllowed(value = ["manager", "admin", "viewer"])
    fun findBySalary(@PathParam salary: Int): List<Employee>
            = repository.findBySalary(salary)

    @GET
    @Path("/salary-greater-than/{salary}")
    @RolesAllowed(value = ["manager", "admin", "viewer"])
    fun findBySalaryGreaterThan(@PathParam salary: Int): List<Employee>
            = repository.findBySalaryGreaterThan(salary)

}

Running 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 Quarkus 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=quarkus -e KEYCLOAK_PASSWORD=quarkus123 jboss/keycloak

Create client on Keycloak

First, we need to create a client with a given name. Let’s say this name is quarkus. The client credentials are used during the authorization process. It is important to choose confidential in the “Access Type” section and enable option “Direct Access Grants”.

quarkus-oauth2-keycloak-client

Then we may switch to the “Credentials” tab, and copy the client secret.

Configure Quarkus OAuth2 connection to Keycloak

In the next steps, we will use two HTTP endpoints exposed by Keycloak. First of them, token_endpoint allows you to generate new access tokens. The second endpoint introspection_endpoint is used to retrieve the active state of a token. In other words, you can use it to validate access or refresh token.

The Quarkus OAuth2 module expects three configuration properties. These are the client’s name, the client’s secret, and the address of the introspection endpoint. The last property quarkus.oauth2.role-claim is responsible for setting the name of claim used to load the roles. The list of roles is a part of the response returned by the introspection endpoint. Let’s take a look at the final list of configuration properties for integration with my local instance of Keycloak.

quarkus.oauth2.client-id=quarkus
quarkus.oauth2.client-secret=7dd4d516-e06d-4d81-b5e7-3a15debacebf
quarkus.oauth2.introspection-url=http://localhost:8888/auth/realms/master/protocol/openid-connect/token/introspect
quarkus.oauth2.role-claim=roles

Create users and roles on Keycloak

Our application uses three roles: viewer, manager, and admin. Therefore, we will create three test users on Keycloak. Each of them has a single role assigned. The manager role is a composite role, and it contains the viewer role. The same with the admin, that contains both manager and viewer. Here’s the full list of test users.

quarkus-oauth2-keycloak-users

Of course, we also need to define roles. In the picture below, I highlighted the roles used by our application.

Before proceeding to the tests, we need to do one thing. We have to edit the client scope responsible for displaying a list of roles. To do that go to the section “Client Scopes”, and then find the roles scope. After editing it, you should switch to the “Mappers” tab. Finally, you need to find and edit the “realm roles” entry. The value of a field “Token Claim Name” should be the same as the value set in the quarkus.oauth2.role-claim property. I highlighted it in the picture below. In the next section, I’ll show you how Quarkus OAuth2 retrieves roles from the introspection endpoint.

quarkus-oauth2-keycloak-clientclaim

Analyzing Quarkus OAuth2 authorization process

In the first step, we are calling the Keycloak token endpoint to obtain a valid access token. We may choose between five supported grant types. Because I want to authorize with a user password I’m setting parameter grant_type to password. We also need to set client_id, client_secret, and of course user credentials. A test user in the request visible below is test_viewer. It has the role viewer assigned.

$ curl -X POST http://localhost:8888/auth/realms/master/protocol/openid-connect/token \
-d "grant_type=password" \ 
-d "client_id=quarkus" \
-d "client_secret=7dd4d516-e06d-4d81-b5e7-3a15debacebf" \
-d "username=test_viewer" \
-d "password=123456"

{
    "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIilWRfdX...",
    "expires_in": 1800,
    "refresh_expires_in": 1800,
    "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2...",
    "token_type": "bearer",
    "not-before-policy": 1600100798,
    "session_state": "cf9862b0-f97a-43a7-abbb-a267fff5e71e",
    "scope": "email profile"
}

Once, we have successfully generated an access token, we may use it for authorizing requests sent to the Quarkus application. But before that, we can verify our token with the Keycloak introspect endpoint. It is an additional step. However, it shows you what type of information is returned by the introspect endpoint, which is then used by the Quarkus OAuth2 module. You can see the request and response for the token value generated in the previous step. Pay close attention to how it returns a list of user’s roles.

$ curl -X POST http://localhost:8888/auth/realms/master/protocol/openid-connect/token/introspect \
-d "token=eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIilWRfdX..."
-H "Authorization: Basic cXVhcmt1czo3ZGQ0ZDUxNi1lMDZkLTRkODEtYjVlNy0zYTE1ZGViYWNlYmY="

{
    "exp": 1600200132,
    "iat": 1600198332,
    "jti": "af160b82-ad41-45d3-8c7d-28096beb2509",
    "iss": "http://localhost:8888/auth/realms/master",
    "sub": "f41828f6-d597-41cb-9081-46c2d7a4d76b",
    "typ": "Bearer",
    "azp": "quarkus",
    "session_state": "0fdbbd83-35f9-4f4f-912a-c17979c2a87b",
    "preferred_username": "test_viewer",
    "email": "test_viewer@example.com",
    "email_verified": true,
    "acr": "1",
    "scope": "email profile",
    "roles": [
        "viewer"
    ],
    "client_id": "quarkus",
    "username": "test_viewer",
    "active": true
}

The generated access token is valid. So, now the only thing we need to do is to set it on the request inside the Authorization header. Role viewer is allowed for the endpoint GET /employees/{id}, so the HTTP response status is 200 OK or 204 No Content.

$ curl -v http://localhost:8080/employees/1 -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIilWRfdX..."
*   Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> GET /employees/1 HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.55.1
> Accept: */*
> Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIilWRfdX...
>
< HTTP/1.1 204 No Content
<
* Connection #0 to host localhost left intact

Now, let’s try to call the endpoint that is disallowed for the viewer role. In the request visible below, we are trying to call endpoint DELETE /employees/{id}. In line with the expectations, the HTTP response status is 403 Forbidden.

$ curl -v -X DELETE http://localhost:8080/employees/1 -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIilWRfdX..."
*   Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> GET /employees/1 HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.55.1
> Accept: */*
> Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIilWRfdX...
>
< HTTP/1.1 403 Forbidden
< Content-Length: 0
<
* Connection #0 to host localhost left intact

Conclusion

It is relatively easy to configure and implement OAuth2 support with Quarkus. However, you may spend a lot of time on Keycloak configuration. That's why I explained step-by-step how to set up OAuth2 authorization there. Enjoy 🙂

The post Quarkus OAuth2 and security with Keycloak appeared first on Piotr's TechBlog.

]]>
https://piotrminkowski.com/2020/09/16/quarkus-oauth2-and-security-with-keycloak/feed/ 0 8811
Micronaut Tutorial: Security https://piotrminkowski.com/2019/04/25/micronaut-tutorial-security/ https://piotrminkowski.com/2019/04/25/micronaut-tutorial-security/#respond Thu, 25 Apr 2019 14:34:16 +0000 https://piotrminkowski.wordpress.com/?p=7122 This is the third part of my tutorial to Micronaut Framework. This time we will discuss the most interesting Micronaut security features. I have already described core mechanisms for IoC and dependency injection in the first part of my tutorial, and I have also created a guide to building a simple REST server-side application in […]

The post Micronaut Tutorial: Security appeared first on Piotr's TechBlog.

]]>
This is the third part of my tutorial to Micronaut Framework. This time we will discuss the most interesting Micronaut security features. I have already described core mechanisms for IoC and dependency injection in the first part of my tutorial, and I have also created a guide to building a simple REST server-side application in the second part.

For more details you may refer to:

Security is an essential part of every web application. Easily configurable, built-in web security mechanisms is something that every single modern micro-framework must have. It is no different with Micronaut. In this part of my tutorial you will learn how to:

  • Build custom authentication provider
  • Configure and test basic authentication for your HTTP API
  • Secure your HTTP API using JSON Web Tokens
  • Enable communication over HTTPS

Enabling security

To enable security for Micronaut application you should first include the following dependency into your pom.xml:

<dependency>
   <groupId>io.micronaut</groupId>
   <artifactId>micronaut-security</artifactId>
</dependency>

The next step is to enable security feature through application properties:

micronaut:
  security:
    enabled: true

Setting the property micronaut.security.enabled to true causes enabling security for all the existing controllers. Because we already have the controller that has been used as an example for the previous part of the tutorial, we should disable security for it. To do that I have annotated with @Secured(SecurityRule.IS_ANONYMOUS). It allows anonymous access to all endpoints implemented inside the controller.

@Controller("/persons")
@Secured(SecurityRule.IS_ANONYMOUS)
@Validated
public class PersonController { ... }

Basic Authentication Provider

Once you enable Micronaut security, Basic Auth is enabled by default. All you need to do is to implement your custom authentication provider. It has to implement an AuthenticationProvider interface. In fact, you just need to verify your username and password, which are both passed inside HTTP Authorization header. Our sample authentication provider uses configuration properties as a user repository. Here’s the fragment of application.yml file that contains list of user passwords and assigned roles:

credentials:
  users:
    smith: smith123
    scott: scott123
    piomin: piomin123
    test: test123
  roles:
    smith: ADMIN
    scott: VIEW
    piomin: VIEW
    test: ADMIN

The configuration properties are injected into UsersStore configuration bean which is annotated with @ConfigurationProperties. User passwords are stored inside users map, while roles inside roles map. They are both annotated with @MapFormat and have username as a key.

@ConfigurationProperties("credentials")
public class UsersStore {

   @MapFormat
   Map<String, String> users;
   @MapFormat
   Map<String, String> roles;

   public String getUserPassword(String username) {
      return users.get(username);
   }

   public String getUserRole(String username) {
      return roles.get(username);
   }
}

Finally, we may proceed to the authentication provider implementation. It injects a UsersStore bean that contains a list of users with passwords and roles. The overridden method should return UserDetails object. The username and password are automatically decoded from base64 taken from Authentication header and bind to fields identity and secret in AuthenticationRequest method parameter. If input password is the same as stored password it returns UserDetails object with roles, otherwise throws an exception.

@Singleton
public class UserPasswordAuthProvider implements AuthenticationProvider {

    @Inject
    UsersStore store;

    @Override
    public Publisher<AuthenticationResponse> authenticate(AuthenticationRequest req) {
        String username = req.getIdentity().toString();
        String password = req.getSecret().toString();
        if (password.equals(store.getUserPassword(username))) {
            UserDetails details = new UserDetails(username, Collections.singletonList(store.getUserRole(username)));
            return Flowable.just(details);
        } else {
            return Flowable.just(new AuthenticationFailed());
        }
    }
}

Secured Controller

Now, we may create our sample secure REST controller. The following controller is just a copy of previously described controller PersonController, but it also contains some Micronaut Security annotation. Through @Secured(SecurityRule.IS_AUTHENTICATED) used on the whole controller it is available only for succesfully authenticated users. This annotation may be overridden on the method level. The method for adding a new person is available only for users having ADMIN role.

@Controller("/secure/persons")
@Secured(SecurityRule.IS_AUTHENTICATED)
public class SecurePersonController {

   List<Person> persons = new ArrayList<>();

   @Post
   @Secured("ADMIN")
   public Person add(@Body @Valid Person person) {
      person.setId(persons.size() + 1);
      persons.add(person);
      return person;
   }

   @Get("/{id:4}")
   public Optional<Person> findById(@NotNull Integer id) {
      return persons.stream()
            .filter(it -> it.getId().equals(id))
            .findFirst();
   }

   @Version("1")
   @Get("{?max,offset}")
   public List<Person> findAll(@Nullable Integer max, @Nullable Integer offset) {
      return persons.stream()
            .skip(offset == null ? 0 : offset)
            .limit(max == null ? 10000 : max)
            .collect(Collectors.toList());
   }

   @Version("2")
   @Get("?max,offset")
   public List<Person> findAllV2(@NotNull Integer max, @NotNull Integer offset) {
      return persons.stream()
            .skip(offset == null ? 0 : offset)
            .limit(max == null ? 10000 : max)
            .collect(Collectors.toList());
   }

}

To test Micronaut security features used in our controller we will create a JUnit test class containing three methods. All these methods use Micronaut HTTP client for calling target endpoints. It provides basicAuth method, that allows you to easily pass user credentials. The first test method testAdd verifies a positive scenario of adding a new person. The test user smith has ADMIN role, which is required for calling this HTTP endpoint. In contrast, method testAddFailed calls the same HTTP endpoint, but with different user scott, which has VIEW role. We expect that HTTP 401 is returned by the endpoint. The same user scott has an access to GET endpoints, so we expect that test method testFindById is finished with success.

@MicronautTest
public class SecurePersonControllerTests {

   @Inject
   EmbeddedServer server;

   @Test
   public void testAdd() throws MalformedURLException {
      HttpClient client = HttpClient.create(new URL("http://" + server.getHost() + ":" + server.getPort()));
      Person person = new Person();
      person.setFirstName("John");
      person.setLastName("Smith");
      person.setAge(33);
      person.setGender(Gender.MALE);
      person = client.toBlocking()
            .retrieve(HttpRequest.POST("/secure/persons", person).basicAuth("smith", "smith123"), Person.class);
      Assertions.assertNotNull(person);
      Assertions.assertEquals(Integer.valueOf(1), person.getId());
   }

   @Test
   public void testAddFailed() throws MalformedURLException {
      HttpClient client = HttpClient.create(new URL("http://" + server.getHost() + ":" + server.getPort()));
      Person person = new Person();
      person.setFirstName("John");
      person.setLastName("Smith");
      person.setAge(33);
      person.setGender(Gender.MALE);
      Assertions.assertThrows(HttpClientResponseException.class,
            () -> client.toBlocking().retrieve(HttpRequest.POST("/secure/persons", person).basicAuth("scott", "scott123"), Person.class),
            "Forbidden");
   }

   @Test
   public void testFindById() throws MalformedURLException {
      HttpClient client = HttpClient.create(new URL("http://" + server.getHost() + ":" + server.getPort()));
      Person person = client.toBlocking()
            .retrieve(HttpRequest.GET("/secure/persons/1").basicAuth("scott", "scott123"), Person.class);
      Assertions.assertNotNull(person);
   }
}

Enable HTTPS

Our controller is secured, but not the HTTP server. Micronaut by default starts the server with disabled SSL. However, it supports HTTPS out of the box. To enable HTTPS support you should first set property micronaut.ssl.enabled to true. By default Micronaut with HTTPS enabled starts on port 8443, but you can override it using property micronaut.ssl.port.
We will enable HTTPS only for the single JUnit test class. To do that we first create file src/test/resources/ssl.yml with the following configuration:

micronaut:
  ssl:
    enabled: true
    buildSelfSigned: true

Micronaut simplifies SSL configuration build for test purposes. It turns out, we don’t have to generate any keystore or certificate if we use property micronaut.ssl.buildSelfSigned. Otherwise you would have to generate a keystore by yourself. It is not difficult, if you are creating a self-signed certificate. You may use openssl or keytool for that. Here’s the appropriate keytool command for generating keystore, however you should point out that it is recommended tool by Micronaut, which recommend using openssl:

$ keytool -genkey -alias server -keystore server.jks

If you decide to generate self-signed certificate by yourself you have configure them:

micronaut:
  ssl:
    enabled: true
    keyStore:
      path: classpath:server.keystore
      password: 123456
      type: JKS

The last step is to create a JUnit test that uses configuration provided in file ssl.yml.

@MicronautTest(propertySources = "classpath:ssl.yml")
public class SecureSSLPersonControllerTests {

   @Inject
   EmbeddedServer server;
   
   @Test
   public void testFindById() throws MalformedURLException {
      HttpClient client = HttpClient.create(new URL(server.getScheme() + "://" + server.getHost() + ":" + server.getPort()));
      Person person = client.toBlocking()
            .retrieve(HttpRequest.GET("/secure/persons/1").basicAuth("scott", "scott123"), Person.class);
      Assertions.assertNotNull(person);
   }
   
   // other tests ...

}

JWT Authentication

To enable JWT token based authentication we first need to include the following dependency into pom.xml:

<dependency>
   <groupId>io.micronaut</groupId>
   <artifactId>micronaut-security-jwt</artifactId>
</dependency>

Token authentication is enabled by default through TokenConfigurationProperties properties (micronaut.security.token.enabled). However, we should enable JWT based authentication by setting property micronaut.security.token.jwt.enabled to true. This change allows us to use JWT authentication for our sample application. We also need to be able to generate an authentication token used for authorization. To do that we should enable /login endpoint and set some configuration properties for the JWT token generator. In the following fragment of application.yml I set HMAC with SHA-256 as hash algorithm for JWT signature generator:

micronaut:
  security:
    enabled: true
    endpoints:
      login:
        enabled: true
    token:
      jwt:
        enabled: true
        signatures:
          secret:
            generator:
              secret: pleaseChangeThisSecretForANewOne
              jws-algorithm: HS256

Now, we can call endpoint POST /login with username and password in JSON body as shown below:

$ curl -X "POST" "http://localhost:8100/login" -H 'Content-Type: application/json; charset=utf-8' -d '{"username":"smith","password":"smith123"}'
{
   "username": "smith",
   "roles": [
      "ADMIN"
   ],
   "access_token": "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJzbWl0aCIsIm5iZiI6MTU1NjE5ODAyMCwicm9sZXMiOlsiQURNSU4iXSwiaXNzIjoic2FtcGxlLW1pY3JvbmF1dC1hcHBsaWNhdGlvbiIsImV4cCI6MTU1NjIwMTYyMCwiaWF0IjoxNTU2MTk4MDIwfQ.by0Dx73QIZeF4MDM4A5nHgw8xm4haPJjsu9z45psQrY",
   "refresh_token": "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJzbWl0aCIsIm5iZiI6MTU1NjE5ODAyMCwicm9sZXMiOlsiQURNSU4iXSwiaXNzIjoic2FtcGxlLW1pY3JvbmF1dC1hcHBsaWNhdGlvbiIsImlhdCI6MTU1NjE5ODAyMH0.2BrdZzuvJNymZlOv56YpUPHYLDdnVAW5UXXNuz3a7xU",
   "token_type": "Bearer",
   "expires_in": 3600
}

The value of field access_token returned in the response should be passed as bearer token in the Authorization header of requests sent to HTTP endpoints. We can any endpoint, for example GET /persons

$ curl -X "GET" "http://localhost:8100/persons" -H "Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJzbWl0aCIsIm5iZiI6MTU1NjE5ODAyMCwicm9sZXMiOlsiQURNSU4iXSwiaXNzIjoic2FtcGxlLW1pY3JvbmF1dC1hcHBsaWNhdGlvbiIsImV4cCI6MTU1NjIwMTYyMCwiaWF0IjoxNTU2MTk4MDIwfQ.by0Dx73QIZeF4MDM4A5nHgw8xm4haPJjsu9z45psQrY"

We can easily test automatically the scenario described above. I have created UserCredentials and UserToken objects for serializing request and deserializing response from /login endpoint. The token retrieved from response is then passed as bearer token by calling bearerAuth method on Micronaut HTTP client instance.

@MicronautTest
public class SecurePersonControllerTests {

   @Inject
   EmbeddedServer server;
   
   @Test
   public void testFindByIdUsingJWTToken() throws MalformedURLException {
      HttpClient client = HttpClient.create(new URL("http://" + server.getHost() + ":" + server.getPort()));
      UserToken token = client.toBlocking().retrieve(HttpRequest.POST("/login", new User Credentials("scott", "scott123")), UserToken.class);
      Person person = client.toBlocking()
            .retrieve(HttpRequest.GET("/secure/persons/1").bearerAuth(token.getAccessToken()), Person.class);
      Assertions.assertNotNull(person);
   }
}

Source Code

We were using the same repository as for two previous parts of my Micronaut tutorial: https://github.com/piomin/sample-micronaut-applications.git.

The post Micronaut Tutorial: Security appeared first on Piotr's TechBlog.

]]>
https://piotrminkowski.com/2019/04/25/micronaut-tutorial-security/feed/ 0 7122
Introduction to Blockchain with Java using Ethereum, web3j and Spring Boot https://piotrminkowski.com/2018/06/22/introduction-to-blockchain-with-java-using-ethereum-web3j-and-spring-boot/ https://piotrminkowski.com/2018/06/22/introduction-to-blockchain-with-java-using-ethereum-web3j-and-spring-boot/#comments Fri, 22 Jun 2018 12:22:02 +0000 https://piotrminkowski.wordpress.com/?p=6699 Blockchain is one of the buzzwords in the IT world during some last months. This term is related to cryptocurrencies, and was created together with Bitcoins. It is decentralized, immutable data structure divided into blocks, which are linked and secured using cryptographic algorithms. Every single block in this structure typically contains a cryptographic hash of […]

The post Introduction to Blockchain with Java using Ethereum, web3j and Spring Boot appeared first on Piotr's TechBlog.

]]>
Blockchain is one of the buzzwords in the IT world during some last months. This term is related to cryptocurrencies, and was created together with Bitcoins. It is decentralized, immutable data structure divided into blocks, which are linked and secured using cryptographic algorithms. Every single block in this structure typically contains a cryptographic hash of the previous block, a timestamp, and transaction data. Blockchain is managed by a peer-to-peer network, and during inter-node communication every new block is validated before adding. This is a short portion of theory about blockchain. In a nutshell, this is a technology which allows us to manage transactions between two parties in a decentralized way. Now, the question is how we can implement it in our system.
Here comes Ethereum. It is a decentralized platform created by Vitarik Buterin that provides scripting language for the development of applications. It is based on ideas from Bitcoin, and is driven by the new cryptocurrency called Ether. Today, Ether is the second largest cryptocurrency after Bitcoin. The heart of Ethereum technology is EVM (Ethereum Virtual Machine), which can be treated as something similar to JVM, but using a network of fully decentralized nodes. To implement transactions based Ethereum in the Java world we use web3j library. This is a lightweight, reactive, type safe Java and Android library for integrating with nodes on Ethereum blockchains. More details can be found on its website https://web3j.io.

1. Running Ethereum locally

Although there are many articles on the Web about blockchain and ethereum it is not easy to find a solution describing how to run ready-for-use instances of Ethereum on the local machine. It is worth to mention that generally there are two most popular Ethereum clients we can use: Geth and Parity. It turns out we can easily run Geth nodes locally using a Docker container. By default it connects the node to the Ethereum main network. Alternatively, you can connect it to the test network or Rinkeby network. But the best option for beginning is just to run it in development mode by setting --dev parameter on Docker container running command.
Here’s the command that starts Docker container in development mode and exposes Ethereum RPC API on port 8545.

$ docker run -d --name ethereum -p 8545:8545 -p 30303:30303 ethereum/client-go --rpc --rpcaddr "0.0.0.0" --rpcapi="db,eth,net,web3,personal" --rpccorsdomain "*" --dev

The one really good message when running that container in development mode is that you have plenty of Ethers on your default, test account. In that case, you don’t have to mine any Ethers to be able to start tests. Great! Now, let’s create some other test accounts and also check out some things. To achieve it we need to run Geth’s interactive JavaScript console inside Docker container.

$ docker exec -it ethereum geth attach ipc:/tmp/geth.ipc

2. Managing Ethereum node using JavaScript console

After running the JavaScript console you can easily display the default account (coinbase), the list of all available accounts and their balances. Here’s the screen illustrating results for my Ethereum node.
blockchain-1
Now, we have to create some test accounts. We can do it by calling personal.newAccount(password) function. After creating required accounts, you can perform some test transactions using the JavaScript console, and transfer some funds from the base account to the newly created accounts. Here are the commands used for creating accounts and executing transactions.
blockchain-2

3. Blockchain with Spring Boot system

The architecture of our sample system is very simple. I don’t want to complicate anything, but just show you how to send transactions to Geth node and receive notifications. While transaction-service sends a new transaction to Ethereum node, bonus-service observe node and listen for incoming transactions. Then it send bonus to the sender’s account once per 10 transactions received from his account. Here’s the diagram that illustrates an architecture of our sample system.
blockchain-arch

4. Enable Web3j for Ethereum Spring Boot application

I think that now we have clarity on exactly what we want to do. So, let’s proceed to the implementation. First, we should include all required dependencies in order to be able to use the web3j library inside the Spring Boot application. Fortunately, there is a starter that can be included.

<dependency>
   <groupId>org.web3j</groupId>
   <artifactId>web3j-spring-boot-starter</artifactId>
   <version>1.6.0</version>
</dependency>

Because we are running Ethereum Geth client on Docker container we need to change auto-configured client’s address for web3j.

spring:
  application:
    name: transaction-service
server:
  port: ${PORT:8090}
web3j:
  client-address: http://192.168.99.100:8545

5. Building Ethereum Spring Boot applications

If we included web3j starter to the project dependencies all you need is to autowire Web3j bean. Web3j is responsible for sending transactions to Geth client node. It receives a response with transaction hash if it has been accepted by the node or error object if it has been rejected. While creating a transaction object it is important to set the gas limit to minimum 21000. If you send a lower value, you will probably receive error Error: intrinsic gas too low.

@Service
public class BlockchainService {

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

    @Autowired
    Web3j web3j;

    public BlockchainTransaction process(BlockchainTransaction trx) throws IOException {
        EthAccounts accounts = web3j.ethAccounts().send();
        EthGetTransactionCount transactionCount = web3j.ethGetTransactionCount(accounts.getAccounts().get(trx.getFromId()), DefaultBlockParameterName.LATEST).send();
        Transaction transaction = Transaction.createEtherTransaction(accounts.getAccounts().get(trx.getFromId()), transactionCount.getTransactionCount(), BigInteger.valueOf(trx.getValue()), BigInteger.valueOf(21_000), accounts.getAccounts().get(trx.getToId()),BigInteger.valueOf(trx.getValue()));
        EthSendTransaction response = web3j.ethSendTransaction(transaction).send();
        if (response.getError() != null) {
            trx.setAccepted(false);
            return trx;
        }
        trx.setAccepted(true);
        String txHash = response.getTransactionHash();
        LOGGER.info("Tx hash: {}", txHash);
        trx.setId(txHash);
        EthGetTransactionReceipt receipt = web3j.ethGetTransactionReceipt(txHash).send();
        if (receipt.getTransactionReceipt().isPresent()) {
            LOGGER.info("Tx receipt: {}", receipt.getTransactionReceipt().get().getCumulativeGasUsed().intValue());
        }
        return trx;
    }

}

The @Service bean visible above is invoked by the controller. The implementation of POST method takes BlockchainTransaction object as parameter. You can send sender id, receiver id, and transaction amount. Sender and receiver ids are equivalent to index in query eth.account[index].

@RestController
public class BlockchainController {

    @Autowired
    BlockchainService service;

    @PostMapping("/transaction")
    public BlockchainTransaction execute(@RequestBody BlockchainTransaction transaction) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException, CipherException, IOException {
        return service.process(transaction);
    }

}

You can send a test transaction by calling POST method using the following command.

  $ curl --header "Content-Type: application/json" --request POST --data '{"fromId":2,"toId":1,"value":3}' http://localhost:8090/transaction

Before sending any transactions you should also unlock the sender account.
blockchain-3

Application bonus-service listens for transactions processed by the Ethereum node. It subscribes for notifications from Web3j library by calling web3j.transactionObservable().subscribe(...) method. It returns the amount of received transactions to the sender’s account once per 10 transactions sent from that address. Here’s the implementation of the observable method inside application bonus-service.

@Autowired
Web3j web3j;

@PostConstruct
public void listen() {
   Subscription subscription = web3j.transactionObservable().subscribe(tx -> {
      LOGGER.info("New tx: id={}, block={}, from={}, to={}, value={}", tx.getHash(), tx.getBlockHash(), tx.getFrom(), tx.getTo(), tx.getValue().intValue());
      try {
         EthCoinbase coinbase = web3j.ethCoinbase().send();
         EthGetTransactionCount transactionCount = web3j.ethGetTransactionCount(tx.getFrom(), DefaultBlockParameterName.LATEST).send();
         LOGGER.info("Tx count: {}", transactionCount.getTransactionCount().intValue());
         if (transactionCount.getTransactionCount().intValue() % 10 == 0) {
            EthGetTransactionCount tc = web3j.ethGetTransactionCount(coinbase.getAddress(), DefaultBlockParameterName.LATEST).send();
            Transaction transaction = Transaction.createEtherTransaction(coinbase.getAddress(), tc.getTransactionCount(), tx.getValue(), BigInteger.valueOf(21_000), tx.getFrom(), tx.getValue());
            web3j.ethSendTransaction(transaction).send();
         }
      } catch (IOException e) {
         LOGGER.error("Error getting transactions", e);
      }
   });
   LOGGER.info("Subscribed");
}

Conclusion

Blockchain and cryptocurrencies are not the easy topics to start. Ethereum simplifies development of applications that use blockchain, by providing a complete, scripting language. Using the web3j library together with Spring Boot and Docker image of Ethereum Geth client allows to quickly start local development of solutions implementing blockchain technology. IF you would like to try it locally just clone my repository available on GitHub https://github.com/piomin/sample-spring-blockchain.git

The post Introduction to Blockchain with Java using Ethereum, web3j and Spring Boot appeared first on Piotr's TechBlog.

]]>
https://piotrminkowski.com/2018/06/22/introduction-to-blockchain-with-java-using-ethereum-web3j-and-spring-boot/feed/ 14 6699
Secure Discovery with Spring Cloud Netflix Eureka https://piotrminkowski.com/2018/05/21/secure-discovery-with-spring-cloud-netflix-eureka/ https://piotrminkowski.com/2018/05/21/secure-discovery-with-spring-cloud-netflix-eureka/#comments Mon, 21 May 2018 08:47:54 +0000 https://piotrminkowski.wordpress.com/?p=6600 Building a standard, not secure discovery mechanism with Spring Cloud Netflix Eureka is rather an easy thing to do. The same solution built over secure SSL communication between discovery client and server maybe a slightly more advanced challenge. I haven’t found any complete example of such an application on the web. Let’s try to implement […]

The post Secure Discovery with Spring Cloud Netflix Eureka appeared first on Piotr's TechBlog.

]]>
Building a standard, not secure discovery mechanism with Spring Cloud Netflix Eureka is rather an easy thing to do. The same solution built over secure SSL communication between discovery client and server maybe a slightly more advanced challenge. I haven’t found any complete example of such an application on the web. Let’s try to implement it beginning from the server-side application.

1. Generate certificates

If you develop Java applications for some years you have probably heard about keytool. This tool is available in your ${JAVA_HOME}\bin directory and is designed for managing keys and certificates. We begin by generating a keystore for the server-side Spring Boot application. Here’s the appropriate keytool command that generates a certficate stored inside JKS keystore file named eureka.jks.

secure-discovery-2

2. Setting up a secure Spring Eureka server

Since the Eureka server is embedded to the Spring Boot application, we need to secure it using standard Spring Boot properties. I placed generated keystore file eureka.jks on the application’s classpath. Now, the only thing that has to be done is to prepare some configuration settings inside application.yml that point to keystore file location, type, and access password.

server:
  port: 8761
  ssl:
    enabled: true
    key-store: classpath:eureka.jks
    key-store-password: 123456
    trust-store: classpath:eureka.jks
    trust-store-password: 123456
    key-alias: eureka

3. Setting up two-way SSL authentication

We will complicate our example a little. A standard SSL configuration assumes that only the client verifies the server certificate. We will force client’s certificate authentication on the server-side. It can be achieved by setting the property server.ssl.client-auth to need.

server:
  ssl:
    client-auth: need

It’s not all, because we also have to add client’s certficate to the list of trusted certificates on the server-side. So, first let’s generate client’s keystore using the same keytool command as for server’s keystore.

secure-spring-eureka-discovery-1

Now, we need to export certficates from generated keystores for both client and server sides.

secure-spring-eureka-secure-discovery-3

Finally, we import the client’s certificate to the server’s keystore and the server’s certificate to the client’s keystore.

secure-spring-eureka-secure-discovery-4

4. Running secure Spring Eureka server

The sample applications are available on GitHub in repository sample-secure-eureka-discovery (https://github.com/piomin/sample-secure-eureka-discovery.git). After running discovery-service application, Eureka is available under address https://localhost:8761. If you try to visit its web dashboard you get the following exception in your web browser. It means Eureka server is secured.

hqdefault

Well, Eureka dashboard is sometimes an useful tool, so let’s import client’s keystore to our web browser to be able to access it. We have to convert client’s keystore from JKS to PKCS12 format. Here’s the command that performs mentioned operation.

$ keytool -importkeystore -srckeystore client.jks -destkeystore client.p12 -srcstoretype JKS -deststoretype PKCS12 -srcstorepass 123456 -deststorepass 123456 -srcalias client -destalias client -srckeypass 123456 -destkeypass 123456 -noprompt

5. Client’s secure application configuration

When implementing a secure connection on the client-side, we generally need to do the same as in the previous step – import a keystore. However, it is not a very simple thing to do, because Spring Cloud does not provide any configuration property that allows you to pass the location of the SSL keystore to a discovery client. What’s worth mentioning Eureka client leverages the Jersey client to communicate with the server-side application. It may be surprising a little it is not Spring RestTemplate, but we should remember that Spring Cloud Eureka is built on top of Netflix OSS Eureka client, which does not use Spring libraries.
HTTP basic authentication is automatically added to your eureka client if you include security credentials to connection URL, for example http://piotrm:12345@localhost:8761/eureka. For more advanced configuration, like passing SSL keystore to HTTP client we need to provide @Bean of type DiscoveryClientOptionalArgs.
The following fragment of code shows how to enable SSL connection for discovery client. First, we set location of keystore and truststore files using javax.net.ssl.* Java system property. Then, we provide custom implementation of Jersey client based on Java SSL settings, and set it for DiscoveryClientOptionalArgs bean.

@Bean
public DiscoveryClient.DiscoveryClientOptionalArgs discoveryClientOptionalArgs() throws NoSuchAlgorithmException {
   DiscoveryClient.DiscoveryClientOptionalArgs args = new DiscoveryClient.DiscoveryClientOptionalArgs();
   System.setProperty("javax.net.ssl.keyStore", "src/main/resources/client.jks");
   System.setProperty("javax.net.ssl.keyStorePassword", "123456");
   System.setProperty("javax.net.ssl.trustStore", "src/main/resources/client.jks");
   System.setProperty("javax.net.ssl.trustStorePassword", "123456");
   EurekaJerseyClientBuilder builder = new EurekaJerseyClientBuilder();
   builder.withClientName("account-client");
   builder.withSystemSSLConfiguration();
   builder.withMaxTotalConnections(10);
   builder.withMaxConnectionsPerHost(10);
   args.setEurekaJerseyClient(builder.build());
   return args;
}

6. Enabling HTTPS on the client side

The configuration provided in the previous step applies only to communication between the discovery client and the Eureka server. What if we also would like to secure HTTP endpoints exposed by the client-side application? The first step is pretty the same as for the discovery server: we need to generate keystore and set it using Spring Boot properties inside application.yml.

server:
  port: ${PORT:8090}
  ssl:
    enabled: true
    key-store: classpath:client.jks
    key-store-password: 123456
    key-alias: client

During registration we need to “inform” Eureka server that our application’s endpoints are secured. To achieve it we should set property eureka.instance.securePortEnabled to true, and also disable non secure port, which is enabled by default.with nonSecurePortEnabled property.

eureka:
  instance:
    nonSecurePortEnabled: false
    securePortEnabled: true
    securePort: ${server.port}
    statusPageUrl: https://localhost:${server.port}/info
    healthCheckUrl: https://localhost:${server.port}/health
    homePageUrl: https://localhost:${server.port}
  client:
    securePortEnabled: true
    serviceUrl:
      defaultZone: https://localhost:8761/eureka/

7. Running secure Spring client’s application

Finally, we can run client-side application. After launching the application should be visible in Eureka Dashboard.

secure-discovery-5

All the client application’s endpoints are registred in Eureka under HTTPS protocol. I have also override default implementation of actuator endpoint /info, as shown on the code fragment below.

@Component
public class SecureInfoContributor implements InfoContributor {

   @Override
   public void contribute(Builder builder) {
      builder.withDetail("hello", "I'm secure app!");
   }

}

Now, we can try to visit /info endpoint one more time. You should see the same information as below.

secure-discovery-6

Alternatively, if you try to set on the client-side the certificate, which is not trusted by server-side, you will see the following exception while starting your client application.

secure-discovery-7

Conclusion

Securing connection between microservices and Eureka server is only the first step of securing the whole system. We need to thing about secure connection between microservices and config server, and also between all microservices during inter-service communication with @LoadBalanced RestTemplate or OpenFeign client. You can find the examples of such implementations and many more in my book “Mastering Spring Cloud” (https://www.packtpub.com/application-development/mastering-spring-cloud).

The post Secure Discovery with Spring Cloud Netflix Eureka appeared first on Piotr's TechBlog.

]]>
https://piotrminkowski.com/2018/05/21/secure-discovery-with-spring-cloud-netflix-eureka/feed/ 9 6600
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
Building Secure APIs with Vert.x and OAuth2 https://piotrminkowski.com/2017/09/15/building-secure-apis-with-vert-x-and-oauth2/ https://piotrminkowski.com/2017/09/15/building-secure-apis-with-vert-x-and-oauth2/#respond Fri, 15 Sep 2017 08:13:24 +0000 https://piotrminkowski.wordpress.com/?p=6034 Preface Today I would like to get back to the subject touched on in one of my previous articles – Vert.x toolkit. In the post Asynchronous Microservices With Vert.x. I described how to develop microservices using Vert.x modules for a web application, service discovery, circuit breaker, and distributed configuration. I did not mention there anything […]

The post Building Secure APIs with Vert.x and OAuth2 appeared first on Piotr's TechBlog.

]]>
Preface

Today I would like to get back to the subject touched on in one of my previous articles – Vert.x toolkit. In the post Asynchronous Microservices With Vert.x. I described how to develop microservices using Vert.x modules for a web application, service discovery, circuit breaker, and distributed configuration. I did not mention there anything about security aspects, which are usually important when talking about open APIs. It is time to take a closer look at some Vert.x modules for authentication and authorization. Following description available on vert.io site it provides some simple out of the box implementations for authentication in our applications. There are modules providing auth implementation backed by JDBC, MongoDB, and also some supporting solutions like JSON web tokens (JWT), Apache Shiro, and OAuth2. Like you probably know OAuth2 is the most common authentication method for APIs provided by Facebook, Twitter, or LinkedIn. If you are interested in more details about that authentication method read my article Microservices security with Oauth2, where I described the basics and introduced the simple sample with Spring Security in conjunction with OAuth2 usage.

In the sample application which is available on GitHub under security branch I’m going to present how to provide Oauth2 security for Vertx application using Keycloak and Vert.x OAuth2 module.

Keycloak

For authentication and authorization management we use Keycloak. It is an open-source identity and access management solution, which provides mechanisms supporting OAuth2. Keycloak has a web admin console where administrators can manage all aspects of the server. We can easily run it using a Docker container.

$ docker run -d --name keycloak -p 38080:8080 -e KEYCLOAK_USER=admin -e KEYCLOAK_PASSWORD=admin -e KEYCLOAK_LOGLEVEL=DEBUG jboss/keycloak

Management dashboard is available under http://192.168.99.100:38080/. Let’s begin from creating Client. The client will be used by our application (or rather a service) for authenticating itself against Keycloak. In the first step we have to set Client ID and Root URL. Root URL is not needed while using OAuth2 Password Credentials Flow, but rather for Authorization Code Flow. I put there our sample application localhost address.

vertx-sec-1

We should enable options Direct Access Grants and Authorization in the Settings section of newly created client. Also Access Type should be set to confidential and Valid Redirect URIs to the callback address routed inside application (it is explained in the later section).

vertx-sec-2

The last information needed from Client section is a Secret available under Credentials tab.

vertx-sec-3

Now we can proceed to create a user with credentials. In the sample I’ll present in the next section we use password credentials flow, so don’t forget to change the password on the newly created user.

vertx-sec-5

vertx-sec-7

Finally, we set authorities for our user. First, let’s create some roles in Roles section. For me it is view-account, modify-account. For these roles I also enabled Scope Param Required. It means that if client need to obtain that authority it has to send role name in the request scope.

vertx-sec-4

The last step is to assign the roles to our test user piotr.minkowski.

vertx-sec-6

Building application

Vert.x provides the module supporting OAuth2 authorization. We should include the following dependency into our pom.xml.

<dependency>
   <groupId>io.vertx</groupId>
   <artifactId>vertx-auth-oauth2</artifactId>
   <version>${vertx.version}</version>
</dependency>

We have to begin from defining Keycloak OAuth2Auth provider. We use the default realm (1). In additional to the realm name we should set realm public key (2) which is available in the Realm Settings section under Keys tab. We should also set Keycloak Client ID (3) as resource and client secret as credentials (4).

JsonObject keycloakJson = new JsonObject()
   .put("realm", "master") // (1)
   .put("realm-public-key", "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1xVBifXfS1uVM8S14JlyLpXck+0+hBQX258IiL5Fm2rZpkQ5lN9N1tadQdXBKk8V/0SxdTyoX7cpYQkcOs0Rj0XXmX7Lnk56euZwel+3MKAZWA20ld8BCfmDtX4/+VP311USUqR/W8Fd2p/gugKWF6VDMkri92qob1DdrcUiRlD8XYC0pwHwSvyW/3JvE5HeTy3U4vxC+19wHcwzLGNlVOlYPk9mzJHXN+LhZr/Tc7HeAsvVxYDXwOOh+/UWweMkvKy+OSNKG3aWLb92Ni3HejFn9kd4TRHfaapwWg1m5Duf3uqz8WDHbS/LeS4g3gQS0SvcCYI0huSoG3NA/z4K7wIDAQAB") // (2)
   .put("auth-server-url", "http://192.168.99.100:38080/auth")
   .put("ssl-required", "external")
   .put("resource", "vertx-account") // (3)
   .put("credentials", new JsonObject().put("secret", "73b55e04-e562-41ea-b39c-263b7b36945d")); // (4)

OAuth2Auth oauth2 = KeycloakAuth.create(vertx, OAuth2FlowType.PASSWORD, keycloakJson);

vertx-sec-8

I exposed API method for login which retrieves token from Keycloak using OAuth2FlowType.PASSWORD authentication method.

router.post("/login").produces("application/json").handler(rc -> {
   User u = Json.decodeValue(rc.getBodyAsString(), User.class);
   oauth2.getToken(u.toJson(), res -> {
      if (res.failed()) {
         LOGGER.error("Access token error: {}", res.cause().getMessage());
         rc.response().setStatusCode(HttpResponseStatus.INTERNAL_SERVER_ERROR.code()).end();
      } else {
         AccessToken token = res.result();
         LOGGER.info("Access Token: {}", KeycloakHelper.rawAccessToken(token.principal()));
         User user = new User(KeycloakHelper.rawAccessToken(token.principal()));
         rc.response().end(user.toString());
      }
   });
});

I sent the following message to the endpoint POST /login.

{
   "username":"piotr.minkowski", 
   "password":"Piot_123", 
   "scope":"modify-account view-account"
}

That is an equivalent to the following Vert.x JsonObject passed as a parameter to OAuth2 getToken method.

new JsonObject().put("username", "piotr.minkowski").put("password", "Piot_123").put("scope", "modify-account view-account")

POST /login method return access token inside JSON object. That token should be passed as Authorization header parameter for every call of a protected resource. Here’s the main class with API methods definitions. We begin from creating OAuth2AuthHandler object (1) which is responsible for token validation. It takes OAuth2Auth Keycloak object as a parameter. Then we should set OAuth2AuthHandler as a handler for all methods matching /account/* path (2). If the token has been successfully validated we can proceed to authorization. We check if view-account role is assigned to user when calling GET method (3), and modify-account role when calling POST method (4). If using Keycloak for authorization we always have to set the prefix to “realm” while invoking isAuthorised method. If the role is realm then the lookup happens in global roles list.

OAuth2Auth oauth2 = KeycloakAuth.create(vertx, OAuth2FlowType.PASSWORD, keycloakJson);
OAuth2AuthHandler oauth2Handler = (OAuth2AuthHandler) OAuth2AuthHandler.create(oauth2, "http://localhost:2222"); // (1)
Router router = Router.router(vertx);
router.route("/account/*").handler(ResponseContentTypeHandler.create());
router.route("/account/*").handler(oauth2Handler); // (2)
router.route(HttpMethod.POST, "/account").handler(BodyHandler.create());
router.route(HttpMethod.POST, "/login").handler(BodyHandler.create());
oauth2Handler.setupCallback(router.get("/callback"));
router.get("/account/:id").produces("application/json").handler(rc -> {
   rc.user().isAuthorised("realm:view-account", authRes -> { // (3)
      LOGGER.info("Auth: {}", authRes.result());
      if (authRes.result() == Boolean.TRUE) {
         repository.findById(rc.request().getParam("id"), res -> {
         Account account = res.result();
         LOGGER.info("Found: {}", account);
         rc.response().end(account.toString());
      });
   } else {
      rc.response().setStatusCode(HttpResponseStatus.UNAUTHORIZED.code()).end();
   }
});
});
router.post("/account").produces("application/json").handler(rc -> {
   rc.user().isAuthorised("realm:modify-account", authRes -> { // (4)
   LOGGER.info("Auth: {}", authRes.result());
   if (authRes.result() == Boolean.TRUE) {
      Account a = Json.decodeValue(rc.getBodyAsString(), Account.class);
      repository.save(a, res -> {
         Account account = res.result();
         LOGGER.info("Created: {}", account);
         rc.response().end(account.toString());
      });
   } else {
      rc.response().setStatusCode(HttpResponseStatus.UNAUTHORIZED.code()).end();
   }
});
});

Testing

I created a JUnit test case to check if OAuth2 authentication works fine. Vert.x provides a library that can be used for testing. It is specially designed to work well with asynchronous code. Include the following dependency to your pom.xml.

<dependency>
   <groupId>io.vertx</groupId>
   <artifactId>vertx-unit</artifactId>
   <version>${vertx.version}</version>
   <scope>test</scope>
</dependency>

Then annotate your JUnit test class with @RunWith(VertxUnitRunner.class). Before running our test method we should deploy verticles. Verticle with REST API is deployed on port 2222.

Vertx vertx;

@Before
public void before(TestContext context) throws IOException {
   vertx = Vertx.vertx();
   vertx.deployVerticle(MongoVerticle.class.getName(), context.asyncAssertSuccess());
   DeploymentOptions options = new DeploymentOptions().setConfig(new JsonObject().put("http.port", 2222));
   vertx.deployVerticle(AccountServer.class.getName(), options, context.asyncAssertSuccess());
}

Here’s a JUnit test method. We use WebClient for calling HTTP methods and Vert.x-Unit Async for complete test case on asynchronous calls finishes (3). First, we are calling POST \login method to retrieve access token from Keycloak (1). Then we are calling one of API method and setting Authorization header with access token string retrieved from POST \login method (2). During test case execution verticle with MongoDB (MongoVerticle) and API definition (AccountServer) are deployed and started, but you need to start manually MongoDB database, Consul and Keycloak. I suggest running it with Docker.

@Test
public void testAuth(TestContext context) {
   Async async = context.async();
   WebClient client = WebClient.create(vertx);
   User u = new User("piotr.minkowski", "Piot_123", "modify-account view-account");
   client.post(2222, "localhost", "/login").sendJson(u, ar -> { // (1)
      LOGGER.info("Response code: {}", ar.result().statusCode());
      LOGGER.info("Response: {}", ar.result().bodyAsString());
      if (ar.result().statusCode() == 200) {
         User user = ar.result().bodyAsJson(User.class);
         client.get(2222, "localhost", "/account").putHeader("Authorization", "Bearer " + user.getAccessToken()).send(r -> { // (2)
            LOGGER.info("GET result: {}", r.result().bodyAsString());
            async.complete(); // (3)
         });
      } else {
         async.complete();
      }
   });
}

Final Thoughts

To be honest I have never dealt with Vert.x before the start of work on a series of articles published on my blog. From those couple of days spent on that toolkit recognition I’ll definitely recommend using it when working on REST APIs. Vert.x provides the smart implementation for security with OAuth2. Additionally, you can use it in combination with a solution like Keycloak, which is used for identity and access management. As usual, there are also some drawbacks. I had a problem with understanding how the authorities exactly work in Vert.x. When I created a role inside Keycloak client it didn’t work in my application. Only the global realm role worked fine. However, those problems do not overshadow Vert.x advantages.

The post Building Secure APIs with Vert.x and OAuth2 appeared first on Piotr's TechBlog.

]]>
https://piotrminkowski.com/2017/09/15/building-secure-apis-with-vert-x-and-oauth2/feed/ 0 6034