eBook – Guide Spring Cloud – NPI EA (cat=Spring Cloud)
announcement - icon

Let's get started with a Microservice Architecture with Spring Cloud:

>> Join Pro and download the eBook

eBook – Mockito – NPI EA (tag = Mockito)
announcement - icon

Mocking is an essential part of unit testing, and the Mockito library makes it easy to write clean and intuitive unit tests for your Java code.

Get started with mocking and improve your application tests using our Mockito guide:

Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Reactive – NPI EA (cat=Reactive)
announcement - icon

Spring 5 added support for reactive programming with the Spring WebFlux module, which has been improved upon ever since. Get started with the Reactor project basics and reactive programming in Spring Boot:

>> Join Pro and download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Jackson – NPI EA (cat=Jackson)
announcement - icon

Do JSON right with Jackson

Download the E-book

eBook – HTTP Client – NPI EA (cat=Http Client-Side)
announcement - icon

Get the most out of the Apache HTTP Client

Download the E-book

eBook – Maven – NPI EA (cat = Maven)
announcement - icon

Get Started with Apache Maven:

Download the E-book

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

eBook – RwS – NPI EA (cat=Spring MVC)
announcement - icon

Building a REST API with Spring?

Download the E-book

Course – LS – NPI EA (cat=Jackson)
announcement - icon

Get started with Spring and Spring Boot, through the Learn Spring course:

>> LEARN SPRING
Course – RWSB – NPI EA (cat=REST)
announcement - icon

Explore Spring Boot 3 and Spring 6 in-depth through building a full REST API with the framework:

>> The New “REST With Spring Boot”

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

Yes, Spring Security can be complex, from the more advanced functionality within the Core to the deep OAuth support in the framework.

I built the security material as two full courses - Core and OAuth, to get practical with these more complex scenarios. We explore when and how to use each feature and code through it on the backing project.

You can explore the course here:

>> Learn Spring Security

Course – LSD – NPI EA (tag=Spring Data JPA)
announcement - icon

Spring Data JPA is a great way to handle the complexity of JPA with the powerful simplicity of Spring Boot.

Get started with Spring Data JPA through the guided reference course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (cat=Spring Boot)
announcement - icon

Refactor Java code safely — and automatically — with OpenRewrite.

Refactoring big codebases by hand is slow, risky, and easy to put off. That’s where OpenRewrite comes in. The open-source framework for large-scale, automated code transformations helps teams modernize safely and consistently.

Each month, the creators and maintainers of OpenRewrite at Moderne run live, hands-on training sessions — one for newcomers and one for experienced users. You’ll see how recipes work, how to apply them across projects, and how to modernize code with confidence.

Join the next session, bring your questions, and learn how to automate the kind of work that usually eats your sprint time.

Course – LJB – NPI EA (cat = Core Java)
announcement - icon

Code your way through and build up a solid, practical foundation of Java:

>> Learn Java Basics

Partner – LambdaTest – NPI EA (cat= Testing)
announcement - icon

Distributed systems often come with complex challenges such as service-to-service communication, state management, asynchronous messaging, security, and more.

Dapr (Distributed Application Runtime) provides a set of APIs and building blocks to address these challenges, abstracting away infrastructure so we can focus on business logic.

In this tutorial, we'll focus on Dapr's pub/sub API for message brokering. Using its Spring Boot integration, we'll simplify the creation of a loosely coupled, portable, and easily testable pub/sub messaging system:

>> Flexible Pub/Sub Messaging With Spring Boot and Dapr

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

If you're working on a Spring Security (and especially an OAuth) implementation, definitely have a look at the Learn Spring Security course:

>> LEARN SPRING SECURITY

1. Introduction

In this tutorial, we’ll show how to customize the mapping from JWT (JSON Web Token) claims into Spring Security’s Authorities.

2. Background

When a properly configured Spring Security-based application receives a request, it goes through a series of steps that, in essence, aims at two goals:

  • Authenticate the request, so the application can know who is accessing it
  • Decide whether the authenticated request may perform the associated action

For an application using JWT as its main security mechanism, the authorization aspect consists of:

  • Extracting claim values from the JWT payload, usually the scope or scp claim
  • Mapping those claims into a set of GrantedAuthority objects

Once the security engine has set up those authorities, it can then evaluate whether any access restrictions apply to the current request and decide whether it can proceed.

3. Default Mapping

Out-of-the-box, Spring uses a straightforward strategy to convert claims into GrantedAuthority instances. Firstly, it extracts the scope or scp claim and splits it into a list of strings. Next, for each string, it creates a new SimpleGrantedAuthority using the prefix SCOPE_ followed by the scope value.

To illustrate this strategy, let’s create a simple endpoint that allows us to inspect some key properties of the Authentication instance made available to the application:

@RestController
@RequestMapping("/user")
public class UserRestController {
    
    @GetMapping("/authorities")
    public Map<String,Object> getPrincipalInfo(JwtAuthenticationToken principal) {
        
        Collection<String> authorities = principal.getAuthorities()
          .stream()
          .map(GrantedAuthority::getAuthority)
          .collect(Collectors.toList());
        
        Map<String,Object> info = new HashMap<>();
        info.put("name", principal.getName());
        info.put("authorities", authorities);
        info.put("tokenAttributes", principal.getTokenAttributes());

        if ( principal instanceof AccountToken ) {
          info.put( "account", ((AccountToken)principal).getAccount());
        }

        return info;
    }
}

Here, we use a JwtAuthenticationToken argument because we know that, when using JWT-based authentication, this will be the actual Authentication implementation created by Spring Security. We create the result extracting from its name property, the available GrantedAuthority instances, and the JWT’s original attributes.

Now, let’s assume we invoke this endpoint passing and encoded-and-signed JWT containing this payload:

{
  "aud": "api://f84f66ca-591f-4504-960a-3abc21006b45",
  "iss": "https://sts.windows.net/2e9fde3a-38ec-44f9-8bcd-c184dc1e8033/",
  "iat": 1648512013,
  "nbf": 1648512013,
  "exp": 1648516868,
  "email": "[email protected]",
  "family_name": "Sevestre",
  "given_name": "Philippe",
  "name": "Philippe Sevestre",
  "scp": "profile.read",
  "sub": "eXWysuqIJmK1yDywH3gArS98PVO1SV67BLt-dvmQ-pM",
  ... more claims omitted
}

The response should look like a JSON object with three properties:

{
  "tokenAttributes": {
     // ... token claims omitted
  },
  "name": "0047af40-473a-4dd3-bc46-07c3fe2b69a5",
  "authorities": [
    "SCOPE_profile",
    "SCOPE_email",
    "SCOPE_openid"
  ]
}

We can use those scopes to restrict access to certain parts of our applications by creating a SecurityFilterChain:

@Bean
SecurityFilterChain customJwtSecurityChain(HttpSecurity http) throws Exception {
    // @formatter:off
    return http.oauth2ResourceServer(oauth2 -> oauth2.jwt(jwtConfigurer -> jwtConfigurer
                                .jwtAuthenticationConverter(customJwtAuthenticationConverter(accountService)))).build();
    // @formatter:on
}

Notice that we’ve intentionally avoided using WebSecurityConfigureAdapter. As described, this class will be deprecated in Spring Security version 5.7, so it’s better to start moving to the new approach as soon as possible.

Alternatively, we could use method-level annotations and an SpEL expression to achieve the same result:

@GetMapping("/authorities")
@PreAuthorize("hasAuthority('SCOPE_profile.read')")
public Map<String,Object> getPrincipalInfo(JwtAuthenticationToken principal) {
    // ... same code as before
}

Finally, for more complex scenarios, we can also resort to accessing directly the current JwtAuthenticationToken from which we have direct access to all GrantedAuthorities

4. Customizing the SCOPE_ Prefix

As our first example of how to change Spring Security’s default claim mapping behavior, let’s see how to change the SCOPE_ prefix to something else. As described in the documentation, there are two classes involved in this task:

  • JwtAuthenticationConverter: Converts a raw JWT into an AbstractAuthenticationToken
  • JwtGrantedAuthoritiesConverter: Extracts a collection of GrantedAuthority instances from the raw JWT.

Internally, JwtAuthenticationConverter uses JwtGrantedAuthoritiesConverter to populate a JwtAuthenticationToken with GrantedAuthority objects along with other attributes.

The simplest way to change this prefix is to provide our own JwtAuthenticationConverter bean, configured with JwtGrantedAuthoritiesConverter configured to one of our own choice:

@Configuration
@EnableConfigurationProperties(JwtMappingProperties.class)
@EnableMethodSecurity
public class SecurityConfig {
    // ... fields and constructor omitted
    @Bean
    public Converter<Jwt, Collection<GrantedAuthority>> jwtGrantedAuthoritiesConverter() {
        JwtGrantedAuthoritiesConverter converter = new JwtGrantedAuthoritiesConverter();
        if (StringUtils.hasText(mappingProps.getAuthoritiesPrefix())) {
            converter.setAuthorityPrefix(mappingProps.getAuthoritiesPrefix().trim());
        }
        return converter;
    }
    
    @Bean
    public JwtAuthenticationConverter customJwtAuthenticationConverter() {
        JwtAuthenticationConverter converter = new JwtAuthenticationConverter();
        converter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter();
        return converter;
    }

Here, JwtMappingProperties is just a @ConfigurationProperties class that we’ll use to externalize mapping properties. Although not shown in this snippet, we’ll use constructor injection to initialize the mappingProps field with an instance populated from any configured PropertySource, thus giving us enough flexibility to change its values at deploy time.

This @Configuration class has two @Bean methods: jwtGrantedAuthoritiesConverter() creates the required Converter that creates the GrantedAuthority collection. In this case, we’re using the stock JwtGrantedAuthoritiesConverter configured with the prefix set in the configuration properties.

Next, we have customJwtAuthenticationConverter(), where we construct the JwtAuthenticationConverter configured to use our custom converter. From there, Spring Security will pick it up as part of its standard auto-configuration process and replace the default one.

Now, once we set the baeldung.jwt.mapping.authorities-prefix property to some value, MY_SCOPE, for instance, and invoke /user/authorities, we’ll see the customized authorities:

{
  "tokenAttributes": {
    // ... token claims omitted 
  },
  "name": "0047af40-473a-4dd3-bc46-07c3fe2b69a5",
  "authorities": [
    "MY_SCOPE_profile",
    "MY_SCOPE_email",
    "MY_SCOPE_openid"
  ]
}

5. Using a Customized Prefix in Security Constructs

It is important to note that, by changing the authorities’ prefixes, we’ll impact any authorization rule that relies on their names. For instance, if we change the prefix to MY_PREFIX_, any @PreAuthorize expressions that assume the default prefix would no longer work. The same applies to HttpSecurity-based authorization constructs.

Fixing this issue, however, is simple. First, let’s add to our @Configuration class a @Bean method that returns the configured prefix. Since this configuration is optional, we must ensure that we return the default value if no one was given it:

@Bean
public String jwtGrantedAuthoritiesPrefix() {
  return mappingProps.getAuthoritiesPrefix() != null ?
    mappingProps.getAuthoritiesPrefix() : 
      "SCOPE_";
}

Now, we can use reference this bean using the @<bean-name> syntax in SpEL expressions. This is how we’d use the prefix bean with @PreAuthorize:

@GetMapping("/authorities")
@PreAuthorize("hasAuthority(@jwtGrantedAuthoritiesPrefix + 'profile.read')")
public Map<String,Object> getPrincipalInfo(JwtAuthenticationToken principal) {
    // ... method implementation omitted
}

We can also use a similar approach when defining a SecurityFilterChain:

@Bean
SecurityFilterChain customJwtSecurityChain(HttpSecurity http) throws Exception {
    return http.authorizeHttpRequests(auth -> {
        auth.requestMatchers("/user/**")
          .hasAuthority(mappingProps.getAuthoritiesPrefix() + "profile");
      })
      // ... other customizations omitted
      .build();
}

6. Customizing the Principal‘s Name

Sometimes, the standard sub claim that Spring maps to the Authentication’s name property comes with a value that is not very useful. Keycloak-generated JWTs are a good example:

{
  // ... other claims omitted
  "sub": "0047af40-473a-4dd3-bc46-07c3fe2b69a5",
  "scope": "openid profile email",
  "email_verified": true,
  "name": "User Primo",
  "preferred_username": "user1",
  "given_name": "User",
  "family_name": "Primo"
}

In this case, sub comes with an internal identifier, but we can see that the preferred_username claim has a more friendly value. We can easily modify JwtAuthenticationConverter’s behavior by setting its principalClaimName property with the desired claim name:

@Bean
public JwtAuthenticationConverter customJwtAuthenticationConverter() {

    JwtAuthenticationConverter converter = new JwtAuthenticationConverter();
    converter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter());

    if (StringUtils.hasText(mappingProps.getPrincipalClaimName())) {
        converter.setPrincipalClaimName(mappingProps.getPrincipalClaimName());
    }
    return converter;
}

Now, if we set the baeldung.jwt.mapping.authorities-prefix property to “preferred_username”, the /user/authorities result will change accordingly:

{
  "tokenAttributes": {
    // ... token claims omitted 
  },
  "name": "user1",
  "authorities": [
    "MY_SCOPE_profile",
    "MY_SCOPE_email",
    "MY_SCOPE_openid"
  ]
}

7. Scope Names Mapping

Sometimes, we might need to map the scope names received in the JWT to an internal name. For example, this can be the case where the same application needs to work with tokens generated by different authorization servers, depending on the environment where it was deployed.

We might be tempted to extend JwtGrantedAuthoritiesConverter, but since this is a final class, we can’t use this approach. Instead, we must code our own Converter class and inject it into JwtAuthorizationConverter. This enhanced mapper, MappingJwtGrantedAuthoritiesConverter, implements Converter<Jwt, Collection<GrantedAuthority>> and looks much like the original one:

public class MappingJwtGrantedAuthoritiesConverter implements Converter<Jwt, Collection<GrantedAuthority>> {
    private static Collection<String> WELL_KNOWN_AUTHORITIES_CLAIM_NAMES = Arrays.asList("scope", "scp");
    private Map<String,String> scopes;
    private String authoritiesClaimName = null;
    private String authorityPrefix = "SCOPE_";
     
    // ... constructor and setters omitted

    @Override
    public Collection<GrantedAuthority> convert(Jwt jwt) {
        
        Collection<String> tokenScopes = parseScopesClaim(jwt);
        if (tokenScopes.isEmpty()) {
            return Collections.emptyList();
        }
        
        return tokenScopes.stream()
          .map(s -> scopes.getOrDefault(s, s))
          .map(s -> this.authorityPrefix + s)
          .map(SimpleGrantedAuthority::new)
          .collect(Collectors.toCollection(HashSet::new));
    }
    
    protected Collection<String> parseScopesClaim(Jwt jwt) {
       // ... parse logic omitted 
    }
}

Here, the key aspect of this class is the mapping step, where we use the supplied scopes map to translate the original scopes into the mapped ones. Also, any incoming scope that has no mapping available will be preserved.

Finally, we use this enhanced converter in our @Configuration in its jwtGrantedAuthoritiesConverter() method:

@Bean
public Converter<Jwt, Collection<GrantedAuthority>> jwtGrantedAuthoritiesConverter() {
    MappingJwtGrantedAuthoritiesConverter converter = new MappingJwtGrantedAuthoritiesConverter(mappingProps.getScopes());

    if (StringUtils.hasText(mappingProps.getAuthoritiesPrefix())) {
        converter.setAuthorityPrefix(mappingProps.getAuthoritiesPrefix());
    }
    if (StringUtils.hasText(mappingProps.getAuthoritiesClaimName())) {
        converter.setAuthoritiesClaimName(mappingProps.getAuthoritiesClaimName());
    }
    return converter;
}

8. Using a Custom JwtAuthenticationConverter

In this scenario, we’ll take full control of the JwtAuthenticationToken generation process. We can use this approach to return an extended version of this class with additional data recovered from a database.

There are two possible approaches to replace the standard JwtAuthenticationConverter. The first, which we’ve used in the previous sections, is to create a @Bean method that returns our custom converter. This, however, implies that our customized version must extend Spring’s JwtAuthenticationConverter so the autoconfiguration process can pick it.

The second option is to use the HttpSecurity-based DSL approach, where we can provide our custom converter. We’ll do this using the oauth2ResourceServer customizer, which allows us to plug any converter that implements a much more generic interface Converter<Jwt, AbstractAuthorizationToken>:

@Bean
SecurityFilterChain customJwtSecurityChain(HttpSecurity http) throws Exception {
    // @formatter:off
    return http.oauth2ResourceServer(oauth2 -> oauth2.jwt(jwtConfigurer -> jwtConfigurer
                                .jwtAuthenticationConverter(customJwtAuthenticationConverter(accountService)))).build();
    // @formatter:on
}

Our CustomJwtAuthenticationConverter uses an AccountService (available online) to retrieve an Account object based on username claim value. It then uses it to create a CustomJwtAuthenticationToken with an extra accessor method for the account data:

public class CustomJwtAuthenticationConverter implements Converter<Jwt, AbstractAuthenticationToken> {

    // ...private fields and construtor omitted
    @Override
    public AbstractAuthenticationToken convert(Jwt source) {
        
        Collection<GrantedAuthority> authorities = jwtGrantedAuthoritiesConverter.convert(source);
        String principalClaimValue = source.getClaimAsString(this.principalClaimName);
        Account acc = accountService.findAccountByPrincipal(principalClaimValue);
        return new AccountToken(source, authorities, principalClaimValue, acc);
    }
}

Now, let’s modify our /user/authorities handler to use our enhanced Authentication:

@GetMapping("/authorities")
public Map<String,Object> getPrincipalInfo(JwtAuthenticationToken principal) {

    // ... create result map as before (omitted)
    if (principal instanceof AccountToken) {
        info.put( "account", ((AccountToken)principal).getAccount());
    }
    return info;
}

One advantage of taking this approach is that we can now easily use our enhanced authentication object in other parts of the application. For instance, we can access the account info in SpEL expressions directly from the built-in variable authentication:

@GetMapping("/account/{accountNumber}")
@PreAuthorize("authentication.account.accountNumber == #accountNumber")
public Account getAccountById(@PathVariable("accountNumber") String accountNumber, AccountToken authentication) {
    return authentication.getAccount();
}

Here, the @PreAuthorize expression enforces that the accountNumber passed in the path variable belongs to the user. This approach is particularly useful when used in conjunction with Spring Data JPA, as described in the official documentation.

9. Testing Tips

The examples given so far assume we have a functioning identity provider (IdP) that issues JWT-based access tokens. A good option is to use the embedded Keycloak server that we’ve already covered here. Additional configuration instructions are also available in our Quick Guide to Using Keycloak.

Please notice that those instructions cover how to register an OAuth client. For live tests, Postman is a good tool that supports the authorization code flow. The important detail here is how to properly configure the Valid Redirect URI parameter. Since Postman is a desktop application, it uses a helper site located at https://oauth.pstmn.io/v1/callback to capture the authorization code. Consequently, we must ensure we have internet connectivity during the tests. If this is not possible, we can use the less secure password grant flow instead.

Regardless of the selected IdP and client selection, we must configure our resource server so it can properly validate the received JWTs. For standard OIDC providers, this means providing a suitable value to the spring.security.oauth2.resourceserver.jwt.issuer-uri property. Spring will then fetch all configuration details using the .well-known/openid-configuration document available there.

In our case, the issuer URI for our Keycloak realm is http://localhost:8083/auth/realms/baeldung. We can point our browser to retrieve the full document at http://localhost:8083/auth/realms/baeldung/.well-known/openid-configuration.

10. Conclusion

In this article, we’ve shown different ways to customize the way Spring Security map authorities from JWT claims.

The code backing this article is available on GitHub. Once you're logged in as a Baeldung Pro Member, start learning and coding on the project.
Baeldung Pro – NPI EA (cat = Baeldung)
announcement - icon

Baeldung Pro comes with both absolutely No-Ads as well as finally with Dark Mode, for a clean learning experience:

>> Explore a clean Baeldung

Once the early-adopter seats are all used, the price will go up and stay at $33/year.

eBook – HTTP Client – NPI EA (cat=HTTP Client-Side)
announcement - icon

The Apache HTTP Client is a very robust library, suitable for both simple and advanced use cases when testing HTTP endpoints. Check out our guide covering basic request and response handling, as well as security, cookies, timeouts, and more:

>> Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

Course – LS – NPI EA (cat=REST)

announcement - icon

Get started with Spring Boot and with core Spring, through the Learn Spring course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (tag=Refactoring)
announcement - icon

Modern Java teams move fast — but codebases don’t always keep up. Frameworks change, dependencies drift, and tech debt builds until it starts to drag on delivery. OpenRewrite was built to fix that: an open-source refactoring engine that automates repetitive code changes while keeping developer intent intact.

The monthly training series, led by the creators and maintainers of OpenRewrite at Moderne, walks through real-world migrations and modernization patterns. Whether you’re new to recipes or ready to write your own, you’ll learn practical ways to refactor safely and at scale.

If you’ve ever wished refactoring felt as natural — and as fast — as writing code, this is a good place to start.

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

eBook Jackson – NPI EA – 3 (cat = Jackson)