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
Partner – Diagrid – NPI (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

1. Overview

In this tutorial, we’ll explore options for testing access control rules with mocked identities in a Spring application with OAuth2 security.

We’ll use MockMvc request post-processors, WebTestClient mutators, and test annotations, from both spring-security-test and spring-addons.

2. Why Using Spring-Addons?

In the field of OAuth2, spring-security-test only offers request post-processors and mutators that require the context of respectively a MockMvc or WebTestClient request. This can be just fine for @Controllers, but it is an issue to test method security (@PreAuthorize, @PostFilter, etc.) on a @Service or @Repository.

Using annotations like @WithJwt or @WithOidcLogin, we can mock the security context when unit testing any kind of @Component in both servlet and reactive applications. This is why we’ll use spring-addons-oauth2-test during some of our tests: it provides us with such annotations for most of Spring OAuth2 Authentication implementations.

3. What Will We Test?

The companion GitHub repository contains two resource servers sharing the following features:

  • secured with a JWT decoder (rather than opaque token introspection)
  • require ROLE_AUTHORIZED_PERSONNEL authority to access /secured-route and /secured-method
  • return 401 if authentication is missing or invalid (expired, wrong issuer, etc.) and 403 if access is denied (missing roles)
  • define access control using Java configuration (with requestMatcher and pathMatcher for servlet and reactive app, respectively) and method security
  • use data from the Authentication in the security context to build response payloads

To illustrate the slight differences between servlet and reactive test APIs, one is a servlet and the second is a reactive application.

In this article, we’ll focus on testing access control rules in unit and integration tests and assert that the HTTP status of the response matches the expectations according to mocked user identities, or that an exception is thrown when unit testing other @Component than @Controller, like @Service or @Repository secured with @PreAuthorize, @PostFilter and alike.

All tests pass without any authorization server, but we’ll need one to be up and running if we ever like to start the resource servers under test and query it with tools like Postman. A Docker Compose file for a local Keycloak instance is provided to get started quickly:

  • admin console available from: http://localhost:8080/admin/master/console/#/baeldung
  • admin account is admin / admin
  • a baeldung realm is created already with a confidential client (baeldung_confidential / secret) an two users (authorized and forbidden, both with secret as secret)

4. Unit Testing With Mocked Authentications

By “unit test”, we mean a test of a single @Component in isolation of any other dependency (which we’ll mock). The tested @Component could be a @Controller in a @WebMvcTest or @WebFluxTest, or as any other secured @Service@Repository, etc., in a plain JUnit test.

MockMvc and WebTestClient ignore the Authorization header, and there’s no need to provide a valid access token. Of course, we could instantiate or mock any authentication implementation and manually create a security context at the beginning of each test, but this is way too tedious. Instead, we’ll use spring-security-test MockMvc request post-processors, WebTestClient mutators, or spring-addons annotations to populate the test security context with a mocked Authentication instance of our choice.

We’ll use @WithMockUser just to see that it builds a UsernamePasswordAuthenticationToken instance which is frequently an issue as OAuth2 runtime configuration puts other types of Authentication in the security context:

  • JwtAuthenticationToken for resource server with a JWT decoder
  • BearerTokenAuthentication for resource server with access token introspection (opaqueToken)
  • OAuth2AuthenticationToken for clients with oauth2Login
  • Absolutely anything if we decide to return another Authentication instance than Spring default one in a custom authentication converter. So, technically, it’s possible for an OAuth2 authentication converter to return a UsernamePasswordAuthenticationToken instance and use @WithMockUser in tests, but it’s a pretty unnatural choice, and we won’t use that here.

4.1. Important Notes

MockMvc pre-processors and WebTestClient mutators don’t use the beans defined in the security configuration to build the test Authentication instances. As a consequence, defining OAuth2 claims with SecurityMockMvcRequestPostProcessors.jwt() or SecurityMockServerConfigurers.mockJwt() won’t have any impact on authentication name and authorities. We have to set the name and authorities by ourselves, using the dedicated methods.

As a contrast, the factories behind spring-addons annotations scan the test context for an authentication converter and use it if they find one. So, when using @WithJwt it’s important to expose any custom JwtAuthenticationConverter as a bean (instead of just inlining it as a lambda in security conf):

@Configuration
@EnableMethodSecurity
@EnableWebSecurity
static class SecurityConf {
    @Bean
    SecurityFilterChain filterChain(HttpSecurity http, Converter<Jwt, AbstractAuthenticationToken> authenticationConverter) throws Exception {
        http.oauth2ResourceServer(resourceServer -> resourceServer.jwt(jwtResourceServer -> jwtResourceServer.jwtAuthenticationConverter(authenticationConverter)));
        ...
    }

    @Bean
    JwtAuthenticationConverter authenticationConverter(Converter<Jwt, Collection<GrantedAuthority>> authoritiesConverter) {
        final var authenticationConverter = new JwtAuthenticationConverter();
        authenticationConverter.setPrincipalClaimName(StandardClaimNames.PREFERRED_USERNAME);
        authenticationConverter.setJwtGrantedAuthoritiesConverter(authoritiesConverter);
        return authenticationConverter;
    }
}

Notably, the authentication converter is exposed as a @Bean which is explicitly injected in the security filter-chain. That way, the factory behind @WithJwt can use it to build the Authentication from the claims, the exact same way it would be at runtime with a real token.

Also note that in the advanced cases were the the authentication converter returns something else than a JwtAuthenticationToken (or BearerTokenAuthentication in the case of a resource server with token introspection), only the test annotation from Spring addons will build the expected type of Authentication.

4.2. Test Setup

For @Controller unit tests, we should decorate test classes with @WebMvcTest for servlet apps and @WebFluxTest for reactive ones.

Spring autowires MockMvc or WebTestClient for us, and as we’re writing controller unit tests, we’ll mock MessageService.

This is what an empty @Controller unit test would look like in a servlet application:

@WebMvcTest(controllers = GreetingController.class)
class GreetingControllerTest {

    @MockBean
    MessageService messageService;

    @Autowired
    MockMvc mockMvc;

    //...
}
And this is what an empty @Controller unit test would look like in a reactive application:
@WebFluxTest(controllers = GreetingController.class)
class GreetingControllerTest {
    private static final AnonymousAuthenticationToken ANONYMOUS = 
      new AnonymousAuthenticationToken("anonymous", "anonymousUser", 
      AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS"));

    @MockBean
    MessageService messageService;

    @Autowired
    WebTestClient webTestClient;

    //...
}
Now, let’s see how to assert that HTTP status codes match the specifications we set earlier.

4.3. Unit Testing With MockMvc Post-Processors

To populate the test security context with JwtAuthenticationToken, which is the default Authentication type for resource servers with the JWT decoder, we’ll use the jwt post-processor for MockMvc requests:

import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors;

Let’s see a few sample of requests with MockMvc and assertions on response status depending on the endpoint and mocked Authentication:

@Test
void givenRequestIsAnonymous_whenGetGreet_thenUnauthorized() throws Exception {
    mockMvc.perform(get("/greet").with(SecurityMockMvcRequestPostProcessors.anonymous()))
      .andExpect(status().isUnauthorized());
}

In the above, we ensure that an anonymous request can’t get a greeting and that it is properly returned a 401.

Let’s now see how a request is answered 200 Ok or 403 Forbidden depending on the endpoints security rules and on the authorities assigned to a test  JwtAuthenticationToken:

@Test
void givenUserIsGrantedWithRoleAuthorizedPersonnel_whenGetSecuredRoute_thenOk() throws Exception {
    var secret = "Secret!";
    when(messageService.getSecret()).thenReturn(secret);

    mockMvc.perform(get("/secured-route").with(SecurityMockMvcRequestPostProcessors.jwt()
      .authorities(new SimpleGrantedAuthority("ROLE_AUTHORIZED_PERSONNEL"))))
        .andExpect(status().isOk())
        .andExpect(content().string(secret));
}

@Test
void givenUserIsNotGrantedWithRoleAuthorizedPersonnel_whenGetSecuredRoute_thenForbidden() throws Exception {
    mockMvc.perform(get("/secured-route").with(SecurityMockMvcRequestPostProcessors.jwt()
      .authorities(new SimpleGrantedAuthority("admin"))))
        .andExpect(status().isForbidden());
}

4.4. Unit Testing With WebTestClient Mutators

In the reactive resource server, the Authentication type in the security context is the same as in the servlet one: JwtAuthenticationToken. As a consequence, we’ll use the mockJwt mutator for WebTestClient:

import org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers;

As opposed to MockMvc post-processors, there is no anonymous WebTestClient mutator. However, when can easily define an anonymous Authentication instance and use it through the generic mockAuthentication mutator:

private static final AnonymousAuthenticationToken ANONYMOUS = new AnonymousAuthenticationToken(
    "anonymous", "anonymousUser", AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS"));

@Test
void givenRequestIsAnonymous_whenGetGreet_thenUnauthorized() throws Exception {
    webTestClient.mutateWith(SecurityMockServerConfigurers.mockAuthentication(ANONYMOUS))
        .get()
        .uri("/greet")
        .exchange()
        .expectStatus()
        .isUnauthorized();
}

@Test
void givenUserIsAuthenticated_whenGetGreet_thenOk() throws Exception {
    var greeting = "Whatever the service returns";
    when(messageService.greet()).thenReturn(Mono.just(greeting));

    webTestClient.mutateWith(SecurityMockServerConfigurers.mockJwt().authorities(List.of(
      new SimpleGrantedAuthority("admin"), 
      new SimpleGrantedAuthority("ROLE_AUTHORIZED_PERSONNEL")))
        .jwt(jwt -> jwt.claim(StandardClaimNames.PREFERRED_USERNAME, "ch4mpy")))
        .get()
        .uri("/greet")
        .exchange()
        .expectStatus()
        .isOk()
        .expectBody(String.class)
        .isEqualTo(greeting);

    verify(messageService, times(1)).greet();
}

@Test
void givenUserIsNotGrantedWithRoleAuthorizedPersonnel_whenGetSecuredRoute_thenForbidden() throws Exception {
    webTestClient.mutateWith(mockJwt().authorities(new SimpleGrantedAuthority("admin")))
        .get()
        .uri("/secured-route")
        .exchange()
        .expectStatus()
        .isForbidden();
}

4.5. Unit Testing Controllers With Annotations from Spring-Addons

We can use test annotations in the exact same way in servlet and reactive apps.

All we need is to add a dependency on spring-addons-oauth2-test:

<dependency>
    <groupId>com.c4-soft.springaddons</groupId>
    <artifactId>spring-addons-oauth2-test</artifactId>
    <version>7.6.12</version>
    <scope>test</scope>
</dependency>

This library comes with quite a few annotations covering the following use-cases:

  • @WithMockAuthentication is frequently enough when testing role based access-control: it is intended to take authorities as parameter, but also accepts a user name and implementation types to mock for Authentication and Principal.
  • @WithJwt to use when testing a resource server with JWT decoder. It relies on an authentication factory which picks the Converter<Jwt, ? extends AbstractAthenticationToken> (or Converter<Jwt, ? extends Mono<? extends AbstractAuthenticationToken>> in reactive applications) from the security configuration, and a JSON payload on the test classpath. This gives a complete hand on claims and provides with the same authentication instance as we would have at runtime for the same JWT payload.
  • @WithOpaqueToken works the same as @WithJwt, but for resource servers with token introspection: it relies on a factory picking the OpaqueTokenAuthenticationConverter (or ReactiveOpaqueTokenAuthenticationConverter).
  • @WithOAuth2Login and @WithOidcLogin will be our choice when what we want to test is an OAuth2 client with login

Before getting into the tests, we will define some JSON files as test resources. It’s intended to mock the JSON payload (or introspection response) of access tokens for representative users (personas or personae). We may copy the payload of real tokens using tools like https://jwt.io.

Ch4mpy will be our test user with the AUTHORIZED_PERONNEL role:

{
  "iss": "https://localhost:8443/realms/master",
  "sub": "281c4558-550c-413b-9972-2d2e5bde6b9b",
  "iat": 1695992542,
  "exp": 1695992642,
  "preferred_username": "ch4mpy",
  "realm_access": {
    "roles": [
      "admin",
      "ROLE_AUTHORIZED_PERSONNEL"
    ]
  },
  "email": "[email protected]",
  "scope": "openid email"
}

And we’ll define a second user without AUTHORIZED_PERONNEL role:

{
  "iss": "https://localhost:8443/realms/master",
  "sub": "2d2e5bde6b9b-550c-413b-9972-281c4558",
  "iat": 1695992551,
  "exp": 1695992651,
  "preferred_username": "tonton-pirate",
  "realm_access": {
    "roles": [
      "uncle",
      "skipper"
    ]
  },
  "email": "[email protected]",
  "scope": "openid email"
}

Now, we can remove identity mocking from the test body, decorating the test method with an annotation instead. For demonstration purpose, we’ll use both @WithMockAuthentication and @WithJwt, but one would be enough in actual tests. We’d probably choose the first when we need to define just authorities or name, and the second when we need to have a hand on many claims:

@Test
@WithAnonymousUser
void givenRequestIsAnonymous_whenGetSecuredMethod_thenUnauthorized() throws Exception {
    api.perform(get("/secured-method"))
        .andExpect(status().isUnauthorized());
}

@Test
@WithMockAuthentication({ "admin", "ROLE_AUTHORIZED_PERSONNEL" })
void givenUserIsGrantedWithRoleAuthorizedPersonnel_whenGetSecuredMethod_thenOk() throws Exception {
    final var secret = "Secret!";
    when(messageService.getSecret()).thenReturn(secret);

    api.perform(get("/secured-method"))
        .andExpect(status().isOk())
        .andExpect(content().string(secret));
}

@Test
@WithMockAuthentication({ "admin" })
void givenUserIsNotGrantedWithRoleAuthorizedPersonnel_whenGetSecuredMethod_thenForbidden() throws Exception {
    api.perform(get("/secured-method"))
        .andExpect(status().isForbidden());
}

@Test
@WithJwt("ch4mpy.json")
void givenUserIsCh4mpy_whenGetSecuredMethod_thenOk() throws Exception {
    final var secret = "Secret!";
    when(messageService.getSecret()).thenReturn(secret);

    api.perform(get("/secured-method"))
        .andExpect(status().isOk())
        .andExpect(content().string(secret));
}

@Test
@WithJwt("tonton-pirate.json")
void givenUserIsTontonPirate_whenGetSecuredMethod_thenForbidden() throws Exception {
    api.perform(get("/secured-method"))
        .andExpect(status().isForbidden());
}

Annotations definitely fit very well with the BDD paradigm:

  • preconditions (Given) are in text context (annotation decorating the test)
  • only tested code execution (When) and result assertions (Then) are in the test body

4.6. Unit Testing @Service or @Repository Secured Method

When testing @Controller, the choice between request MockMvc post-processors (or WebTestClient mutators) and annotations is mostly a matter of team preference, but to unit test MessageService::getSecret access control, spring-security-test is no longer an option, and we’ll need spring-addons annotations.

Here is the JUnit setup:

  • activate Spring auto-wiring with @ExtendWith(SpringExtension.class)
  • import and autowire the MessageService to get an instrumented instance
  • if using @WithJwt, we need to import the configuration containing the JwtAuthenticationConverter as well as the AuthenticationFactoriesTestConf. Otherwise, decorating the test with @EnableMethodSecurity is enough.

We’ll assert that MessageService throws an exception each time the user is missing the ROLE_AUTHORIZED_PERSONNEL authority.

Here is a complete unit test of a @Service in a servlet application:

@ExtendWith(SpringExtension.class)
@TestInstance(Lifecycle.PER_CLASS)
@Import({ MessageService.class, SecurityConf.class })
@ImportAutoConfiguration(AuthenticationFactoriesTestConf.class)
class MessageServiceUnitTest {
    @Autowired
    MessageService messageService;
    
    @MockBean
    JwtDecoder jwtDecoder;

    @Test
    void givenSecurityContextIsNotSet_whenGreet_thenThrowsAuthenticationCredentialsNotFoundException() {
        assertThrows(AuthenticationCredentialsNotFoundException.class, () -> messageService.getSecret());
    }

    @Test
    @WithAnonymousUser
    void givenUserIsAnonymous_whenGreet_thenThrowsAccessDeniedException() {
        assertThrows(AccessDeniedException.class, () -> messageService.getSecret());
    }

    @Test
    @WithJwt("ch4mpy.json")
    void givenUserIsCh4mpy_whenGreet_thenReturnGreetingWithPreferredUsernameAndAuthorities() {
        assertEquals("Hello ch4mpy! You are granted with [admin, ROLE_AUTHORIZED_PERSONNEL].", 
          messageService.greet());
    }

    @Test
    @WithMockUser(authorities = { "admin", "ROLE_AUTHORIZED_PERSONNEL" }, username = "ch4mpy")
    void givenSecurityContextIsPopulatedWithUsernamePasswordAuthenticationToken_whenGreet_thenThrowsClassCastException() {
        assertThrows(ClassCastException.class, () -> messageService.greet());
    }
}

A unit test of a @Service in a reactive application is not much different:

@ExtendWith(SpringExtension.class)
@TestInstance(Lifecycle.PER_CLASS)
@Import({ MessageService.class, SecurityConf.class })
@ImportAutoConfiguration(AuthenticationFactoriesTestConf.class)
class MessageServiceUnitTest {
    @Autowired
    MessageService messageService;
    
    @MockBean
    ReactiveJwtDecoder jwtDecoder;

    @Test
    void givenSecurityContextIsEmpty_whenGreet_thenThrowsAuthenticationCredentialsNotFoundException() {
        assertThrows(AuthenticationCredentialsNotFoundException.class, () -> messageService.greet()
            .block());
    }

    @Test
    @WithAnonymousUser
    void givenUserIsAnonymous_whenGreet_thenThrowsClassCastException() {
        assertThrows(ClassCastException.class, () -> messageService.greet()
            .block());
    }

    @Test
    @WithJwt("ch4mpy.json")
    void givenUserIsCh4mpy_whenGreet_thenReturnGreetingWithPreferredUsernameAndAuthorities() {
        assertEquals("Hello ch4mpy! You are granted with [admin, ROLE_AUTHORIZED_PERSONNEL].", 
          messageService.greet().block());
    }

    @Test
    @WithMockUser(authorities = { "admin", "ROLE_AUTHORIZED_PERSONNEL" }, username = "ch4mpy")
    void givenSecurityContextIsPopulatedWithUsernamePasswordAuthenticationToken_whenGreet_thenThrowsClassCastException() {
        assertThrows(ClassCastException.class, () -> messageService.greet().block());
    }
}

4.7. JUnit 5 @ParametrizedTest

JUnit 5 allows to define tests that run several times with different values for a parameter. This parameter can be the mocked Authentication to put in the security context.

@WithMockAuthentication builds the authentication instance independently of Spring context, which makes it very easy to use in parametrized tests:

@ParameterizedTest
@AuthenticationSource({
        @WithMockAuthentication(authorities = { "admin", "ROLE_AUTHORIZED_PERSONNEL" }, name = "ch4mpy"),
        @WithMockAuthentication(authorities = { "uncle", "PIRATE" }, name = "tonton-pirate") })
void givenUserIsAuthenticated_whenGetGreet_thenOk(@ParameterizedAuthentication Authentication auth) throws Exception {
    final var greeting = "Whatever the service returns";
    when(messageService.greet()).thenReturn(greeting);

    api.perform(get("/greet"))
        .andExpect(status().isOk())
        .andExpect(content().string(greeting));

    verify(messageService, times(1)).greet();
}

The points to note in the code above are the following:

  • use @ParameterizedTest instead of @Test
  • decorate the test with @AuthenticationSource containing an array of all the @WithMockAuthentication to use
  • add a @ParameterizedAuthentication parameter to the test method

Because @WithJwt uses a bean from the application context to build Authentication instances, we have a little more to do:

@TestInstance(Lifecycle.PER_CLASS)
class MessageServiceUnitTest {

    @Autowired
    WithJwt.AuthenticationFactory authFactory;

    private Stream<AbstractAuthenticationToken> allIdentities() {
        final var authentications = authFactory.authenticationsFrom("ch4mpy.json", "tonton-pirate.json").toList();
        return authentications.stream();
    }

    @ParameterizedTest
    @MethodSource("allIdentities")
    void givenUserIsAuthenticated_whenGreet_thenReturnGreetingWithPreferredUsernameAndAuthorities(@ParameterizedAuthentication Authentication auth) {
        final var jwt = (JwtAuthenticationToken) auth;
        final var expected = "Hello %s! You are granted with %s.".formatted(jwt.getTokenAttributes().get(StandardClaimNames.PREFERRED_USERNAME), auth.getAuthorities());
        assertEquals(expected, messageService.greet());
    }
}

Our checklist when using @ParameterizedTest with @WithJwt should be the following:

  • decorate the test class with @TestInstance(Lifecycle.PER_CLASS)
  • autowire the WithJwt.AuthenticationFactory
  • define a method returning a stream of the authentication to use, using the authentication factory for each
  • use @ParameterizedTest instead of @Test
  • decorate the test with @MethodSource referencing the method defined above
  • add a @ParameterizedAuthentication parameter to the test method

5. Integration Testing With Mocked Authorizations

We’ll write Spring Boot integration tests with @SpringBootTest so that Spring wires actual components together. To keep using mocked identities, we’ll use it with MockMvc or WebTestClient. The tests themself and options to populate the test security context with mocked identities are the same as for unit tests. Only test setup changes:

  • No more components mock nor argument matcher
  • We’ll use @SpringBootTest(webEnvironment = WebEnvironment.MOCK) instead of @WebMvcTest or @WebFluxTest. The MOCK environment is the best match for mocked authorizations with MockMvc or WebTestClient
  • decorate explicitly test class with @AutoConfigureMockMvc or @AutoConfigureWebTestClient for MockMvc or WebTestClient injection

Here is the skeleton for a Spring Boot servlet integration test:

@SpringBootTest(webEnvironment = WebEnvironment.MOCK)
@AutoConfigureMockMvc
class ServletResourceServerApplicationTests {
    @Autowired
    MockMvc api;
    
    // Test structure and mocked identities options are the same as seen before in unit tests
}

And this is its equivalent in a reactive application:

@SpringBootTest(webEnvironment = WebEnvironment.MOCK)
@AutoConfigureWebTestClient
class ReactiveResourceServerApplicationTests {
    @Autowired
    WebTestClient api;
    
    // Test structure and mocked identities options are the same as seen before in unit tests
}

Of course, this kind of integration test saves the configuration of mocks, argument captors, etc., but it is also slower and much more fragile than unit tests. We should use it with caution, maybe with lower coverage than @WebMvcTest or @WebFluxTest, just to assert that auto-wiring and inter-component communication work.

6. To Go Further

So far, we tested resource servers secured with a JWT decoder, which have JwtAuthenticationToken instances in the security context. We only ran automated tests with mocked HTTP requests without involving any authorization server in the process.

6.1. Testing With Any Type of OAuth2 Authentication

As seen earlier, Spring OAuth2 security context can hold other types of Authentication, in which case, we should use other annotations, request post-processors or mutators in tests:

  • By default, resource servers with token introspection have BearerTokenAuthentication instances in their security context, and tests should use @WithOpaqueTokenopaqueToken(), or mockOpaqueToken()
  • Clients with oauth2Login(), usually have an OAuth2AuthenticationToken in their security context and we’d use @WithOAuth2Login@WithOidcLoginoauth2Login()oidcLogin()mockOAuth2Login() or mockOidcLogin()
  • Suppose we explicitly configure a custom Authentication type with http.oauth2ResourceServer().jwt().jwtAuthenticationConverter(…) or whatever. In that case, we could have to provide our own unit test tooling, which is not that complicated when using spring-addons implementations as a sample. The same Github repo also contains samples with custom Authentication and dedicated test annotation

6.2. Running the Sample Applications

The sample projects contain properties for the master realm of a Keycloak instance running at https://localhost:8443. Using any other OIDC authorization server would require no more than adapting issuer-uri property and the authorities mapper in Java config: change realmRoles2AuthoritiesConverter bean to map authorities from the private claim(s) the new authorization server puts roles into.

For more details about Keycloak setup, refer to the official getting started guides. The one for standalone zip distribution might be the easiest to start with.

To set up a local Keycloak instance with TLS using a self-signed certificate, this GitHub repo could be super useful.

The authorization server should have a minimum of:

  • Two declared users, one being granted the ROLE_AUTHORIZED_PERSONNEL and not the other
  • A declared client with authorization code flow enabled for tools like Postman to get access tokens on behalf of those users

7. Conclusion

In this article, we explored two options for unit and integration testing Spring OAuth2 access control rules with mocked identities in both servlet and reactive applications:

  • MockMvc request post-processors and WebTestClient mutators from spring-security-test
  • OAuth2 test annotations from spring-addons-oauth2-test

We also saw that we could test @Controllers with MockMvc request post-processors, WebTestClient mutators, or annotations. However, only the latter enables us to set the security context when testing other types of components.

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)
2 Comments
Oldest
Newest
Inline Feedbacks
View all comments