Course – LSS – NPI (cat=Security/Spring Security)
announcement - icon

I just announced the new Learn Spring Security course, including the full material focused on the new OAuth2 stack in Spring Security:

>> CHECK OUT THE COURSE

1. Overview

In this tutorial, we’ll discuss how to get our Spring Security OAuth2 implementation to make use of JSON Web Tokens.

We’re also continuing to build on the Spring REST API + OAuth2 + Angular article in this OAuth series.

Further reading:

Logout in an OAuth Secured Application

A practical deep-dive into how to implement logout in a Spring Security OAuth2 application with JWT.

OAuth2 Remember Me with Refresh Token (using the Spring Security OAuth legacy stack)

Learn how to implement remember-me functionality with an Angular frontend, for an application secured with Spring Security OAuth.

OAuth2 for a Spring REST API - Handle the Refresh Token in Angular

Have a look at how to refresh a token using the Spring Security 5 OAuth stack and leveraging a Zuul proxy.

2. The OAuth2 Authorization Server

Previously, the Spring Security OAuth stack offered the possibility of setting up an Authorization Server as a Spring Application. We then had to configure it to use JwtTokenStore so that we could use JWT tokens.

However, the OAuth stack has been deprecated by Spring and now we’ll be using Keycloak as our Authorization Server.

So this time, we’ll set up our Authorization Server as an embedded Keycloak server in a Spring Boot app. It issues JWT tokens by default, so there is no need for any other configuration in this regard.

3. Resource Server

Now let’s take a look at how to configure our Resource Server to use JWT.

We’ll do this in an application.yml file:

server: 
  port: 8081
  servlet: 
    context-path: /resource-server

spring:
  jpa:
    defer-datasource-initialization: true
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: http://localhost:8083/auth/realms/baeldung
          jwk-set-uri: http://localhost:8083/auth/realms/baeldung/protocol/openid-connect/certs

JWTs include all the information within the Token, so the Resource Server needs to verify the Token’s signature to make sure the data has not been modified. The jwk-set-uri property contains the public key that the server can use for this purpose.

The issuer-uri property points to the base Authorization Server URI, which can also be used to verify the iss claim as an added security measure.

Additionally, if the jwk-set-uri property is not set, the Resource Server will attempt to use the issuer-uri to determine the location of this key from the Authorization Server metadata endpoint.

It is important to note, adding the issuer-uri property mandates that we should have the Authorization Server running before we can start the Resource Server application.

Now let’s see how we can configure JWT support using Java configuration: 

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.cors()
            .and()
              .authorizeRequests()
                .antMatchers(HttpMethod.GET, "/user/info", "/api/foos/**")
                  .hasAuthority("SCOPE_read")
                .antMatchers(HttpMethod.POST, "/api/foos")
                  .hasAuthority("SCOPE_write")
                .anyRequest()
                  .authenticated()
            .and()
              .oauth2ResourceServer()
                .jwt();
    }
}

Here we are overriding the default Http Security configuration; we need to specify explicitly that we want this to behave as a Resource Server and that we’ll be using JWT formatted Access Tokens using the methods oauth2ResourceServer() and jwt(), respectively.

The above JWT configuration is what the default Spring Boot instance is providing us with. This can also be customized as we’ll see shortly.

4. Custom Claims in the Token

Now let’s set up some infrastructure to be able to add a few custom claims in the Access Token returned by the Authorization Server. The standard claims provided by the framework are all well and good, but most of the time we’ll need some extra information in the token to utilize on the Client side.

Let’s take an example of a custom claim, organization, that will contain the name of a given user’s organization.

4.1. Authorization Server Configuration

For this we need to add a couple of configurations to our realm definition file, baeldung-realm.json:

  • Add an attribute organization to our user [email protected]:
    "attributes" : {
      "organization" : "baeldung"
    },
  • Add a protocolMapper called organization to the jwtClient configuration:
    "protocolMappers": [{
      "id": "06e5fc8f-3553-4c75-aef4-5a4d7bb6c0d1",
      "name": "organization",
      "protocol": "openid-connect",
      "protocolMapper": "oidc-usermodel-attribute-mapper",
      "consentRequired": false,
      "config": {
        "userinfo.token.claim": "true",
        "user.attribute": "organization",
        "id.token.claim": "true",
        "access.token.claim": "true",
        "claim.name": "organization",
        "jsonType.label": "String"
      }
    }],

For a standalone Keycloak setup, this can also be done using the Admin console. 

It’s important to remember that the JSON configuration above is specific to Keycloak, and can differ for other OAuth servers.

With this new configuration up and running, we’ll get an extra attribute, organization = baeldung, in the token payload for [email protected]:

{
  jti: "989ce5b7-50b9-4cc6-bc71-8f04a639461e"
  exp: 1585242462
  nbf: 0
  iat: 1585242162
  iss: "http://localhost:8083/auth/realms/baeldung"
  sub: "a5461470-33eb-4b2d-82d4-b0484e96ad7f"
  typ: "Bearer"
  azp: "jwtClient"
  auth_time: 1585242162
  session_state: "384ca5cc-8342-429a-879c-c15329820006"
  acr: "1"
  scope: "profile write read"
  organization: "baeldung"
  preferred_username: "[email protected]"
}

4.2. Use the Access Token in the Angular Client

Next we’ll want to make use of the Token information in our Angular Client application. We’ll use the angular2-jwt library for that.

We’ll make use of the organization claim in our AppService, and add a function getOrganization:

getOrganization(){
  var token = Cookie.get("access_token");
  var payload = this.jwtHelper.decodeToken(token);
  this.organization = payload.organization; 
  return this.organization;
}

This function makes use of JwtHelperService from the angular2-jwt library to decode the Access Token and get our custom claim. Now all we need to do is display it in our AppComponent:

@Component({
  selector: 'app-root',
  template: `<nav class="navbar navbar-default">
  <div class="container-fluid">
    <div class="navbar-header">
      <a class="navbar-brand" href="/">Spring Security Oauth - Authorization Code</a>
    </div>
  </div>
  <div class="navbar-brand">
    <p>{{organization}}</p>
  </div>
</nav>
<router-outlet></router-outlet>`
})

export class AppComponent implements OnInit {
  public organization = "";
  constructor(private service: AppService) { }  
   
  ngOnInit() {  
    this.organization = this.service.getOrganization();
  }  
}

5. Access Extra Claims in the Resource Server

But how can we access that information over on the Resource Server side?

5.1. Access Authentication Server Claims

That’s really simple, we just need to extract it from the org.springframework.security.oauth2.jwt.Jwt‘s AuthenticationPrincipal, as we would do for any other attribute in UserInfoController:

@GetMapping("/user/info")
public Map<String, Object> getUserInfo(@AuthenticationPrincipal Jwt principal) {
    Map<String, String> map = new Hashtable<String, String>();
    map.put("user_name", principal.getClaimAsString("preferred_username"));
    map.put("organization", principal.getClaimAsString("organization"));
    return Collections.unmodifiableMap(map);
}

5.2. Configuration to Add/Remove/Rename Claims

Now what if we want to add more claims on the Resource Server side? Or remove or rename some?

Let’s say we want to modify the organization claim coming in from the Authentication Server to get the value in uppercase. However, if the claim is not present on a user, we need to set its value as unknown.

To achieve this, we’ll have to add a class that implements the Converter interface and uses MappedJwtClaimSetConverter to convert claims:

public class OrganizationSubClaimAdapter implements 
  Converter<Map<String, Object>, Map<String, Object>> {
    
    private final MappedJwtClaimSetConverter delegate = 
      MappedJwtClaimSetConverter.withDefaults(Collections.emptyMap());

    public Map<String, Object> convert(Map<String, Object> claims) {
        Map<String, Object> convertedClaims = this.delegate.convert(claims);
        String organization = convertedClaims.get("organization") != null ? 
          (String) convertedClaims.get("organization") : "unknown";
        
        convertedClaims.put("organization", organization.toUpperCase());

        return convertedClaims;
    }
}

Then, in our SecurityConfig class, we need to add our own JwtDecoder instance to override the one provided by Spring Boot and set our OrganizationSubClaimAdapter as its claims converter:

@Bean
public JwtDecoder jwtDecoder(OAuth2ResourceServerProperties properties) {
    NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withJwkSetUri(
      properties.getJwt().getJwkSetUri()).build();
    
    jwtDecoder.setClaimSetConverter(new OrganizationSubClaimAdapter());
    return jwtDecoder;
}

Now when we hit our /user/info API for the user [email protected], we’ll get the organization as UNKNOWN.

Note that overriding the default JwtDecoder bean configured by Spring Boot should be done carefully to ensure all the necessary configuration is still included.

6. Loading Keys From a Java Keystore

In our previous configuration, we used the Authorization Server’s default public key to verify our token’s integrity.

We can also use a keypair and certificate stored in a Java Keystore file to do the signing process.

6.1. Generate JKS Java KeyStore File

Let’s first generate the keys, and more specifically a .jks file, using the command line tool keytool:

keytool -genkeypair -alias mytest 
                    -keyalg RSA 
                    -keypass mypass 
                    -keystore mytest.jks 
                    -storepass mypass

The command will generate a file called mytest.jks which contains our keys, the Public and Private keys.

Also make sure keypass and storepass are the same.

6.2. Export Public Key

Next we need to export our Public key from generated JKS. We can use the following command to do so:

keytool -list -rfc --keystore mytest.jks | openssl x509 -inform pem -pubkey

A sample response will look like this:

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAgIK2Wt4x2EtDl41C7vfp
OsMquZMyOyteO2RsVeMLF/hXIeYvicKr0SQzVkodHEBCMiGXQDz5prijTq3RHPy2
/5WJBCYq7yHgTLvspMy6sivXN7NdYE7I5pXo/KHk4nz+Fa6P3L8+L90E/3qwf6j3
DKWnAgJFRY8AbSYXt1d5ELiIG1/gEqzC0fZmNhhfrBtxwWXrlpUDT0Kfvf0QVmPR
xxCLXT+tEe1seWGEqeOLL5vXRLqmzZcBe1RZ9kQQm43+a9Qn5icSRnDfTAesQ3Cr
lAWJKl2kcWU1HwJqw+dZRSZ1X4kEXNMyzPdPBbGmU6MHdhpywI7SKZT7mX4BDnUK
eQIDAQAB
-----END PUBLIC KEY-----
-----BEGIN CERTIFICATE-----
MIIDCzCCAfOgAwIBAgIEGtZIUzANBgkqhkiG9w0BAQsFADA2MQswCQYDVQQGEwJ1
czELMAkGA1UECBMCY2ExCzAJBgNVBAcTAmxhMQ0wCwYDVQQDEwR0ZXN0MB4XDTE2
MDMxNTA4MTAzMFoXDTE2MDYxMzA4MTAzMFowNjELMAkGA1UEBhMCdXMxCzAJBgNV
BAgTAmNhMQswCQYDVQQHEwJsYTENMAsGA1UEAxMEdGVzdDCCASIwDQYJKoZIhvcN
AQEBBQADggEPADCCAQoCggEBAICCtlreMdhLQ5eNQu736TrDKrmTMjsrXjtkbFXj
Cxf4VyHmL4nCq9EkM1ZKHRxAQjIhl0A8+aa4o06t0Rz8tv+ViQQmKu8h4Ey77KTM
urIr1zezXWBOyOaV6Pyh5OJ8/hWuj9y/Pi/dBP96sH+o9wylpwICRUWPAG0mF7dX
eRC4iBtf4BKswtH2ZjYYX6wbccFl65aVA09Cn739EFZj0ccQi10/rRHtbHlhhKnj
iy+b10S6ps2XAXtUWfZEEJuN/mvUJ+YnEkZw30wHrENwq5QFiSpdpHFlNR8CasPn
WUUmdV+JBFzTMsz3TwWxplOjB3YacsCO0imU+5l+AQ51CnkCAwEAAaMhMB8wHQYD
VR0OBBYEFOGefUBGquEX9Ujak34PyRskHk+WMA0GCSqGSIb3DQEBCwUAA4IBAQB3
1eLfNeq45yO1cXNl0C1IQLknP2WXg89AHEbKkUOA1ZKTOizNYJIHW5MYJU/zScu0
yBobhTDe5hDTsATMa9sN5CPOaLJwzpWV/ZC6WyhAWTfljzZC6d2rL3QYrSIRxmsp
/J1Vq9WkesQdShnEGy7GgRgJn4A8CKecHSzqyzXulQ7Zah6GoEUD+vjb+BheP4aN
hiYY1OuXD+HsdKeQqS+7eM5U7WW6dz2Q8mtFJ5qAxjY75T0pPrHwZMlJUhUZ+Q2V
FfweJEaoNB9w9McPe1cAiE+oeejZ0jq0el3/dJsx3rlVqZN+lMhRJJeVHFyeb3XF
lLFCUGhA7hxn2xf3x1JW
-----END CERTIFICATE-----

6.3. Maven Configuration

We don’t want the JKS file to be picked up by the maven filtering process, so we’ll make sure to exclude it in the pom.xml:

<build>
    <resources>
        <resource>
            <directory>src/main/resources</directory>
            <filtering>true</filtering>
            <excludes>
                <exclude>*.jks</exclude>
            </excludes>
        </resource>
    </resources>
</build>

If we’re using Spring Boot, we need to make sure that our JKS file is added to the application classpath via the Spring Boot Maven Plugin addResources:

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>
                <addResources>true</addResources>
            </configuration>
        </plugin>
    </plugins>
</build>

6.4. Authorization Server

Now we will configure Keycloak to use our Keypair from mytest.jks by adding it to the realm definition JSON file’s KeyProvider section as follows:

{
  "id": "59412b8d-aad8-4ab8-84ec-e546900fc124",
  "name": "java-keystore",
  "providerId": "java-keystore",
  "subComponents": {},
  "config": {
    "keystorePassword": [ "mypass" ],
    "keyAlias": [ "mytest" ],
    "keyPassword": [ "mypass" ],
    "active": [ "true" ],
    "keystore": [
            "src/main/resources/mytest.jks"
          ],
    "priority": [ "101" ],
    "enabled": [ "true" ],
    "algorithm": [ "RS256" ]
  }
},

Here we have set the priority to 101, greater than any other Keypair for our Authorization Server, and set active to true. This is done to ensure that our Resource Server will pick this particular Keypair from the jwk-set-uri property we specified earlier.

Again, this configuration is specific to Keycloak and may differ for other OAuth Server implementations.

7. Conclusion

In this brief article we focused on setting up our Spring Security OAuth2 project to use JSON Web Tokens.

The full implementation of this article can be found over on GitHub.

Course – LSS (cat=Security/Spring Security)

I just announced the new Learn Spring Security course, including the full material focused on the new OAuth2 stack in Spring Security:

>> CHECK OUT THE COURSE
res – Security (video) (cat=Security/Spring Security)
Comments are closed on this article!