Security Top

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

>> 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 top of 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:
  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.

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

Importantly, 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. So 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

Let's now 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. 

Additionally, 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. Additionally, if the claim is not present on a user, we need to set its value as unknown.

To achieve this, first, 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;
    }
}

Second, 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 customDecoder(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

Next, 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 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 would 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 quick article we focused on setting up our Spring Security OAuth2 project to use JSON Web Tokens.

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

Security bottom

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

>> CHECK OUT THE COURSE
35 Comments
Oldest
Newest
Inline Feedbacks
View all comments
Sandro Augusto Oliveira
Sandro Augusto Oliveira
4 years ago

Hi,
Thanks for the nice tutorial!
I’ve downloaded the code from Github but I couldn’t start Auth Server, I’ve got the following exception:
Caused by: java.io.FileNotFoundException: class path resource [mytest.jks] cannot be opened because it does not exist.

Do you know what it could be?

Tks in advance!
BR
Sandro

Eugen Paraschiv
4 years ago

Hey Sandro – yes, I can definitely replicate the problem, looking into it.
Cheers,
Eugen.

Sandro Augusto Oliveira
Sandro Augusto Oliveira
4 years ago

Hi Eugen,
Ok :). I’ve also faced an error on UI Password starting:
Exception in thread “main” java.lang.NoSuchMethodError: org.springframework.boot.builder.SpringApplicationBuilder.showBanner(Z)Lorg/springframework/boot/builder/SpringApplicationBuilder;
BR
Sandro

Eugen Paraschiv
4 years ago

Both should be fixed – you can now have a look. Hope that helps. Cheers,
Eugen.

DL
DL
4 years ago

src/main/resources
true

*.jks

if you remove this .. file not found exception is resolved .. it states that do no read any .jks file hence compilers skips the file.

wisely liu
wisely liu
4 years ago

IOUtils.toString(resource.getInputStream()) gives error method toString in class Object cannot be applied to given types;
required: no arguments
found: InputStream
reason: actual and formal argument lists differ in length

what happened?

Dennis
Dennis
3 years ago
Reply to  wisely liu

Hi, probably no longer needed, but when going over this tutorial I came across the same problem. I solved it by using an older implementation of commons-io:

commons-io
commons-io
2.4

For some reason, the IOUtils is not found in 2.5, and I was given IOUtils from tomcat embedded as alternative.

dziadeusz
dziadeusz
4 years ago

Thanks Eugen, you saved my day just as always. I recommend the Spring Security Master Class to everyone as well.

dziadeusz
dziadeusz
4 years ago
Reply to  dziadeusz

How about using these by the way?
encrypt.key-store.location=
encrypt.key-store.password=
encrypt.key-store.alias=
encrypt.key-store.secret=

Eugen Paraschiv
4 years ago
Reply to  dziadeusz

Spring Cloud? Sure, that’s a good option to handle configuration. There are a couple of writeups about Spring Cloud here on the site, and a few more coming.
Glad you’re enjoying the site btw.
Cheers,
Eugen.

VERGiL
VERGiL
3 years ago

Is there any easy was to access jwt token custom claims from a spring rest controller (resource server)?

Eugen Paraschiv
3 years ago
Reply to  VERGiL

Hey Vergil,
While I haven’t tried to do that explicitly, you should be able to do it, yes. Keep in mind that you can inject the raw request in the controller, so you can certainly get to the token and implicitly the claims. Might be interesting to explore in an article though.
Cheers,
Eugen.

Shubham Gupta
Shubham Gupta
3 years ago

Hi Eugen I followed your tutorial and wanted to implement JWT Token over already exiting SpringBoot+SpringSecurity+Spring OAuth2 (http://pastebin.com/hufRRHMZ). Here is the normal response which I used to get:(http://pastebin.com/ibG99BbU). I have tried to follow the steps but then my auth server is responding with 404. What I did: I removed the @EnableAuthorization from SecurityConfig1 and then added second config file here is the link to my second config file(http://pastebin.com/pPjYd3ud) and I removed from SecurityConfig and then with the same request I am getting 404. Update 2: Hi Finally, I was able to resolve it. I provided @EnableAuthorization in SecurityConfig2 and added.… Read more »

Comments are closed on this article!