Learn Spring Security OAuth: The Certification Class - Part 2

Lesson 3: The New OAuth2 Client Support - part 2

2.2. The WebClient Bean

The goal of this Client application is to be able to consume the Resource Server on behalf of the user and so, what we’ll need here is a simple HTTP client.

Instead of using any plain HTTP client and having to deal with sending the token manually we’ll make use of the framework support by defining a WebClient instance.

We’ll need to add some additional dependencies for this:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webflux</artifactId>
</dependency>
<dependency>
    <groupId>io.projectreactor.netty</groupId>
    <artifactId>reactor-netty</artifactId>
</dependency>

Even though we’re working in a servlet environment and we’ll be using the Tomcat embedded server, we still need to include this last Netty dependency, because the WebClient is part of the reactive support in Spring and so we need that on the classpath to provide an HTTP Connector the WebClient can use.

As we can see, the WebClient entity is part of the Reactive stack but it’s still suitable to use it in a Servlet stack , especially since the old RestTemplate will be deprecated and all new features are focused in the WebClient.

Now we can define the WebClient bean.

We need to add a filter to the WebClient configuration to include the token we obtain when logging in as a Bearer token in the requests we make.

Spring provides such a filter out of the box; the ServletOAuth2AuthorizedClientExchangeFilterFunction . In order for this filter to work properly, it needs to access the registered and authorized OAuth Clients information.

We can create a OAuth2AuthorizedClientManager instance, or if we don’t need any advanced behavior related to this, we can simply delegate the definition of this manager to the filter itself by passing some components defined by Spring Boot:

  • ClientRegistrationRepository: a repository of the registered OAuth Clients
  • OAuth2AuthorizedClientRepository: stores the information of Clients that have already been authorized, like for example, the Access Token issued to it

We can simply obtain these beans from the context and use them in the filter constructor:

@Bean
WebClient webClient(ClientRegistrationRepository clientRegistrationRepository, OAuth2AuthorizedClientRepository authorizedClientRepository) {
    ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2 =
      new ServletOAuth2AuthorizedClientExchangeFilterFunction(clientRegistrationRepository, authorizedClientRepository);
      
    oauth2.setDefaultOAuth2AuthorizedClient(true);
    
    return WebClient.builder()
      .apply(oauth2.oauth2Configuration())
      .build();
}

Note that we’re using the setDefaultOAuth2AuthorizedClient directive to automatically detect the Client configuration to be used based on the Authentication object.

It’s important to know that with this:

  • we first need to be authenticated using OAuth2 to discover the client configuration to use
  • the Bearer token will be included automatically in every request we make with the WebClient

We should be cautious with this as the Access Token shouldn’t be sent just anywhere.

2.3. The Client Controller

Let’s now implement our own controller in the Client application, which will simply consume the Resource Server.

What’s interesting here is that even though the Resource Server is a REST API, the Client application can chose to expose the data however it needs to.

For example, we could also expose data as a REST API and have a more JS-centric front-end app here. Or, we could go with an entirely different style since the Client application is a fully independent app.

It can, for instance, go with a more traditional MVC-style implementation. So let’s do that here.

We’ll inject the WebClient into our Controller and use it to obtain the list of projects we need to populate the projects page:

@Controller
public class ProjectClientController {

    @Autowired
    private WebClient webClient;
    
    @Value("${resourceserver.api.project.url:http://localhost:8081/lsso-resource-server/api/projects/}")
    private String projectApiUrl;

    @GetMapping("/projects")
    public String getProjects(Model model) {
        List<ProjectModel> projects = this.webClient.get()
          .uri(projectApiUrl)
          .retrieve()
          .bodyToMono(new ParameterizedTypeReference<List<ProjectModel>>() {
          })
          .block();
        model.addAttribute("projects", projects);
        return "projects";
    }
    
    // ...
    
}

Naturally, in a Reactive stack we wouldn’t block the flow but since we’re using a traditional Servlet stack here, it’s ok to do it.

Also, if we weren’t using the OAuth2 Login feature in our app or if we hadn’t configured the WebClient to autodetect which client to use then we would need to specify it at this moment using the attributes directive:

this.webClient.get()
  .uri(projectApiUrl)
  .attributes(ServletOAuth2AuthorizedClientExchangeFilterFunction
    .clientRegistrationId("custom"))
  .retrieve()
  // ...

We can implement the create operation as well:

@PostMapping("/projects")
public String saveProject(ProjectModel project, Model model) {
    try {
        this.webClient.post()
          .uri(projectApiUrl)
          .bodyValue(project)
          .retrieve()
          .bodyToMono(Void.class)
          .block();
        return "redirect:/projects";
    } catch (final HttpServerErrorException e) {
        model.addAttribute("msg", e.getResponseBodyAsString());
        return "addproject";
    }
}

And there we have it, we’re all done. We can now run our application and browse http://localhost:8082/lsso-client to see it working.

When logging in, we can use the credentials john@test.com / 123, which have been configured in the Authorization Server.

Note that here we’re not prompted to confirm the scopes granted to the Client, since this is an optional step and depends on the Authorization Server configuration. In our example, the consent step is disabled.

Note on Client Logout : with this configuration, you might notice that after logging out from the application you can still access the OAuth2 protected pages without having to re-authenticate . The functionality is still working as expected; if you track the requests and responses in the browser, you’ll see that the application is actually sending a request to the Authorization Server for the user to authorize the Client app. But since you’re still logged in at the provider’s site for your browser and the permissions have been granted, it simply redirects you back transparently .

One other aspect that comes into play here, is the fact that we have configured only one ID provider . If there was at least one more provider to authenticate, you would get sent to an auto-generated page first to indicate which provider you want to use to log in. This would make it clear that the user is actually not authenticated, but it is also an unnecessary step if there is only one provider, so Spring skips it if possible.

Related to this, OpenID Connect provides a spec to allow logging out the user at the Provider from the Client, but this is out of the scope of this lesson. If you want to learn more about this, refer to the Resources section below.

3. Resources

Lesson 4: The New Resource Server Support - part 1

1. Goal

In this lesson, we’ll build a simple Resource Server using the new OAuth stack in Spring Security 5.

2. Lesson Notes

The relevant module you need to import when you’re starting with this lesson is: lsso-module2/resource-server-start.

If you want to have a look at the fully implemented lesson, as a reference, feel free to import: lsso-module2/resource-server-end.

2.1. Introduction

At the moment, Spring Security supports protecting endpoints using two forms of OAuth 2.0 Bearer Tokens:

  • Opaque Tokens
  • JWT

In the JWT form, the user information is included in the token. The Resource Server usually uses a public key to verify that the data hasn’t been tampered with.

Opaque Tokens, on the other hand, don’t have meaning by themselves. The Resource Server has to send them to the Authorization Server to be validated and optionally to retrieve user information.

Even though we’ll use JWTs in this lesson, we won’t go too deep into the topic, as the main focus of the lesson is to setup a Resource Server and analyze the features that it provides. We’ll look at both types of tokens in more detail in the next lessons.

2.2. Maven Dependencies

Let’s start by using Spring Boot features to easily configure the Resource Server with the help of application properties .

First, we need to add the following dependency to our pom.xml :slight_smile:

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

The spring-boot-starter-oauth2-resource-server artifact includes the spring-security-oauth2-resource-server one that, in its turn, contains the new Resource Server support.

This dependency also includes the core Spring Security libraries. Therefore, we can remove the base spring-boot-starter-security library from the dependencies.

2.3. Spring Boot Configuration

At this level, we have to define whether we’re going to use JWT or Opaque Tokens. In our case, we’ll set up the Resource Server to use JWTs.

We need to configure the Resource Server in such a way that it establishes the trust relation with the Authorization Server. This means that we need to make sure that the Resource Server can verify the JWTs signed by the Authorization Server .

In order to do that, it should be aware of the public key provided by the Authorization Server .

We can configure this in 2 different ways.

The first option is to simply specify the base Authorization Server URI by setting the issuer-uri property:

spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: http://localhost:8083/auth/realms/baeldung

In this configuration, we set the URI of the Authorization Server that issues the JWT. At a high-level, the Resource Server will attempt to determine the public key using the metadata that might be supplied by the Authorization Server.

A disadvantage of this method is that the Authorization Server needs to be running when the Resource Server starts up. Also, it needs to expose APIs that allow discovering the endpoint of the public keys.

The second option is to explicitly specify the endpoint containing the public keys ourselves using the property jwk-set-uri :slight_smile:

spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          jwk-set-uri: http://localhost:8083/auth/realms/baeldung/protocol/openid-connect/certs

Here, we’ll use both options because, even though we’re explicitly indicating the public key endpoint, it’s still a good practice to indicate the issuer URI as well. It’s used by the Spring Security framework as an extra security measure.

There is a third option that consists in specifying the location of a local file that contains the public key. We won’t cover this option because it’s neither a common nor convenient approach.

Lesson 4: The New Resource Server Support - part 2

2.4. Accessing Secured Resources

At this point, our application is completely functional so we can start it. By default, all the endpoints will be secured and we need to send an Access Token to access them.

Let’s obtain the token . In Postman, we have a couple of predefined requests to simulate the Authorization Code Flow that allow us to obtain an Access Token. This process is similar to the one we carry out in the Live Tests that you can find in the complete implementation of the lesson.

First of all, we should obtain the URL of the authorization endpoint from the login form , which we’ll then use to obtain an Authorization Code . We can do this by executing the request: GET - Extract Authorization Endpoint from the LSSO > The Basics of OAuth2 (New Stack) > The New Resource Server Support folder of our Postman collection.

Note that these requests are prepared in such a way that we actually don’t have to copy-paste anything because the values are automatically stored and used by Postman. Therefore, we can immediately execute the next request: POST - Request Authorization Code

In the request’s body, we’ll find the user’s credentials that are sent to the Authorization Server. Now we’re ready to obtain an Access Token by executing the request: POST - Request Access Token. If we inspect the request’s parameters, we’ll find the form parameters that we’ve received from the previous request.

Now that we have the Access Token, we can get the list of Projects that are stored on the Resource Server and hence should be secured.

To validate this, let’s first check whether we can succeed in retrieving this list without providing the token. Let’s execute the request GET - Request Projects. In the Authorization tab, we can see that the request contains no Bearer token. As a result, we get a response with the 401 Unauthorized error status.

Now, let’s add the Access Token to the request and execute it. We’ll see that the Resource Server now returns the list of Projects . So, we’ve just checked that the resource endpoint is in fact secured.

To sum up, we have a fully functional Resource Server by using just two application properties .

2.5. Java Configuration

We can also configure the Resource Server using Java configuration.

Let’s make the ResourceSecurityConfig class extend WebSecurityConfigurerAdapter and update the HttpSecurity configuration:

@Configuration
public class ResourceSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
       http.authorizeRequests()
              .anyRequest()
                .authenticated()
            .and()
              .oauth2ResourceServer()
                .jwt();
    }
}

Since we’re overriding the default configuration here, we need to specify explicitly that we want this to behave as a Resource Server using the oauth2ResourceServer DSL .

We can use this to configure exactly how our Resource Server will behave. For example, we can indicate we’ll be using JWT-formatted Access Tokens by adding the jwt() method.

Since we’re not indicating which specific JWT configurations should be used, the service will still rely on the properties we defined previously.

In future lessons, we’ll analyze how we can use other directives to pull off more advanced configurations.

3. Resources

Lesson 5: JWT Support (text-only)

1. Goal

In this lesson, we’ll learn about the different Resource Servce Configurations that Spring Security offers to use JWT (JSON Web Tokens).

2. Lesson Notes

The relevant module you need to import when you’re working with this lesson is: lsso-module2/jwt-support

This lesson only needs a single reference codebase, so there is no end version of the project.

2.1. Maven Dependencies

We need the spring-boot-starter-oauth2-resource-server library in our Resource Server.

This starter includes, among other libraries, the spring-security-oauth2-jose library that contains the JWT signing and encryption functionality.

2.2. Exploring Basic JWT Properties

In the Resource Server project, we have some basic configuration defined in the application.yml file for setting up JWT support.

Let’s understand one of the properties here which is jwk-set-uri.

With JWTs all the information is contained within the Token. So all the Resource Server really needs is a way of verifying the token’s signature to make sure the data has not been modified.

The jwk-set-uri we’re configuring here contains the public key that the server can use to verify the integrity of the token. More exactly, according to the specification, this is called the JSON Web Key or JWK for short.

Another property configured here is issuer-uri. This is used as an additional security measure to verify that the iss (issuer) claim contained in the JWTs corresponds with this value.

2.3. Overriding the Spring Boot Autoconfiguration

Spring Boot defines two beans to configure our service as a Resource Server: a JwtDecoder and a WebSecurityConfigurerAdapter bean.

The WebSecurityConfigurerAdapter class actually belongs to Spring Security. Spring Boot provides an implementation to define a default Security configuration that configures the service as a Resource Server.

Naturally, Spring Boot is configured to intelligently back off and avoid declaring the bean if we provide our own as we’re doing here.

With this we get programmatic access to all the configuration of the Resource Server via oauth2ResourceServer().

Next, we can do JWT configuration with DSL - jwt() :

.jwt().decoder(decoder)
    .jwtAuthenticationConverter(jwtAuthenticationConverter);

Here, we’ve used 2 different objects: a JwtDecoder which is in charge of decoding String tokens into validated instances of the Jwt class, and a Converter that maps this Jwt instance to an Authentication object.

This is a really useful part of the functionality as this is the place where we have the option to map the JWT to the Spring Security principal.

Now, Spring Security supplies a default converter JwtGrantedAuthoritiesConverter that maps the scopes to authorities prefixed with SCOPE_ . So, normally, we don’t need to configure the converter, unless we have special requirements on how to extract the authorities from the JWTs.

Regarding the decoder, though Spring Security doesn’t provide a default implementation, but, it does search for a bean of type JwtDecoder bean in the context.

That’s why it’s possible to avoid providing the decoder and simply define a bean of this type:

@Bean
public JwtDecoder customDecoder() {
}

And that’s where Spring Boot comes into play again providing this JwtDecoder bean for us . As we’re indeed using Boot here we can just get rid of this bean.

Some of the features this Boot default instance provides are:

  • defining a default token validator object that checks the iss claim - as we explained before - as well as the exp (expired) and the nbf (not before) claims to make sure the Token is valid in the corresponding time frame
  • defining a mapper that converts claims into Java types

This default decoding and authentication converting behavior is probably all we need in most of the cases.

But, since the JWT can contain any custom information that we might need to use in different ways, it’s not uncommon that we may have to tune the default behavior to suit those needs.

The Spring Security documentation offers pretty clear examples for handling common JWT scenarios like customizing the validations we execute on the Token, modifying the claims mapping process and obtaining custom authorities out of JWT claims.

2.4. Exploring Other Properties

We can also setup more advanced JWT-related properties in our configuration.

One is the jws-algorithm , in case we need to validate tokens that have been signed with a different algorithm than the default one which is RS256.

And the other is the public-key-location which can be used to load the public key from a local file instead of taking it from the JWK Set endpoint.

3. Resources

Lesson 1: Basic Authorization with OAuth2 (text-only)

1. Goal

In this lesson, we’ll learn about the basic mechanisms we have to control the access to our Resources when using OAuth2.

2. Lesson Notes

The relevant module you need to import when you’re starting with this lesson is: lsso-module3/basic-authorization-start

If you want have a look at the fully implemented lesson, as a reference, feel free to import: lsso-module3/basic-authorization-end

We’ll also need to import the Postman collection that contains all the REST calls that we need to run for this lesson.

2.1. Authorization in the Resource Server

Simply put, the Resource Server has to determine if the authorities granted by the Resource Owner to the Client are enough to access a particular resource.

The authorities are usually represented by the scopes granted by the Authorization Server.

This is, naturally, the approach taken by Spring and even though it can be customized, here we’ll stick to this basic behavior.

Let’s explain how this is handled under the hood by the Spring Security framework.

2.2. Authentication Object

Simply put, the security framework bases most of its functionality around the Authentication interface and its implementations.

As you might know, classes implementing this interface have to implement a method that retrieves a collection of authorities. In the case of OAuth then, the framework creates the Authentication object.

The Authentication object is created based on the Access Token , either from the token itself (in the case of JWT), or from the information provided by the introspection endpoint (for Opaque Tokens), creating the collection of authorities from the scopes.

2.3. Authorities

Authorities are usually represented as simple strings.

The framework adds the SCOPE_ prefix to these authorities by default , to differentiate them from other authorities that can be configured in the service.

The bottom line of all this is that we’ll end up with a regular Authentication object, just like the one we obtain when we use other more traditional authentication methods such as username/password authentication.

This means that the mechanisms we can use to control access are the same as the ones used on these other common scenarios, namely:

  • Authorizing HTTP Requests ( HttpServletRequest s) with the FilterSecurityInterceptor; in practice, this usually means using the WebSecurityConfigurerAdapter to define access rules for the HttpSecurity configuration
  • Using Expression-Based Access Control mainly in @Pre and @Post annotations like @PreAuthorize , @PreFilter , @PostAuthorize and @PostFilter

Let’s analyze some simple examples for these two approaches.

2.4. Authorizing HttpServletRequests

Let’s look at the security configuration for the Resource Server present in the class ResourceSecurityConfig :slight_smile:

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

We’ve set up the HttpSecurity configuration here by overriding the corresponding WebSecurityConfigurerAdapter method.

The authorizeRequests() method allows specifying the access rules for our endpoints.

So here, for example, we’re allowing access to the GET projects endpoint only if the Authentication instance contains the SCOPE_read authority.

This will be true if the Client was granted the read scope when requesting the Access Token.

2.5. Using Expression-Based Access Control

Now let’s have a look at how we can use expression-based access control.

In the same class ResourceSecurityConfig, we can see the service is already configured to process the @Pre and @Post annotations, as it contains the @EnableGlobalMethodSecurity annotation:

@EnableGlobalMethodSecurity(prePostEnabled = true)
@Configuration
public class ResourceSecurityConfig extends WebSecurityConfigurerAdapter {
    // ...
}

Next, let’s head to the ProjectController.

Here, we’ll use a common case example which is simply using the @PreAuthorize annotation to restrict access to a particular method.

For example, if we want to permit access to the findOne method here in the Controller only to Clients that have been granted the email scope, we can add the @PreAuthorize annotation, along with the hasAuthority rule with the expected scope, prefixed correspondingly:

@PreAuthorize("hasAuthority('SCOPE_email')")
@GetMapping(value = "/{id}")
public ProjectDto findOne(@PathVariable Long id) {
    // ...
}

It’s worth mentioning that here we’re using this annotation in the REST controller because, conceptually, it belongs to this layer.

But this can be used in any layer of our service; the request will be effectively executed until it invokes the annotated method and runs the proper validations.

Let’s see these configurations in action now; we’ll add a breakpoint in the BearerTokenAccessDeniedHandler#handle method and start the the Resource Server in debug mode.

We will also need the Authorization Server up and running, so let’s boot it as well.

2.6. Access Control in Action - No Scope

We will run the REST calls present in the Postman collection folder for this lesson.

We’ll first execute the necessary requests consecutively to obtain a new access token:

  • Extract Authorization Endpoint - extracts the authorization endpoint with scope equals to none
  • Request Authorization Code - gets the Authorization Code from the Authorization Server
  • Request Access Token - exchanges the Authorization Code retrieved above to get an Access Token

Here, we’ve obtained an Access Token, without specifying any scope.

Next, let’s execute the call to get the Projects.

Since we’ve configured this endpoint to require the read scope, the execution will stop at the breakpoint we set.

What happened is that the framework threw an AccessDeniedException because the Authentication naturally didn’t comply with the access rules and therefore, this AccessDeniedHandler is invoked.

As we step further in the same method, we can see that the method adds an informative WWW-Authenticate header as indicated in the Bearer Token Usage specs:

// ...
String wwwAuthenticate = computeWWWAuthenticateHeaderValue(parameters);
response.addHeader(HttpHeaders.WWW_AUTHENTICATE, wwwAuthenticate);

and transforms the exception into the corresponding 403 - Forbidden response:

// ...
response.setStatus(HttpStatus.FORBIDDEN.value());

As we step out from this breakpoint and return to Postman, we can see that we got 403 Forbidden response and a helpful message in the WWW-Authenticate header indicating that this error response is due to insufficient scopes since the request requires higher privileges than the ones provided by the access token.

Next, let’s see the same flow in action when requesting an Access Token with read scope.

2.7. Access Control in Action - read Scope

Let’s repeat the process of obtaining an Access Token, but this time we’ll request one with read scope.

And let’s trigger the " Get All Projects" endpoint again.

This time, we’re able to successfully retrieve the list of Projects because we have requested the Access Token with read scope .

Next, let’s also try to access the “Project by Id” endpoint that is protected by the @PreAuthorize annotation requesting the email scope.

As we send this request, the breakpoint will be triggered again, as the framework throws an AccessDeniedException and the handler proceeds in the exact same way.

We resume back to the Postman and we can see that we have got the same 403 - Forbidden response with the corresponding WWW-Authenticate header.

2.8. Access Control in Action - email and read Scope

Next, let’s see the same flow again, but this time with requesting Access Token with both email and read scope.

With this token, we’ll execute the endpoint for Request Project by Id, and we can see that the request is invoked successfully now.

3. Resources

Lesson 2: Verify/Validate Claims from the JWT (Text-Only)

1. Goal

In this lesson, we’re going to look at JWT Claim validation in Spring.

2. Lesson Notes

The relevant module you need to import when you’re working with this lesson is: lsso-module3/verify-validate-claims-jwt.

This lesson only needs a single reference codebase, so there is no end version of the project.

2.1. Overview

In this lesson we’ll analyze and see in action the validators provided by Spring Security out of the box:

  • the JwtTimestampValidator : checks that the token is not expired and that it can be used in the moment it’s received.
  • the JwtIssuerValidator : validates that the token has been created and signed by the expected provider

It’s important to know that these validators are configured automatically by the framework when we set up JWT support.

2.2. Spring Security Validators - JwtTimestampValidator

Let’s start by analyzing what happens under the hood with the simple configuration we have in the Resource Server’s security config class:

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

This is a simple config setting up our application as an OAuth Resource Server that uses JWT.

The framework will search the context for a bean implementing the JwtDecoder interface to transform the String tokens into Jwt objects.

Spring Security offers a useful implementation of this interface, the NimbusJwtDecoder class that contains a jwtValidator field:

public final class NimbusJwtDecoder implements JwtDecoder {
  
  // ...
  
  private OAuth2TokenValidator<Jwt> jwtValidator =  JwtValidators.createDefault();
  
  // ...
  
}

If we navigate into the JwtValidators.createDefault() method we’ll see the output of this createDefault method is clear:

public static OAuth2TokenValidator<Jwt> createDefault() {
	return new DelegatingOAuth2TokenValidator<>(Arrays.asList(new JwtTimestampValidator()));
}

It’s setting up a composite validator containing a single actual validator, the JwtTimestampValidator.

This validator is in charge of verifying that the token is used within the timeframe in which it’s valid.

This timeframe is determined by 2 claims contained in the JWT:

  • nbf : the Not Valid Before claim determines the instant in which the token can start to be used
  • exp : the Expiration Time claim indicates the time after which the JWT must not be accepted for processing

In the OAuth specification, these 2 claims are optional; thus they’ll only be validated if they’re included in the Token.

2.3. Spring Security Validators - JwtIssuerValidator

If we go back to the JwtValidators helper class implementation, we’ll see it offers one other function to create a JWT validator, the createDefaultWithIssuer method:

public static OAuth2TokenValidator<Jwt> createDefaultWithIssuer(String issuer) {
	List<OAuth2TokenValidator<Jwt>> validators = new ArrayList<>();
	validators.add(new JwtTimestampValidator());
	validators.add(new JwtIssuerValidator(issuer));
	return new DelegatingOAuth2TokenValidator<>(validators);
}

The implementation is fairly the same as the one we’ve just seen, with the difference that here there is one other validator getting configured, the JwtIssuerValidator.

Naturally, the framework will use this approach if we provide the issuer identifier, for example, as we’ve done in this application with the issuer-uri application property.

With this validator in place the decoder will check that the value corresponds with the iss (Issuer) claim, that is, who created and signed the token.

2.4. Testing the Spring Security Validators - Modifying the JWT

To see these validators in action, we can set up a breakpoint in the NimbusJwtDecoder#validateJwt method and launch the Resource Server application in debug mode.

Naturally, the best way to clearly see how this works is to force a failure in the validation of a JWT.

Normally, we wouldn’t be able to manipulate the content of JWTs because Authorization Servers only expose the public key to verify the signature of the token. But in this case, as it’s deployed locally, we do have access to the private key and we can use that to generate tokens with any data we want.

Follow these steps to create a new JWT from an existing one, with custom fields and values:

  1. Obtain an Access Token from the Authorization Server. You can use the Postman Collection (in the master branch) requests corresponding to this lesson to obtain a token
  2. Copy the JWT in the jwt.io tool to use as base to decode it
  3. Obtain the Authorization Server private key by searching the privateKey field in the Authorization Server’s baeldung-realm.json configuration file
  4. Copy the private key in jwt.io, in the last text box of the VERIFY SIGNATURE section
  5. Wrap the private key with an Encapsulation Boundary:
-----BEGIN RSA PRIVATE KEY-----
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMj...
-----END RSA PRIVATE KEY-----

Now we can modify the fields in the JSON-formatted token payload, and we’ll see the encoded token is modified automatically. For example, in this case we’ll change the following claims:

  • exp (Expiration Time) : we’ll set this to be the same as the iat (Issued At) instant, plus one
  • iss (Issuer): we’ll simply remove a character from this string

2.5. Testing the Spring Security Validators - Debugging

We can now send this new token as a Bearer token in a request to the Resource Server using Postman.

For example, we can make the following request:

GET http://localhost:8081/lsso-resource-server/api/projects
Authorization: Bearer {newJWT}

Once we send this, Postman will be idle waiting for the response.

We can head to the IDE and continue with the execution step by step.

At this point we can confirm the jwtValidator is a composite validator as we mentioned before. If we now step into the execution of this validator, we can see that all this does is trigger the validation logic of the token validators it contains one by one.

We can see how the new entries appear in the errors variable after the validators get invoked.

We can resume the execution and have a look at the response we obtained in Postman.

We see we obtain a 401-Unauthorized response as expected.

A noteworthy aspect here is that if we inspect the response Headers we’ll see it contains a WWW-Authenticate header indicating one of the errors’ code and description we just saw.

Naturally, this is standard behavior as it’s part of the Bearer Token specification.

3. Resources

Lesson 3: Accessing JWT Bearer Token Authentication attributes (text-only)

1. Goal

In this lesson, we’ll learn how to access Token attributes in our Resource Server application.

2. Lesson Notes

The relevant module you need to import when you’re starting with this lesson is: lsso-module3/accessing-token-authentication-attributes-start

If you want have a look at the fully implemented lesson, as a reference, feel free to import: lsso-module3/accessing-token-authentication-attributes-end

We’ll also need to import the Postman collection that contains all the REST calls that we need to run for this lesson.

2.1. Introduction

We can use Access Token properties in our Resource Server application to modify business logic after the user is authorized, or to add additional security rules to guard routes or methods.

Usually, a Resource Server doesn’t care about the user requesting a resource; instead, it cares about the Authorities granted to that user.

However, if needed, it’s possible to obtain some information about the user that is making the request from the Access Token that was issued to the user.

Let’s see this with some examples.

2.2. Accessing the Token

In this example, we will access the Token in a controller method and inspect the accessible information, including attributes and claims.

We have added a UserInfoController in the start point of the lesson in the Resource Server and added an endpoint for /user/info :slight_smile:

@RestController
public class UserInfoController {

    @GetMapping("/user/info")
    public Map<String, Object> getUserInfo() {

        return null;
    }
}

Our goal is to retrieve the token in the /user/info endpoint.

We’ll first add the Authentication object as a parameter in our controller method:

@GetMapping("/user/info")
public Map<String, Object> getUserInfo(Authentication authentication) {
    // ...
}

We can use this because after a Token is authorized, an instance of the Authentication is set in the Spring SecurityContext .

Since this Authentication holds an OAuth2 Authentication Principal, let’s access this in our method:

@GetMapping("/user/info")
public Map<String, Object> getUserInfo(Authentication authentication) {
    Jwt jwt = (Jwt) authentication.getPrincipal();

    return Collections.singletonMap("token", jwt);
}

Note that we had to cast this to the Jwt type. We’re doing this because our Resource Server is configured to use JWT Tokens, as a result this Principal is a Spring Security Jwt object.

If we were using Opaque Tokens, we could access the Authentication object in the same way but it would be of a different type.

Let’s add a breakpoint here and start the Resource Server in DEBUG mode so we can inspect the Authentication object. We will also need the Authorization Server up and running, so let’s boot it as well.

We’ll run the REST calls present in the Postman collection folder for this lesson.

We’ll first execute the necessary requests consecutively to obtain a new Access Token:

  • Extract Authorization Endpoint - extracts the authorization endpoint
  • Request Authorization Code - gets the Authorization Code from the Authorization Server
  • Request Access Token - exchanges the Authorization Code retrieved above to get an Access Token

Next, let’s execute the request for the new endpoint:

GET - User Info

This will trigger the execution and it will pause on the breakpoint that we’ve added in the UserInfoController .

As we inspect the Authentication object here, we can see its type is JwtAuthenticationToken and it contains authorities for the scopes that were retrieved by the Authorization Server.

We can also notice that the principal field which is of type Jwt contains the information we want to access here in the claims Map . For example, one of the claims we can view is the preferred_username.

The response of this endpoint is the entire token with all its claims.

2.3. Obtaining User Information Directly

We can also obtain a reference to the Principal directly by using the @AuthenticationPrincipal annotation.

The annotation resolves the Authentication.getPrincipal() method on the authentication and stores the value in the annotated method argument. This will eliminate the casting we did in the previous method and make our code a bit cleaner.

In this new example we’ll implement a function to access the preferred_username claim.

First, let’s write a new endpoint to access the entire token using the @AuthenticationPrincipal annotation:

@GetMapping("/user/info/direct")
public Map<String, Object> getUserInfo(@AuthenticationPrincipal Jwt principal) {
    return Collections.singletonMap("principal", principal);
}

and obtain a new token.

We’ll again execute the requests to obtain a new Access Token and call the new endpoint we just created:

GET - Direct User Info

As we can see here, the output is the entire Authentication Principal returned in JSON format:

{
    "principal": {
        "tokenValue": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJlUktVWG10TFhKMHBBNkxBS29aWko1ZlU0VDhCdmxKdERCb3pXanFFdnhjIn0.eyJleHAiOjE1OTA0Mjc1MjUsImlhdCI6MTU5MDQyNzIyNSwiYXV0aF90aW1lIjoxNTkwNDI3MjIwLCJqdGkiOiJjNjMwZGVkMS1lMTc5LTQxMTMtOTYyNi02YzA3YTViZDQ2ZWYiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODMvYXV0aC9yZWFsbXMvYmFlbGR1bmciLCJzdWIiOiJhNTQ2MTQ3MC0zM2ViLTRiMmQtODJkNC1iMDQ4NGU5NmFkN2YiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJsc3NvQ2xpZW50Iiwic2Vzc2lvbl9zdGF0ZSI6IjRiNzQ4MjliLTIzMTYtNGFkOC1hMTYyLTM0ZjY1OTY4NzI2MCIsImFjciI6IjEiLCJzY29wZSI6InByb2ZpbGUgd3JpdGUiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJqb2huQHRlc3QuY29tIn0.H2TCFuFx4OIaj2_rSs2jdvJYMNcLKqGcD3sEMlTK9TwwllI9f2uK2WW0iCyxmC4QkrEPCn3IsTcqhMpvS30oL_VeA201uikAzw4kHll1rs6IM3ve5qlmbnBfSblIlTMNv9tkU18prAa7IUJ8yolihb8fpPkzd86_YtXTY1ipu-Bkh7UGq8UL_SFae7cbcMfF8l2ufAN0hmiZ6ssy0FWca7oSKMl3b8mrkVQOA-NLGvtXKUwp3bk3r8TNjDMPhhgGDoGqt_DVyhn5R4Jt94E-5iJxbsAqGy8FwIgVTY-UwEzfQ66bbjBD8jbEcQ8XsLtK0fbsvn-BTHgL3l6r12nEuQ",
        "issuedAt": "2020-05-25T17:20:25Z",
        "expiresAt": "2020-05-25T17:25:25Z",
        "headers": {
            "kid": "eRKUXmtLXJ0pA6LAKoZZJ5fU4T8BvlJtDBozWjqEvxc",
            "typ": "JWT",
            "alg": "RS256"
        },
        "claims": {
            "sub": "a5461470-33eb-4b2d-82d4-b0484e96ad7f",
            "iss": "http://localhost:8083/auth/realms/baeldung",
            "typ": "Bearer",
            "preferred_username": "john@test.com",
            "acr": "1",
            "azp": "lssoClient",
            "auth_time": 1590427220,
            "scope": "profile write",
            "exp": "2020-05-25T17:25:25Z",
            "session_state": "4b74829b-2316-4ad8-a162-34f659687260",
            "iat": "2020-05-25T17:20:25Z",
            "jti": "c630ded1-e179-4113-9626-6c07a5bd46ef"
        },
        "id": "c630ded1-e179-4113-9626-6c07a5bd46ef",
        "subject": "a5461470-33eb-4b2d-82d4-b0484e96ad7f",
        "notBefore": null,
        "issuer": "http://localhost:8083/auth/realms/baeldung",
        "audience": null
    }
}

We can access any of these properties individually just like any other object.

Now, let’s try accessing the preferred_username claim; this is really easy since we already have the principal:

@GetMapping("/user/info/direct")
public Map<String, Object> getDirectUserInfo(@AuthenticationPrincipal Jwt principal) {
    String username = (String) principal.getClaims().get("preferred_username");

    return Collections.singletonMap("username", username);
}

Here, we’ve invoked the principal.getClaims() method; this returns a Map of the claims. Then, we extracted the desired claim preferred_username from this Map and returned it in our response.

The response will be a JSON containing username as the key and the preferred_username claim as the value.

Let’s test it out by obtaining a new Token, then calling the /user/info/direct endpoint again: GET - Direct User Info

As we can see, the response is the username:

{
    "username": "john@test.com"
}

3. Resources

Lesson 4: Accessing JWT Access Token Authentication Attributes Using SpEL (text-only)

1. Goal

In this lesson, we’ll learn how to access Token attributes in our Resource Server application using Spring Expression Language.

2. Lesson Notes

The relevant module you need to import when you’re starting with this lesson is: lsso-module3/accessing-token-authentication-attributes-spel-start

If you want have a look at the fully implemented lesson, as a reference, feel free to import: lsso-module3/accessing-token-authentication-attributes-spel-end

We’ll also need to import the Postman collection that contains all the REST calls that we need to run for this lesson.

2.1. Accessing Attributes via SpEL

We can use SpEL alongside the @AuthenticationPrincipal annotation to get properties from the Authentication principal. We do this by providing an expression to the annotation that Spring will resolve and return the required value.

In the next example, we’ll be accessing the preferred_username using SpEL.

We’ll add a new method getUserName(), and add the expression attribute to the annotation with the value “claims”:

@GetMapping("/user/info/spel1")
public Map<String, Object> getUserName(@AuthenticationPrincipal(expression = "claims") Map<String, Object> claims) {

    return Collections.singletonMap("username", claims.get("preferred_username"));
}

Under the hood, Spring checks the Authentication Principal for the claims property and invokes the getter if it exists.

Therefore, the expression returns a Map containing the claims. In our example, we’ve retrieved the username from that map.

Now, we can go to Postman and test this request.

We’ll execute the below requests consecutively to obtain a new access token:

  • Extract Authorization Endpoint - extracts the authorization endpoint
  • Request Authorization Code - gets the Authorization Code from the Authorization Server
  • Request Access Token - exchanges the Authorization Code retrieved above to get an Access Token

Then call the new endpoint we just created: GET - SPEL-1

We can see the username is returned in the response:

{
    "username": "john@test.com"
}

2.2. Accessing Attributes via SpEL Using Method-Level Security

We’ve looked at accessing Token attributes via the Authentication interface, and the @AuthenticationPrincipal annotation.

Now, let’s look at another way we can use SpEL to access attributes.

We can access Token information using SpEL by taking advantage of method level security in Spring. This allows us apply guards to routes and methods in Spring, using SpEL.

To showcase this, we’ll implement a secure route that only users with the read scope can access.

First, we have to enable method level security in our Resource Server:

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ResourceSecurityConfig extends WebSecurityConfigurerAdapter {
    // ...
}

We’ve also set prePostEnabled to be true which allows us to use the @PreAuthorize and @PostAuthorize annotations on our methods.

We can now use the @PreAuthorize annotation in any method; in this case, we’ll use it on a Controller method.

We’ll add a new method in our UserInfoController, that uses an SpEL expression to check the scope claim:

@GetMapping("/user/info/spel2")
@PreAuthorize("principal?.claims['scope']?.contains('read')")
public Map<String, Object> getReadScopeResponse() {
    return Collections.singletonMap("response", "users with the read scope can see this");
}

This annotation will only allow the method to be invoked if the expression evaluates to true; in our case, if the scope claim contains read, then, we’re simply returning a String.

Let’s test this to see what happens.

We’ll obtain a new Token so we can set the scopes we want our Token to be granted.

We’ll execute the requests again to obtain a new Access Token. In the first request, GET - Extract Authorization Endpoint, we set the scope as read, so the Token should contain the read scope.

Once we have got the Token, we can confirm it has the read scope by using the user info endpoint we made earlier: GET - User Info

Now let’s execute our SpEL2 endpoint: GET - SPEL-2

The response will have the Http Status 200 :

{
    "response": "users with the read scope can see this"
}

Now, let’s see what happens if the Token is not granted the read scope.

We will need to obtain another Token, by changing the scope to write in the first request: GET - Extract Authorization Endpoint. We’ll then execute the 3 requests to get a new Token.

We can again confirm confirm it has the write scope using the user info endpoint we made earlier: GET - User Info

Now let’s execute our SpEL2 endpoint again and see what happens: GET - SPEL-2

As you can see we get no response and a 403 forbidden Http Status because our Token doesn’t contain the required read scope.

3. Resources