📒
openid-connect-workshop
  • Introduction
  • Introduction
    • Requirements and Setup
    • Sample Application Architecture
  • Intro Labs
    • Authorization Grant Flows in Action
    • Authorization Code Grant Demo
    • GitHub Client
  • Hands-On Labs
    • Resource Server
    • Client (Authorization Code Flow)
    • Client (Client Credentials Flow)
    • Testing JWT Auth&Authz
    • JWT Testing Server
    • SPA Client (Authz Code with PKCE)
  • Bonus Labs​
    • Multi-Tenant Resource Server
    • Micronaut
    • Quarkus
    • Keycloak Testcontainers
Powered by GitBook
On this page
  • Lab Contents
  • Learning Targets
  • Folder Contents
  • Start the Lab
  • Step 1: Resource server with static token validation
  • Step 2: Run JWT generator web application
  • Step 3: Run and test static resource server
  • Step 4: Use new approach for JWT based authorization tests

Was this helpful?

  1. Hands-On Labs

JWT Testing Server

PreviousTesting JWT Auth&AuthzNextSPA Client (Authz Code with PKCE)

Last updated 4 years ago

Was this helpful?

Creating a testing environment is useful to perform

  • Performance & Load Tests

  • Security Tests

  • Other User Acceptance tests

  • ...

By using OAuth2/OIDC you have an additional external component in place that is required to run your clients and server applications.

To support testing environments without having external dependencies you have several possibilities:

  1. Spring Integration Tests for JWT (see last lab)

  2. Test using your own self-signed JWT tokens

  3. Test using the library using a dockerized Keycloak instance

Option 3 is a bit out of focus of this workshop, so we will go for option 2 as part of this workshop.

Lab Contents

Learning Targets

This is quite helpful in testing environments, e.g. doing load/performance testing and preventing from load testing the identity server as well.

This lab is actually split into three steps:

  1. Look into a resource server with static public key to verify JWT tokens

  2. Generate custom JWT tokens for different user identities to be used at the resource server of step 1

  3. Make requests to the resource server of step 1 with generated JWT from step 2

Folder Contents

In the lab 5 folder you find 3 applications:

  • library-server-static-complete: This application is the complete static resource server

  • jwt-generator: This application is the JWT generator to generate custom JWT tokens

Start the Lab

In this lab you will not really implement anything yourself, but you will see how to use such static resource server with custom generated JWt tokens. So let's start.

Step 1: Resource server with static token validation

As we will now locally validate the incoming JWT access tokens using a static public key we do not need the discovery entries (especially the JWKS uri) anymore.

You can see the changes in application.yml, here no issuer uri property is required anymore. Instead, we specify a location reference to a file containing a public key to verify JWT tokens.

This looks like this:

spring:
  jpa:
    open-in-view: false
  jackson:
    date-format: com.fasterxml.jackson.databind.util.StdDateFormat
    default-property-inclusion: non_null
  security:
    oauth2:
      resourceserver:
        jwt:
          publicKeyLocation: classpath:library_server.pub

Now we have to use this public key to configure the JwtDecoder to use this for validating JWT tokens instead of contacting keycloak.

This requires a small change in the class com.example.library.server.config.WebSecurityConfiguration:

Open the class com.example.library.server.config.WebSecurityConfiguration and look at the changes:

package com.example.library.server.config;

import com.example.library.server.security.AudienceValidator;
import com.example.library.server.security.LibraryUserDetailsService;
import com.example.library.server.security.LibraryUserJwtAuthenticationConverter;
import com.example.library.server.security.LibraryUserRolesJwtAuthenticationConverter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator;
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.JwtValidators;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;

import java.security.interfaces.RSAPublicKey;

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

  private final LibraryUserDetailsService libraryUserDetailsService;

  @Value("${spring.security.oauth2.resourceserver.jwt.publicKeyLocation}")
  private RSAPublicKey key;

  public WebSecurityConfiguration(LibraryUserDetailsService libraryUserDetailsService) {
    this.libraryUserDetailsService = libraryUserDetailsService;
  }

  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http.sessionManagement()
        .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
        .and()
        .csrf()
        .disable()
        .authorizeRequests()
        .anyRequest()
        .fullyAuthenticated()
        .and()
        .oauth2ResourceServer()
        .jwt()
        .jwtAuthenticationConverter(libraryUserJwtAuthenticationConverter());
  }

  @Bean
  JwtDecoder jwtDecoder() {
    NimbusJwtDecoder jwtDecoder =
            NimbusJwtDecoder.withPublicKey(this.key).build();

    OAuth2TokenValidator<Jwt> audienceValidator = new AudienceValidator();
    OAuth2TokenValidator<Jwt> withIssuer = JwtValidators.createDefaultWithIssuer("test_issuer");
    OAuth2TokenValidator<Jwt> withAudience =
        new DelegatingOAuth2TokenValidator<>(withIssuer, audienceValidator);

    jwtDecoder.setJwtValidator(withAudience);

    return jwtDecoder;
  }

  @Bean
  LibraryUserJwtAuthenticationConverter libraryUserJwtAuthenticationConverter() {
    return new LibraryUserJwtAuthenticationConverter(libraryUserDetailsService);
  }
}
@Value("${spring.security.oauth2.resourceserver.jwt.publicKeyLocation}")
private RSAPublicKey key;

...

NimbusJwtDecoder jwtDecoder =
            NimbusJwtDecoder.withPublicKey(this.key).build();

Here we use the public key (using RSA crypto algorithm) we read from the publicKeyLocation and create a NimbusJwtDecoder using this public key instead of configuring a JwtDecoder from issuer uri.

With this configuration in place we have already a working resource server that can handle JWt access tokens transmitted via http bearer token header. Spring Security also validates by default:

  • the JWT signature against the given static public key

  • the JWT iss claim against the configured issuer uri

  • that the JWT is not expired, if the JWT contains such entry

Step 2: Run JWT generator web application

Please navigate your Java IDE to the lab4/jwt-generator project. Then start the application by running the class com.example.jwt.generator.Lab5JwtGeneratorApplication.

Then you should see a screen like the following one.

To generate an JWT access token with the correct user identity and role information please fill the shown form with one of the following users and roles:

Username

Email

Role

bwayne

bruce.wayne@example.com

library_user

bbanner

bruce.banner@example.com

library_user

pparker

peter.parker@example.com

library_curator

ckent

clark.kent@example.com

library_admin

After filling the form click on the button Generate JWT then you should get another web page with the generate access token. This should look like this one.

To continue with this lab copy the contents of the JWT and use this JWT as access token to make a request to the resource server in the next step.

Step 3: Run and test static resource server

Please navigate your Java IDE to the lab5/library-server-static-complete project and at first explore this project a bit. Then start the application by running the class com.example.library.server.Lab5CompleteStaticLibraryServerApplication.

To do this we will need to run the copied access token from the JWT generator web application in the previous step.

To make a request for a list of users we have to specify the access token as part of a Authorization header of type Bearer like this:

httpie:

http localhost:9091/library-server/users \
'Authorization: Bearer [access_token]'

curl:

curl -H 'Authorization: Bearer [access_token]' \
-v http://localhost:9091/library-server/users | jq

You have to replace [access_token] with the one you have obtained from the JWt generator application.

If you scroll down a bit on the right hand side then you will see the following block (depending on which user you have specified when generating a JWT):

{
  "scope": "library_admin email profile",
  "email_verified": true,
  "name": "Clark Kent",
  "groups": [
    "library_admin"
  ],
  "preferred_username": "ckent",
  "given_name": "Clark",
  "family_name": "Kent",
  "email": "clark.kent@example.com"
}

As you can see our user has the scopes library_admin, email and profile. These scopes are now mapped to the Spring Security authorities SCOPE_library_admin, SCOPE_email and SCOPE_profile.

This request should succeed with an '200' OK status and return a list of users.

Step 4: Use new approach for JWT based authorization tests

As of version 5.2 of spring security new support for JWT based authorization tests is provided.

To see the new approach please have a look into the test class com.example.library.server.api.BookApiJwtAuthorizationTests.

Here you see that you may configure either default or customized JWT tokens to test different authorization scenarios.

...
@Test
  @DisplayName("get list of books")
  void verifyGetBooks() throws Exception {

    this.mockMvc
        .perform(get("/books").with(jwt()))
        .andExpect(status().isOk());
  }

  @Test
  @DisplayName("get single book")
  void verifyGetBook() throws Exception {

    Jwt jwt = Jwt.withTokenValue("token")
            .header("alg", "none")
            .claim("sub", "bwanye")
            .claim("groups", new String[] {"library_user"}).build();

    this.mockMvc
        .perform(
            get("/books/{bookId}", DataInitializer.BOOK_CLEAN_CODE_IDENTIFIER)
                .with(jwt(jwt)))
        .andExpect(status().isOk());
  }

  @Test
  @DisplayName("delete a book")
  void verifyDeleteBook() throws Exception {
    this.mockMvc
        .perform(
            delete("/books/{bookId}", DataInitializer.BOOK_DEVOPS_IDENTIFIER)
                .with(jwt().authorities(new SimpleGrantedAuthority("ROLE_LIBRARY_CURATOR"))))
        .andExpect(status().isNoContent());
  }
...

This concludes the Lab 5.

The identity provider is not required anymore for this lab.

In this fourth lab you will see how you can configure the resource server from with a custom static private/public key pair and create an application to generate your own JWT tokens using the corresponding self-signing private key.

Now, let's start with step 1 of this lab. Here we will have a look into the required changes we need compared to the resource server of to support static public keys for token signature validation.

In we have seen how Spring security 5 uses the specification to completely configure the resource server to use our keycloak instance.

This configuration above looks like the one as in with one important change:

After starting navigate your browser to .

Same as in we require bearer tokens in JWT format to authenticate at our resource server.

Navigate your web browser to and paste your access token into the Encoded text field.

Details on JWT testing support can be found in the corresponding section.

Keycloak
Lab 1
Lab 1
Lab 1
OpenID Connect Discovery
Lab 1
localhost:9093
Lab 1
jwt.io
spring security reference documentation
TestContainers
Learning Targets
Folder Contents
Learning Targets
Hands-On: Testing Environment for OIDC/JWT
Step 1: Implement a resource server with static public key
Step 2: Generate custom JWT with the JWT generator app
Step 3: Run and test basic resource server
Step 4: Use new JWT based test approach
JWT Generator
JWT Generator Result
JWT IO
JWT IO Decoded