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. Overview

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

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

2. Why Use 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 access control rules defined with 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)
  • use a JwtAuthenticationConverter bean configured to use preferred_username claim as principal name and Keycloak realm_roles as authorities source
  • 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)
  • expose a /greet endpoint accessible to any authenticated user
  • use configuration to secure /secured-route (with requestMatcher and pathMatcher for servlet and reactive app, respectively)
  • use method annotation to secure /secured-method
  • delegate message generation to MessageService (which we’ll mock during @Controller unit tests)
  • secure a method of MessageService with @PreAuthorize
  • in the @Service, extract data from the JwtAuthenticationToken in the security context

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

In this article, we’ll focus on testing access control rules defined in the specs above 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.

4. Unit Testing With Mocked Authorizations

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, as well 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. 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.2. 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.

First, we declare a static import of  jwt request post-processor for MockMvc:

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

Then we activate and customize it by calling with(jwt()) on MockHttpServletRequestBuilder and asserting that the result status matches our specifications, depending on the user identity configured with the jwt() post-processor.

Let’s first see how to assert that MockMvc returns 401 when we mock an unauthorized request and how to mock OAuth2 authorization with jwt() request post-processor for the request to be successful:

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

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

    mockMvc.perform(get("/greet").with(jwt().authorities(List.of(new SimpleGrantedAuthority("admin"),
      new SimpleGrantedAuthority("ROLE_AUTHORIZED_PERSONNEL")))
        .jwt(jwt -> jwt.claim(StandardClaimNames.PREFERRED_USERNAME, "ch4mpy"))))
        .andExpect(status().isOk())
        .andExpect(content().string(greeting));

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

Then we can check that, on an endpoint implementing Role Based Access Control, MockMvc requests actually return 401 when not authorized, 200 when configured with expected authorities, and 403 when authorized but expected authorities are missing:

@Test
void givenRequestIsAnonymous_whenGetSecuredRoute_thenUnauthorized() throws Exception {
    mockMvc.perform(get("/secured-route").with(anonymous()))
        .andExpect(status().isUnauthorized());
}

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

    mockMvc.perform(get("/secured-route").with(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(jwt().authorities(new SimpleGrantedAuthority("admin"))))
        .andExpect(status().isForbidden());
}

4.3. 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.

First, we declare a static import of mockJwt() mutator for WebTestClient:

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

Then we activate it by calling mutateWith(mockJwt()) on WebTestClient and assert that the result status matches our specifications.

Let’s first see how to assert that WebTestClient returns 401 when a request is anonymous and how to mock an OAuth2 authentication with mockJwt() mutator:

@Test
void givenRequestIsAnonymous_whenGetGreet_thenUnauthorized() throws Exception {
    webTestClient.mutateWith(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(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();
}

Then we can check that, on an endpoint implementing Role Based Access Control, WebTestClient requests actually return 401 when not authorized, 200 when configured with expected authorities, and 403 when authorized but expected authorities are missing:

@Test
void givenRequestIsAnonymous_whenGetSecuredRoute_thenUnauthorized() throws Exception {
    webTestClient.mutateWith(mockAuthentication(ANONYMOUS))
        .get()
        .uri("/secured-route")
        .exchange()
        .expectStatus()
        .isUnauthorized();
}

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

    webTestClient.mutateWith(mockJwt().authorities(new SimpleGrantedAuthority("ROLE_AUTHORIZED_PERSONNEL")))
        .get()
        .uri("/secured-route")
        .exchange()
        .expectStatus()
        .isOk()
        .expectBody(String.class)
        .isEqualTo(secret);
}

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

4.4. 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.1.10</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.5. 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)
@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)
@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.6. 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 @WithOpaqueToken, opaqueToken(), or mockOpaqueToken()
  • Clients with oauth2Login(), usually have an OAuth2AuthenticationToken in their security context and we’d use @WithOAuth2Login, @WithOidcLogin, oauth2Login(), 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.

As always, we can find this source code over on GitHub.

Course – LSS (cat=Security/Spring Security)

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
res – Security (video) (cat=Security/Spring Security)
2 Comments
Oldest
Newest
Inline Feedbacks
View all comments
Comments are open for 30 days after publishing a post. For any issues past this date, use the Contact form on the site.