Giter Club home page Giter Club logo

spring-addons's Introduction

Ease OAuth2 / OpenID Configuration & Tests in Spring Boot 3

Useful links

Breaking News

Just added a Sponsor this project link to the repo ;-)

The OAuth2 BFF tutorial is now on Baeldung. It was deeply refreshed in the process and now contains samples for Angular, React (Next.js) and Vue (Vite).

In 7.6.0, the experimental support for RestClient and WebClient builders as well as @HttpExchange (the successor of @FeignClient) is moved to a dedicated starter: spring-addons-starter-rest. As a reminder, it helps to get pre-configured client builders and @HttpExchange proxies with this clients

7.5.0 comes with an important refactoring of the way JWT decoder(s) configuration is resolved. This greatly eases "dynamic" multi-tenant scenarios implementation. The only noticeable breaking change is the removal of SpringAddonsOidcProperties::getOpProperties. This feature is now the responsibility of the newly introduced OpenidProviderPropertiesResolver. The default implementation resolves properties with an exact match on issuer (just as getOpProperties was doing). As usual, auto-configured bean backs-off if you expose one to use another properties resolving strategy.

Important warning for those using @WithJwt (and since 7.3.0, @WithMockJwtAuth) but not spring-addons-starter-oidc: you should expose your JWT converter as a bean. See spring-addons-oauth2-test README for details.

With spring-addons-starter-oidc, you might need 0 Java conf, even in scenarios like:

  • accepting tokens issued by several trussted authorization servers
  • mapping authorities from a variety of claims
  • needing custom OAuth2 redirection URI or HTTP status
  • having per environment CORS configuration (not allowing the same origins in staging and prod for instance)
  • exposing CSRF token as a cookie accessible to a single-page application
  • logging out from an authorization server not strictly implementing RP-Initiated Logout (case of Auth0 and Amazon Cognito for instance)
  • adding extra parameters to authorization or token requests (like the audience required by Auth0)

Unit & Integration Testing With Security

Testing access control requires to configure the test security context. For that, spring-security-test provides with MockMvc request post-processors and WebTestClient mutators, but this can work only in the context of a request, which limits its usage to controllers.

To test any type of @Component (@Controller, off course, but also @Service and @Repository) there are only two options:

  • build tests security context by yourself and populate it with stubbed / mocked authentications
  • use annotations to do it for you (this is where spring-addons-oauth2-test jumps in)

Useful resources:

spring-addons's People

Contributors

apucher avatar ch4mpy avatar clockrun avatar giannettagerardo avatar giovannicandido avatar kriszman avatar kwonglau avatar lartiquel avatar lmller avatar mmiikkkkaa avatar nexus061 avatar ptemplier avatar tvogel8570 avatar weehongayden avatar yennor avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

spring-addons's Issues

Add some sample for Feign client

I've added some Feign clients in my app to possibly other applications using this service with calling the provided clients.
How it's possible to use mockKeycloak for the integration tests of these clients?

Error when running tests: class file has wrong version 55.0, should be 52.0

I'm trying to implement a simple test using the add-on:

package com.example.controllers;

import com.c4_soft.springaddons.security.oauth2.test.annotations.keycloak.WithMockKeycloakAuth;
import com.c4_soft.springaddons.security.oauth2.test.mockmvc.MockMvcSupport;
import com.c4_soft.springaddons.security.oauth2.test.mockmvc.keycloak.ServletKeycloakAuthUnitTestingSupport;
import com.example.config.KeycloakSecurityConfig;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Import;

import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@WebMvcTest(controllers = TestController.class)
@Import({
        ServletKeycloakAuthUnitTestingSupport.UnitTestConfig.class,
        KeycloakSecurityConfig.class})
class TestControllerTest {

    @Autowired
    MockMvcSupport mockMvc;

    @Autowired
    ServletKeycloakAuthUnitTestingSupport keycloak;

    @Test
    @WithMockKeycloakAuth
    public void whenAuthenticatedWithoutTokenThenSecuredRouteIsForbidden() throws Exception {
        mockMvc.get("/test/admin").andExpect(status().isForbidden());
    }
}

The pom.xml has the following dependencies:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.3.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>spring-demo</artifactId>
    <version>0.0.1</version>
    <name>spring-demo</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>8</java.version>
        <keycloak.version>11.0.2</keycloak.version>
        <spring.boot.starter.securiry.version>2.3.3.RELEASE</spring.boot.starter.securiry.version>
        <junit.jupiter.version>5.7.0</junit.jupiter.version>
	<springaddons.version>2.3.4</springaddons.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.keycloak.bom</groupId>
                <artifactId>keycloak-adapter-bom</artifactId>
                <version>${keycloak.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.keycloak</groupId>
            <artifactId>keycloak-spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
            <version>${spring.boot.starter.securiry.version}</version>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>${junit.jupiter.version}</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>com.c4-soft.springaddons</groupId>
            <artifactId>spring-security-oauth2-addons</artifactId>
            <version>${springaddons.version}</version>
        </dependency>
        <dependency>
            <groupId>com.c4-soft.springaddons</groupId>
            <artifactId>spring-security-oauth2-test-addons</artifactId>
            <version>${springaddons.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.c4-soft.springaddons</groupId>
            <artifactId>spring-security-oauth2-test-webflux-addons</artifactId>
            <version>${springaddons.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.c4-soft.springaddons</groupId>
            <artifactId>spring-security-oauth2-test-webmvc-addons</artifactId>
            <version>${springaddons.version}</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

When running mvn clean test I get the error:

[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:testCompile (default-testCompile) on project spring-demo: Compilation failure
[ERROR] /Users/serguei/projects/github/spring-demo/src/test/java/com/example/controllers/TestControllerTest.java:[3,74] cannot access com.c4_soft.springaddons.security.oauth2.test.annotations.keycloak.WithMockKeycloakAuth
[ERROR]   bad class file: /Users/serguei/.m2/repository/com/c4-soft/springaddons/spring-security-oauth2-test-addons/2.3.4/spring-security-oauth2-test-addons-2.3.4.jar(com/c4_soft/springaddons/security/oauth2/test/annotations/keycloak/WithMockKeycloakAuth.class)
[ERROR]     class file has wrong version 55.0, should be 52.0
[ERROR]     Please remove or make sure it appears in the correct subdirectory of the classpath.

The controller under test looks like this:

@RestController
@RequestMapping("/test")
public class TestController {

    @RequestMapping(value = "/anonymous", method = RequestMethod.GET)
    public ResponseEntity<String> getAnonymous() {
        return ResponseEntity.ok("Hello Anonymous");
    }

    @RequestMapping(value = "/user", method = RequestMethod.GET)
    public ResponseEntity<String> getUser() {
        return ResponseEntity.ok("Hello User");
    }

    @RequestMapping(value = "/admin", method = RequestMethod.GET)
    public ResponseEntity<String> getAdmin() {
        return ResponseEntity.ok("Hello Admin");
    }

    @RequestMapping(value = "/all-user", method = RequestMethod.GET)
    public ResponseEntity<String> getAllUser() {
        return ResponseEntity.ok("Hello All User");
    }

Usied java and maven versions:

mvn -v
=> Maven home: /usr/local/Cellar/maven/3.6.3_1/libexec
Java version: 1.8.0_241, vendor: Oracle Corporation, runtime: /Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre
Default locale: en_BE, platform encoding: UTF-8
OS name: "mac os x", version: "10.15.6", arch: "x86_64", family: "mac"

java -version
=> java version "1.8.0_241"
Java(TM) SE Runtime Environment (build 1.8.0_241-b07)
Java HotSpot(TM) 64-Bit Server VM (build 25.241-b07, mixed mode)

javac -version
=> javac 1.8.0_241

What am I missing?

Have one CORS config per CORS path

Same CORS config is applied to all CORS path, wich is limited and doesn't match to Spring model.

Would be better to have an array of CORS configuration properties, one per CORS path.

Client roles mock

I can't figure out how to mock client roles with your library.

Client roles mapper is configured resource_access.${client_id}.roles

Client roles are working properly within application, I just can't make it work in mocked test and I get 403 response.

I will attach sample token, config and custom annotation I use for test down.

Token:

eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJNcEtoMFhDMVV2N1dPbzlpVF9PNFhLYllDVmpyMk9MbFdrTDl5Q2xYREhBIn0.eyJleHAiOjE2MzEwNDc1OTIsImlhdCI6MTYzMTA0NzI5MiwianRpIjoiNWRjZjljMTYtZTlhNS00NWUxLWExZDEtYzE3NjgwOGY2Y2E1IiwiaXNzIjoiaHR0cHM6Ly9rYy5kZXYuYWNlLWVjcy5jb21wdXRlcnJvY2suY29tL2F1dGgvcmVhbG1zL0FDRS1FQ1MiLCJhdWQiOiJhY2NvdW50Iiwic3ViIjoiMWFiNDEyMTItN2IwYS00OTlhLTlkNzQtNmI2YjY5ZjZjZDY0IiwidHlwIjoiQmVhcmVyIiwiYXpwIjoiYWNlLWVsbGEtZWNvc3lzdGVtIiwic2Vzc2lvbl9zdGF0ZSI6ImI3MTQ4YjI3LTFmYWEtNDY2Ni05MzZjLTMzNTEwN2MzOTk1MyIsImFjciI6IjEiLCJhbGxvd2VkLW9yaWdpbnMiOlsiaHR0cDovL2xvY2FsaG9zdDozMDAwIiwiaHR0cHM6Ly9lbGxhLmRldi5hY2UtZWNzLmNvbXB1dGVycm9jay5jb20iXSwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbIm9mZmxpbmVfYWNjZXNzIiwidW1hX2F1dGhvcml6YXRpb24iLCJBRE1JTiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImFjZS1lbGxhLWVjb3N5c3RlbSI6eyJyb2xlcyI6WyJBRE1JTklTVFJBVE9SIiwiRFJJVkVSIl19LCJhY2NvdW50Ijp7InJvbGVzIjpbIm1hbmFnZS1hY2NvdW50IiwibWFuYWdlLWFjY291bnQtbGlua3MiLCJ2aWV3LXByb2ZpbGUiXX19LCJzY29wZSI6Im9wZW5pZCBwcm9maWxlIGVtYWlsIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsIm5hbWUiOiJDdmV0YW4gU2ltc2ljIiwicHJlZmVycmVkX3VzZXJuYW1lIjoiY3ZldGFuIiwiZ2l2ZW5fbmFtZSI6IkN2ZXRhbiIsImZhbWlseV9uYW1lIjoiU2ltc2ljIiwiZW1haWwiOiJjdmV0YW4uc2ltc2ljQGNvbXB1dGVycm9jay5jb20ifQ.ab4FHWdvRi0Xd4z9yq_YoTdV2ObwY92FJROLZ7Qqym08qVAJ_7RfqezwP5WfK-wWWo4LzPGWXNnoqs8nG9rA2HlEcaxMS1ovcBos32z-0JxaRgR4z1AhJ1QIgwN9xF89XuodumMyBdG6Uo0QyyAbbl4sWU8RqPKmkTY1xqk0R-8w2obMMaUBGKpc0c9pzLlf2zFvZDvEPQIwrhgC5cSNyUg6zlH-UlRkWm6jzmaEtF0KaSFwbE_ZefxAsNXBtVBDYCacbHwKNY68yyOL104ZoY6ETOMM6CxzuaiAvnghOki2ZWXX6d6Kp2fMM5LcfUdbsQC7WYP7139syGisRUfaCQ

Spring security config:

package de.ace.eaf.config;

import de.ace.eaf.model.enums.UserRole;
import org.keycloak.adapters.springsecurity.KeycloakConfiguration;
import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationProvider;
import org.keycloak.adapters.springsecurity.config.KeycloakWebSecurityConfigurerAdapter;
import org.keycloak.adapters.springsecurity.management.HttpSessionManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.authority.mapping.SimpleAuthorityMapper;
import org.springframework.security.web.authentication.session.NullAuthenticatedSessionStrategy;
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;


@KeycloakConfiguration
public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {

    private static final String[] AUTH_WHITELIST = {
        // -- swagger ui
        "/v3/api-docs/**",
        "/configuration/ui",
        "/swagger-resources/**",
        "/configuration/security",
        "/swagger-ui/**",
        "/webjars/**",
        // -- actuator
        "/actuator/info",
        "/actuator/health"
    };


    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) {
        SimpleAuthorityMapper simpleAuthorityMapper = new SimpleAuthorityMapper();
        simpleAuthorityMapper.setPrefix("ROLE_");
        simpleAuthorityMapper.setConvertToUpperCase(true);

        KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
        keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(simpleAuthorityMapper);
        auth.authenticationProvider(keycloakAuthenticationProvider);
    }

    @Override
    protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
        return new NullAuthenticatedSessionStrategy();
    }


    @Bean
    @Override
    @ConditionalOnMissingBean(HttpSessionManager.class)
    protected HttpSessionManager httpSessionManager() {
        return new HttpSessionManager();
    }


    @Override
    protected void configure(HttpSecurity http) throws Exception {
        super.configure(http);
        http
                .csrf().disable()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                .mvcMatchers(HttpMethod.OPTIONS, "/**").permitAll()
                .mvcMatchers(AUTH_WHITELIST).permitAll()
                .mvcMatchers("/v1/**").hasAnyRole(UserRole.allRoles())
                .anyRequest().denyAll();
    }

}

Annotation:

package de.ace.eaf.config.annotateduser;


import com.c4_soft.springaddons.security.oauth2.test.annotations.ClaimSet;
import com.c4_soft.springaddons.security.oauth2.test.annotations.IdTokenClaims;
import com.c4_soft.springaddons.security.oauth2.test.annotations.OidcStandardClaims;
import com.c4_soft.springaddons.security.oauth2.test.annotations.StringClaim;
import com.c4_soft.springaddons.security.oauth2.test.annotations.keycloak.WithMockKeycloakAuth;

import java.lang.annotation.*;

@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@WithMockKeycloakAuth(
        id = @IdTokenClaims(sub = "1ab41212-7b0a-499a-9d74-6b6b69f6cd64"),
        oidc = @OidcStandardClaims(
                givenName = "Cvetan",
                familyName = "Simsic",
                email = "[email protected]",
                emailVerified = true,
                preferredUsername = "cvetan"),
        otherClaims = @ClaimSet(stringClaims = @StringClaim(
                name = "resource_access.ace-ella-ecosystem.roles",
                value = "[\"ADMINISTRATOR\",\"DRIVER\"]}")))
public @interface WithAdminKeycloakAuth {
}

I would appreciate if you instruct me if I did something wrong. Thanks

KeycloakAuthenticationToken null while @Preauthorize works

Hi,
I used your lib in my Spring Project. Therefor I disabled all Filters in my @AutoConfigureMockMvc(addFilters = false) so Spring does not try to contact a running external keycloak instance.

@SpringBootTest
//Configure Mockmvc with the app-context
@AutoConfigureMockMvc(addFilters = false)
//Set profile test active
@ActiveProfiles("test")
//@AutoConfigureTestDatabase(replace= AutoConfigureTestDatabase.Replace.NONE)
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
//Enable REstDocs
@ExtendWith(RestDocumentationExtension.class)
@AutoConfigureRestDocs
@Import({
        ServletKeycloakAuthUnitTestingSupport.UnitTestConfig.class,
        SpringSecurityConfiguration.class })

My Test is annotated with:

   @Test
    @Order(5)
    @WithMockKeycloakAuth(authorities = "ROLE_owner",
    id = @IdTokenClaims(sub = "abc"))

This is my test annotation. Thing is that the role check in my pre authorize works while my KeycloakAuthenticationToken is always null.

  @PreAuthorize("hasRole('owner')")
    @ApiOperation("Create a new product")
    @PostMapping(value = "", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
    public Product createProduct(KeycloakAuthenticationToken authentication, @ApiParam(value = "Object with new product attributes") @Valid @RequestBody ProductDto newProductDto) {

Is it correct that this kind of validation only works with a running Keycloak instance?

Thx in advance and for your lib so far

Rename Keycloack to Keycloak

Hello, I have started using your utils and I am very happy with it. Thank you for sharing your code.

At many places Keycloak is wrongly spelled as Keycloack. I do not know whether you are aware of this. You may keep this for sake of backwards compatibility, but I could not resist to point this out :) :)

Use configured `GrantedAuthoritiesMapper` when parsing `@WithMockKeycloakAuth` realm and resource-access roles

Is your feature request related to a problem? Please describe.
When configuring Keycloak roles (be it realm roles oder client roles) in the token, the ROLE_ prefix is not set automatically, but Spring Boot prefixes the authorities (aka roles) with ROLE_ internally.

Describe the solution you'd like
I would like the library to automatically add the ROLE_ prefix to the authorities (or at least make the prefix configurable in the annotation).

Describe alternatives you've considered
An alternative is to manually add the ROLE_ prefix to the roles/authorities. But since this is an implementation detail of Spring Boot, it does not feel natural to add this manually.

Additional context
None

Roles vs authorities in @WithMockKeycloackAuth

Hello, I have started using your utils and I am very happy with it. Thank you for sharing your code. I experience the following difficulty:

  • In @WithMockKeycloackAuth all roles get prefixed with ROLE_. For my purpose this is unwanted behavior, since I would like pass authorities like true authorities w/o the ROLE_ part. The ROLE_ prefix is added in roles() method of KeycloakAuthenticationTokenTestingBuilder and I do not see away to suppress this behavior. The docs at the top of the WithMockKeycloackAuth class suggest that the ROLE_ prefix should be added manually (which is not the case). Also, the default OIDC fields "offline_access", "uma_authorization" seem incompatible with this approach.
 * Sample usage:
 *
 * <pre>
 * &#64;Test
 * &#64;WithMockKeycloackAuth({"ROLE_USER", "ROLE_ADMIN"})
 * public void test() {
 *     ...
 * }
 * </pre>

I see two solutions to this problem:

  1. Remove automatic addition of the ROLE_ prefix. Roles passed are interpreted as-is.
  2. The @WithMockKeycloackAuth handles an additional authorities parameter. Names passed in via this prop are added to the Auth object w/o the ROLE_ prefix.

I base the distinction between roles and authorizations on the Spring Security naming convention (see https://www.baeldung.com/spring-security-granted-authority-vs-role)

If needed I can do a proposal in a PR. Just let me know whether you have time to work on this.

Principal

Hi how would i inject the principal in the testcase?

On my controller i have something like:

@GetMapping
fun something(principal: UsernamePasswordAuthenticationToken) {
   ...
}

where UsernamePasswordAuthenticationToken is type of org.springframework.security.core.Authentication

In my security config i have registered a converter which transforms the jwt to this token.
Would be glad if you could come up with a hint on this.

When i write my test like:

    @Test
    @WithMockAuthentication(UsernamePasswordAuthenticationToken::class)
    fun `should do`() {
        client.mutateWith(mockAuthentication(UsernamePasswordAuthenticationToken::class.java)) ......
    }

Where can i then influence the mock of UsernamePasswordAuthenticationToken i am using. I need to set the .details field with an AppUser because i am using a Converter in my security config like i stated above.

When i debug it i see that i am getting a UsernamePasswordAuthenticationToken$MockitoMock$... and the fields are set as follows:

principal = null
credentials = null
authorities = null
details = null
authenticated = false

Move samples to dedicated modules

Samples are currently mixed in test sources. This has the benefit of keeping jars small, but comes with quite a few drawbacks:

  • it's hard to figure out what dependencies are pulled for samples or lib code
  • users frequently just don't find samples
  • non trivial spring-test conf is required just because several spring-boot apps share the same module

Let's create a samples module with a sub-module for each sample

Seems only support JDK version 17+

Seems only support JDK version 17+
Tried with java 11 does not work, get following exception on startup.

Caused by: java.lang.UnsupportedClassVersionError: com/c4_soft/springaddons/security/oauth2/config/SpringAddonsSecurityProperties has been compiled by a more recent version of the Java Runtime (class file version 61.0), this version of the Java Runtime only recognizes class file versions up to 55.0
at java.base/java.lang.ClassLoader.defineClass1(Native Method)
at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:1017)

`Jwt.id` (jti) isn't been populated with `OpenIdClaims.jti` value

Describe the bug

Jwt.id (jti) isn't been populated with OpenIdClaims.jti value

Code sample

@WithMockJwtAuth(
	claims=OpenIdClaims(
		sub="00000000-0000-0000-0000-000000000000", jti="00000000-0000-0000-0000-000000000000", sessionState="00000000-0000-0000-0000-000000000000",
		otherClaims=Claims(stringClaims=[StringClaim(name="id_institucional", value="aa91613e-34b2-488a-94ef-09ac5bef1904")])
	)
)
fun sampleTest() {}

If I check the id of Jwt, it appears null:

val authenticationJwt = authentication as JwtAuthenticationToken
val principal = authenticationJwtprincipal as Jwt

principal.id == null

Tested in

  • Spring Boot 2.6.1
  • com.c4-soft.springaddons:spring-security-oauth2-test-webmvc-addons:3.1.7-jdk17
  • org.springframework.boot:spring-boot-starter-oauth2-resource-server (the version is based on Spring Boot version)

Expected behavior
Jwt.id be populated based in jti value.

TestRestTemplate support

Is your feature request related to a problem? Please describe.
When writing integration tests with keycloak auth enabled, the only approach I find is to disable authentication entirely.
It would be nice if we could inject auth token to TestRestTemplate

Describe the solution you'd like
The same integration we have with MockMvc in TestRestTemplate

Describe alternatives you've considered
No alterantives

Multitenancy with spring boot and keycloak

Hi guys

I implemented multi-tenancy support for Spring Boot and Keycloak following this guide, but after that my tests doesn't run correctly anymore, I get HTTP 403.

@WithMockKeycloakAuth(
            authorities = "user",
            otherClaims = @ClaimSet(
                    stringClaims = @StringClaim(name = "ssn", value = "03058241111")
            )
    )

I tried to debug through the code described in the blog and this spring-addons lib, but didn't manage to find a clue.

Any help, advice or pointer in right direction would be highly appreciated.

Thanks in advance!

resource-server_with_jwtauthenticationtoken doesn't work

Describe the bug
I tried to run your resource-server_with_jwtauthenticationtoken demo, with mvn spring-boot:run, but when I try to get /greet I've got an exception:

[nio-8080-exec-9] o.s.security.web.FilterChainProxy : Securing GET /greet
[nio-8080-exec-9] o.s.security.web.FilterChainProxy : Invoking DisableEncodeUrlFilter (1/15)
[nio-8080-exec-9] o.s.security.web.FilterChainProxy : Invoking ChannelProcessingFilter (2/15)
[nio-8080-exec-9] o.s.s.w.a.c.ChannelProcessingFilter : Request: filter invocation [GET /greet]; ConfigAttributes: [REQUIRES_INSECURE_CHANNEL]
[nio-8080-exec-9] o.s.security.web.FilterChainProxy : Invoking WebAsyncManagerIntegrationFilter (3/15)
[nio-8080-exec-9] o.s.security.web.FilterChainProxy : Invoking SecurityContextPersistenceFilter (4/15)
[nio-8080-exec-9] s.s.w.c.SecurityContextPersistenceFilter : Set SecurityContextHolder to empty SecurityContext
[nio-8080-exec-9] o.s.security.web.FilterChainProxy : Invoking HeaderWriterFilter (5/15)
[nio-8080-exec-9] o.s.security.web.FilterChainProxy : Invoking CorsFilter (6/15)
[nio-8080-exec-9] o.s.security.web.FilterChainProxy : Invoking CsrfFilter (7/15)
[nio-8080-exec-9] o.s.security.web.csrf.CsrfFilter : Did not protect against CSRF since request did not match And [CsrfNotRequired [TRACE, HEAD, GET, OPTIONS], Not [Or [org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer$BearerTokenRequestMatcher@5b9499fe]]]
[nio-8080-exec-9] o.s.security.web.FilterChainProxy : Invoking LogoutFilter (8/15)
[nio-8080-exec-9] o.s.s.w.a.logout.LogoutFilter : Did not match request to Ant [pattern='/logout', POST]
[nio-8080-exec-9] o.s.security.web.FilterChainProxy : Invoking BearerTokenAuthenticationFilter (9/15)
[nio-8080-exec-9] .o.s.r.w.BearerTokenAuthenticationFilter : Did not process request since did not find bearer token
[nio-8080-exec-9] o.s.security.web.FilterChainProxy : Invoking RequestCacheAwareFilter (10/15)
[nio-8080-exec-9] o.s.security.web.FilterChainProxy : Invoking SecurityContextHolderAwareRequestFilter (11/15)
[nio-8080-exec-9] o.s.security.web.FilterChainProxy : Invoking AnonymousAuthenticationFilter (12/15)
[nio-8080-exec-9] o.s.s.w.a.AnonymousAuthenticationFilter : Set SecurityContextHolder to AnonymousAuthenticationToken [Principal=anonymousUser, Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=0:0:0:0:0:0:0:1, SessionId=null], Granted Authorities=[ROLE_ANONYMOUS]]
[nio-8080-exec-9] o.s.security.web.FilterChainProxy : Invoking SessionManagementFilter (13/15)
[nio-8080-exec-9] o.s.s.w.session.SessionManagementFilter : Request requested invalid session id 5B6D1B1AEB30B6F27F9CB44FEAE5DFDB
[nio-8080-exec-9] o.s.security.web.FilterChainProxy : Invoking ExceptionTranslationFilter (14/15)
[nio-8080-exec-9] o.s.security.web.FilterChainProxy : Invoking FilterSecurityInterceptor (15/15)
[nio-8080-exec-9] edFilterInvocationSecurityMetadataSource : Did not match request to Ant [pattern='/actuator/health/readiness'] - [permitAll] (1/4)
[nio-8080-exec-9] edFilterInvocationSecurityMetadataSource : Did not match request to Ant [pattern='/actuator/health/liveness'] - [permitAll] (2/4)
[nio-8080-exec-9] edFilterInvocationSecurityMetadataSource : Did not match request to Ant [pattern='/v3/api-docs/**'] - [permitAll] (3/4)
[nio-8080-exec-9] o.s.s.w.a.i.FilterSecurityInterceptor : Did not re-authenticate AnonymousAuthenticationToken [Principal=anonymousUser, Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=0:0:0:0:0:0:0:1, SessionId=null], Granted Authorities=[ROLE_ANONYMOUS]] before authorizing
[nio-8080-exec-9] o.s.s.w.a.i.FilterSecurityInterceptor : Authorizing filter invocation [GET /greet] with attributes [authenticated]
[nio-8080-exec-9] o.s.s.w.a.expression.WebExpressionVoter : Voted to deny authorization
[nio-8080-exec-9] o.s.s.w.a.i.FilterSecurityInterceptor : Failed to authorize filter invocation [GET /greet] with attributes [authenticated] using AffirmativeBased [DecisionVoters=[org.springframework.security.web.access.expression.WebExpressionVoter@1d4fb213], AllowIfAllAbstainDecisions=false]
[nio-8080-exec-9] o.s.s.w.a.ExceptionTranslationFilter : Sending AnonymousAuthenticationToken [Principal=anonymousUser, Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=0:0:0:0:0:0:0:1, SessionId=null], Granted Authorities=[ROLE_ANONYMOUS]] to authentication entry point since access is denied

org.springframework.security.access.AccessDeniedException: Access is denied

I've got a working keycloak server in http://localhost:38080 as I wrote it to my application.properties:
spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:38080/realms/mikcube
I've a role named user, I've changed NICE-GUY to user in GreetingController.

The web browser opens a simple login form instead of keycloak login form.

test running succesfully I've changed my keycloak uri in its application.properties too.
I've tried to debug it, and it reaches filterChain(), authenticationConverter(), authoritiesConverter(), corsConfigurationSource()
methods.

If I tried to get health info from actuator (localhost:8080/actuator/health/liveness, http localhost:8080/actuator/health/readiness it responds with 404.

)
Expected behavior
Authentication in my keycloak server

Keycloak auth with custom access token (or id token)

I am putting this here for reference since this feature is not documented.

The access and id tokens of WithMockKeycloakAuth can be enhanced with custom values like so:

    @Test
    @WithMockKeycloakAuth(
            name = "testuser",
            authorities = "study_es_0",
            accessToken = @WithAccessToken(email = "[email protected]"))
    public void myMethod()  {
    ....

A similar approach is possible with idToken = @WithIdToken(...).

Possibly this is something to later add to the docs.

No qualifying bean of type 'org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper' available

Hi,

When playing with the addons using annotation @WithMockKeycloakAuth I always get this :

java.lang.RuntimeException: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'com.c4_soft.springaddons.security.oauth2.test.annotations.keycloak.WithMockKeycloakAuth$Factory': Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}

I have the following class annotations :
@AutoConfigureMockMvc @ActiveProfiles("test") @SpringBootTest
My mockMvc is inserted using autowired.

Keycloak version is : 11.0.3 (with your workaround applied)

Spring boot version : 2.3.2.RELEASE

Other Spring boot dependencies :

  • spring-boot-starter-validation
  • spring-boot-starter-mail
  • spring-boot-starter-security
  • spring-boot-configuration-processor
  • spring-boot-starter-data-jdbc
  • spring-boot-starter-web

Spring addons dependencies (version 2.4.0) :

  • spring-security-oauth2-test-addons
  • spring-security-oauth2-test-webflux-addons
  • spring-security-oauth2-test-webmvc-addons
    `
    Is there something I'm doing wrong or is it a bug ?

Resource Access (aka client roles) should be mapped to authorities

Is your feature request related to a problem? Please describe.
Resource Access (aka client roles) are not mapped as authorities in the current implementation. One has to manually create the roles as authorities (as opposed to realm roles, where the roles are automatically mapped to authorities).

Code Examples:

@Test
@WithMockKeycloakAuth(
    authorities = [ "ROLE_abc" ],
    accessToken = KeycloakAccessToken(
        resourceAccess = [
            KeycloakResourceAccess(resourceId = "client-id", access = KeycloakAccess(roles = ["abc"]))
        ],
    )
)
fun test() {
    // some test
}

The KeycloakResourceAccess is not mapped as an authority automatically (again: as opposed to realm roles) - in code terms it is imho "missing" here:
WithMockKeycloakAuth.java#L111

Describe the solution you'd like
I would like the realm roles to be automatically added as authorities.

Describe alternatives you've considered
An alternative is to manually add the client role to the authorities field in the WithMockKeycloakAuth annotation, but this feels unnatural as it's somehow entered twice.

Additional context
None.

KeycloakAuthenticationToken is not added as principal in MockHttpServletRequest

Describe the bug
First of all - thanks for your amazing work!

This is a bit of a tricky issue, so I try to be concise:

  • When using the library the authentication in the SecurityContext is set just fine
  • However, injecting an Authentication/Principal object into the resource method in the controller does not work, as this object is always null
  • This is because the parameters are "automatically" set by Spring Boot using reflection - in this particular case the ServletRequestMethodArgumentResolver takes care of setting the arguments
  • The ServletRequestMethodArgumentResolver tries to set the authentication parameter by using the userPrincipal of the HttpServletRequest
  • This works just fine in production code as the SecurityContextHolderAwareRequestWrapper is used in production and this delegates to the SecurityContext in order to retrieve the userPrincipal (see SecurityContextHolderAwareRequestWrapper#123)
  • However, the test uses MockHttpServletRequest, which does not delegate to the SecurityContext, but expects the userPrincipal to be set explicitly (see: MockHttpServletRequest#1230
  • This leads to the authentication parameter being null

Code sample
Controller Code:

@RestController
class TestController() {
    @GetMapping("/method")
    fun myMethodWithAuth(
            authentication: Authentication
        ): ResponseEntity<IdentCheckStandardAuskunft> {
            // this won't work in runtime, since the authentication is null
        }
}

Test Code

@AutoConfigureMockMvc
@SpringBootTest
@ActiveProfiles("test")
class FailingTest {
    @Test
    @WithMockKeycloakAuth(
        authorities = [ "ROLE_abc" ],
        accessToken = KeycloakAccessToken(
            resourceAccess = [
                KeycloakResourceAccess(resourceId = "my-client", access = KeycloakAccess(roles = ["abc"]))
            ],
        ),
        claims = OpenIdClaims(
            otherClaims = Claims(
                stringClaims = [
                    StringClaim(name = "something", value = "different")
                ]
            )
        )
    )
    fun test() {
        mockMvc
            .perform(
                post("/method")
                    .contentType(MediaType.APPLICATION_JSON)
                    .content(
                        """
                        {"some" : "content"}
                    """.trimIndent()
                    )
            )
            .andExpectAll(
                status().isOk
            )
    }
}

Expected behavior
Expected: The authentication parameter is not null and contains the configured KeycloakAuthenticationToken object.
Actual: The authentication parameter is null and leads to a NPE.

Additional context
There is a workaround by setting the principal explicitly (using the Authentication object in the SecurityContext but this feels wrong. Full working test:

@AutoConfigureMockMvc
@SpringBootTest
@ActiveProfiles("test")
class FailingTest {
    @Test
    @WithMockKeycloakAuth(
        authorities = [ "ROLE_abc" ],
        accessToken = KeycloakAccessToken(
            resourceAccess = [
                KeycloakResourceAccess(resourceId = "my-client", access = KeycloakAccess(roles = ["abc"]))
            ],
        ),
        claims = OpenIdClaims(
            otherClaims = Claims(
                stringClaims = [
                    StringClaim(name = "something", value = "different")
                ]
            )
        )
    )
    fun test() {
        mockMvc
            .perform(
                post("/method")
                    .contentType(MediaType.APPLICATION_JSON)
                    .principal(SecurityContextHolder.getContext().authentication)
                    .content(
                        """
                        {"some" : "content"}
                    """.trimIndent()
                    )
            )
            .andExpectAll(
                status().isOk
            )
    }
}

Java version error

When I try to use this library on Java 8, appears the following code:

com/c4_soft/springaddons/security/oauth2/test/annotations/keycloak/WithMockKeycloakAuth$Factory has been compiled by a more recent version of the Java Runtime (class file version 55.0), this version of the Java Runtime only recognizes class file versions up to 52.0
java.lang.UnsupportedClassVersionError: com/c4_soft/springaddons/security/oauth2/test/annotations/keycloak/WithMockKeycloakAuth$Factory has been compiled by a more recent version of the Java Runtime (class file version 55.0), this version of the Java Runtime only recognizes class file versions up to 52.0

I'm using com.c4-soft.springaddons:spring-security-oauth2-test-webmvc-addons:2.1.0

Please, release a Java 8 compatible ou informs in somewhere that it works only with Java 11.

Document how to use this if Keycloak is on the classpath

Thank you for this project. It is just what I needed for my @WebMvcTest tests in my Spring Boot project. I am using Spring Boot 2.2.6 with keycloak-spring-boot-starter 9.0.0.

After adding:

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

to my project, I also had to do the following things:

  1. Create a custom meta-annotation for all my webmvc tests:
@Retention(RetentionPolicy.RUNTIME)
@WebMvcTest
@ContextConfiguration(classes = MyProjectRestControllerTestConfiguration.class)
@ActiveProfiles("webmvc-test")
public @interface MyProjectRestControllerTest {
    /**
     * @see WebMvcTest#value
     */
    @AliasFor(annotation = WebMvcTest.class, attribute = "value")
    Class<?>[] value() default {};

    /**
     * @see WebMvcTest#controllers
     */
    @AliasFor(annotation = WebMvcTest.class, attribute = "controllers")
    Class<?>[] controllers() default {};

}
  1. I created a custom @TestConfiguration for my test:
@TestConfiguration
@Import({KeycloakAutoConfiguration.class, WebSecurityConfiguration.class})
public class MyProjectRestControllerTestConfiguration {
    @Bean
    public KeycloakSpringBootConfigResolver keycloakSpringBootConfigResolver() {
        return new KeycloakSpringBootConfigResolver();
    }
}

Where WebSecurityConfiguration is my actual production security config class:

@KeycloakConfiguration
public class WebSecurityConfiguration extends KeycloakWebSecurityConfigurerAdapter {

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder builder) {
        KeycloakAuthenticationProvider provider = new KeycloakAuthenticationProvider();
        provider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
        builder.authenticationProvider(provider);
    }

    @Override
    protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
        return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        super.configure(http);
        http.authorizeRequests()
            .requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()
            .antMatchers("/actuator/info", "/actuator/health").permitAll()
            .anyRequest()
            .hasRole("user");
    }
}
  1. Next, I added application-webmvc-test.properties in src/test/resources:
# Properties are here for proper startup. Keycloak is not actually used in @WebMvcTest tests
keycloak.auth-server-url=http://localhost:8180/auth
keycloak.realm=pegus-digital-test
keycloak.resource=springboot-app
keycloak.public-client=true
keycloak.principal-attribute=preferred_username
  1. Finally my test self:
@MyProjectRestControllerTest(UserRestController.class)
class UserRestControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    void testOwnUserDetails() throws Exception {
        KeycloakAuthRequestPostProcessor keycloakAuthRequestPostProcessor = new KeycloakAuthRequestPostProcessor()
                .roles("user", "admin")
                .name("[email protected]")
                .idToken(idToken -> {
                    idToken.setSubject("aac53545-5d5b-4e1f-8c94-0c593d22b5a2");
                    idToken.setGivenName("Wim");
                    idToken.setFamilyName("Deblauwe");
                });
        mockMvc.perform(get("/api/users/me")
                                .with(keycloakAuthRequestPostProcessor))
               .andExpect(status().isOk())
               .andExpect(jsonPath("authorizationServerUserId").value("aac53545-5d5b-4e1f-8c94-0c593d22b5a2"))
               .andExpect(jsonPath("firstName").value("Wim"))
               .andExpect(jsonPath("lastName").value("Deblauwe"))
               .andExpect(jsonPath("email").value("[email protected]"))
               .andExpect(jsonPath("userRole").value("ADMIN"))
        ;
    }

}

Might be good to add something similar to the documentation, as most people will have Keycloak on the classpath in their project (unlike the example in your repo currently).

Java 8 Compatibility

Hi I tried running with Java 8 and I get the following -

import com.c4_soft.springaddons.security.oauth2.test.Defaults;
                                                    ^
  bad class file: C:\Users\****\.gradle\caches\modules-2\files-2.1\com.c4-soft.springaddons\spring-security-oauth2-test-addons\2.0.3\b489814912684ae9e3108e6f5759aeda854173aa\spring-security-oauth2-test-addons-2.0.3.jar(com/c4_soft/springaddons/security/oauth2/test/Defaults.class)
    class file has wrong version 55.0, should be 52.0

any chance of making your project Java 8 compatible? I checked the pom.xml and the java.version is 11

Thanks

Create JwtAuthenticationToken with custom claims

I am currently using a custom annotation in my tests that builds op @WithMockOidcId() like this:

import com.c4_soft.springaddons.security.oauth2.test.annotations.ClaimSet;
import com.c4_soft.springaddons.security.oauth2.test.annotations.StringClaim;
import com.c4_soft.springaddons.security.oauth2.test.annotations.WithMockAuthentication;
import com.c4_soft.springaddons.security.oauth2.test.annotations.WithMockOidcId;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
@WithMockOidcId(authorities = "ROLE_ADMIN",
        privateClaims = @ClaimSet(stringClaims = @StringClaim(name = "myPrivateClaim", value = "xxxxxx xxxxxx")))
public @interface WithMockCasefileAdmin {
}

However, I would need that a JwtAuthenticationToken is created (I am using Keycloak, but I don't want to depend on Keycloak specific classes in my Spring Boot application).

I know that I can create such a token via:

@WithMockAuthentication(value = JwtAuthenticationToken.class, authorities = "ROLE_ADMIN")

But I can't configure claims in the annotation. Would it be possible to add this option?

Reduce higher level modules count

Some modules could be grouped to reduce the count of sub-modules under the root one:

  • archetypes (4 of them)
  • webmvc (app configuration and unit tests)
  • webflux (app configuration and unit tests)

Question about defining Keycloak attributes using Annotations

In my Keycloak instance I have defined a couple of attributes (x_id, y_id) that get passed in the token, I can retrieve these attributes from a Map on the token (other claims):

// Where authentication is KeycloakAuthenticationToken
KeycloakPrincipal principal = (KeycloakPrincipal<KeycloakSecurityContext>)authentication.getPrincipal();
Map<String, Object> attrs = getPrincipal().getKeycloakSecurityContext().getToken().getOtherClaims();
Object xAttr = attrs.get("x_id");
Object yAttr = attrs.get("y_id");

I tried using privateClaims with a single value, but this didn't seem to work.

@WithMockKeycloakAuth(
    authorities = { "ROLE_USER" },
        oidc = @OidcStandardClaims(
        email = "[email protected]",
        preferredUsername = "foo"),
        privateClaims = @ClaimSet(stringClaims = @StringClaim(name = "x_id", value = "1")))

I'm pretty sure this is obvious, I'm just missing something. Saw a closed question about using @WithAccessToken but I could not find that annotation, I also didn't see anything under the accessToken parameter.
Using Keycloak 9.0.2, Java 11, SpringBoot 2.3.1, spring-security-oauth2-test-webmvc-addons 2.3.1

Problems with keycloak roles and authorities

Hey @ch4mpy ,

First of all, many thanks for this project! It really has made testing soooo much easier!

I've run into a small problem with authorities and roles created by the @WithMockKeycloakAuth annotation.

Regular roles work for the service

I've created a small test service that has a simple controller:

@RestController
@RequestMapping("/test")
public class TestController {

    @RolesAllowed({"ADMIN", "USER"})
    @PostMapping(value = "getroles")
    public String getRoles(@AuthenticationPrincipal KeycloakPrincipal<KeycloakSecurityContext> principal) {
        String id = principalHelper.getId(principal).get();
        Set<String> roles = principalHelper.getRoles(principal);
        return String.format("User %s has roles %s", id, String.join(",", roles));
    }
}

The service starts up and works as expected. Any keycloak token that has ADMIN or USER in the realm roles array of their JWT is allowed access to the endpoint. Any token that doesn't have one of those roles isn't. It works great.

Here's an example of the realm_access section of the payload from a test token (after being decoded using jwt.io, that is):

  "realm_access": {
    "roles": [
      "USER"
    ]
  },

The token that this section is taken from is known good; it works exactly as you would expect it to against the service.

Testing roles aren't the same

However, roles can't be tested the same way. Consider this test:

    @Test
    @WithMockKeycloakAuth(
            authorities = {
                    "ROLE_ADMIN",
                    "ROLE_USER",
            },
            id = @IdTokenClaims(sub = "asdf1234")
    )
    void test1() throws Exception {
        MvcResult result = mvc.perform(
                post("/test/getroles"))
                .andExpect(status().isOk())
                .andReturn();
        assertThat(result.getResponse().getContentAsString()).isEqualTo("User asdf1234 has roles ADMIN,USER");
    }

You would expect that the roles for the token would be ADMIN and USER, but they're ROLE_ADMIN and ROLE_USER. Consequently, this test fails because the result string ends up being User asdf1234 has roles ROLE_ADMIN,ROLE_USER.

The next natural step is to remove the ROLE_ prefix from the authorities:

    @Test
    @WithMockKeycloakAuth(
            authorities = {
                    "ADMIN",
                    "USER",
            },
            id = @IdTokenClaims(sub = "asdf1234")
    )
    void test2() throws Exception {
        MvcResult result = mvc.perform(
                post("/test/getroles"))
                .andExpect(status().isOk())
                .andReturn();
        assertThat(result.getResponse().getContentAsString()).isEqualTo("User asdf1234 has roles ADMIN,USER");
    }

However, this fails with a 403 because the RolesAllowed annotation in the controller is expecting an authority value of ROLE_ADMIN or ROLE_USER.

I've attached my demo service to this ticket. To run the tests and see them fail, run ./gradlew clean test.

keycloak-springboot-microservice.zip

Thanks!

Service Layer Authentication with Keycloak Not working

@ch4mpy
Keycloak: 14
SpringBoot: 2.5.2

SecurityConfig;

@KeycloakConfiguration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {

    private static final String[] AUTH_WHITELIST = {
            "/swagger-resources/**",
            "/swagger-ui/**",
            "/v3/api-docs/**",
            "/actuator/**"
    };

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        super.configure(http);
        // TODO: Need to reactivate CSRF. Currently blocking POST/PUT/DELETE request if enable
        http.csrf().disable();
        http.authorizeRequests()
                .antMatchers(AUTH_WHITELIST).permitAll()
                .antMatchers("/aircraft/**").hasRole(KeycloakRole.aircraft.roleReadName)
                .antMatchers("/**").authenticated();
    }
    @Bean
    @Override
    protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
        return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
    }

    @Bean
    @Primary
    public KeycloakSpringBootConfigResolver KeycloakConfigResolver() {
        return new KeycloakSpringBootConfigResolver();
    }

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) {
        KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
        keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
        auth.authenticationProvider(keycloakAuthenticationProvider);
    }

    @Bean
    @Override
    @ConditionalOnMissingBean(HttpSessionManager.class)
    protected HttpSessionManager httpSessionManager() {
        return new HttpSessionManager();
    }

}

ServiceTest:

@ExtendWith(MockitoExtension.class)
@Import(AirportServiceTest.TestConfig.class)
class AirportServiceTest {

    @Mock
    private AirportDao airportDao;

    @InjectMocks
    private AirportService airportService;


    @Test
    @WithMockKeycloakAuth(authorities = "ROLE_std_airport_read", oidc = @OidcStandardClaims(preferredUsername  = "abc"))
    public void createAirportWithoutAuthority() {
        Mockito.when(airportDao.save(any())).thenReturn(airport1);
        airportService.createAirport(airportReqDto1);
        verify(airportDao,times(1)).save(any());
    }

    @TestConfiguration(proxyBeanMethods = false)
    @EnableGlobalMethodSecurity(prePostEnabled = true)
    @Import({ AirportService.class })
    public static class TestConfig {
        @Bean
        public GrantedAuthoritiesMapper authoritiesMapper() {
            return new SimpleAuthorityMapper();
        }
    }

}

@WithMockKeycloackAuth not found

@WithMockKeycloackAuth is not found.
Added following dependency
<dependency> <groupId>com.c4-soft.springaddons</groupId> <artifactId>spring-security-oauth2-test-webmvc-addons</artifactId> <version>2.3.1</version> <scope>test</scope> </dependency>

my imports
`
import com.c4_soft.springaddons.security.oauth2.test.mockmvc.ServletUnitTestingSupport;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.keycloak.adapters.springboot.KeycloakSpringBootConfigResolver;
import org.keycloak.adapters.springsecurity.KeycloakSecurityComponents;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;

import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.
;
import java.util.ArrayList;
import java.util.List;

import static org.junit.Assert.assertEquals;

@RunWith(SpringRunner.class)
@WebMvcTest(StudentController.class)
@ContextConfiguration(classes = WaterlooApplication.class)
@componentscan(basePackageClasses = {KeycloakSecurityComponents.class, KeycloakSpringBootConfigResolver.class})
public class StudentControllerMVCTests extends ServletUnitTestingSupport {}
`

any idea how to fix?

Lombok as compile dependency might not be correct (freaks out IDE)

This library is very promising!

But at the moment Lombok is added as a compile dependency. This freaks out my IntelliJ IDE, because it detects Lombok in the classpath without using an annotation processor (I am not using Lombok).
I also never used Lombok. But I think adding it as a compile dependency might no be the correct way to use it.

can not find symbol

Hi, I tried to use the advice you mentioned in https://stackoverflow.com/questions/51711268/how-to-test-keycloak-authentication-in-spring-boot-application to test keycloak,but i found that there seemed to be a lack of variables in the source code.

you mentioned the variable ”privateClaims“,but i can not find in annotation @WithMockKeycloakAuth.

thanks for your reply.

@WithMockKeycloakAuth( authorities = { "USER", "AUTHORIZED_PERSONNEL" }, id = @IdTokenClaims(sub = "42"), oidc = @OidcStandardClaims( email = "[email protected]", emailVerified = true, nickName = "Tonton-Pirate", preferredUsername = "ch4mpy"), **privateClaims** = @ClaimSet(stringClaims = @StringClaim(name = "foo", value = "bar")))

Authorities configuration should be per authorization server

Thanks to authorization-server-locations property, it is now possible to configure several identities sources for a single resource server.

Unfortunately, those servers could have different configurations and provide authorities in different claims, case or prefix, but authorities mapping configuration is unique.

Authorities mapping configuration should be turned into an array with one entry per authorization-server (just as CORS configuration is made per path)

Move Keycloak related stuff in dedicated jar

Currently, KeycloakAuthenticationToken related stuff (among which @WithMockKeycloakAuth annotation and MockMvc request post-processor) are standing with other Authentication implementations stuff. This makeseverything depend on Keycloak libs (even if it's marked as optional)

It would be better to isolate Keycloak stuf in dedicated jar and remove any reference to Keycloak from the rest.

How to use KeycloakAUthRequestPostProcessor in 2.4.1?

I am currently using version 2.0, but I would like to upgrade to the latest 2.4.1 version.

I currently have this code:

return new KeycloakAuthRequestPostProcessor()
                .roles("user", role)
                .name(email)
                .accessToken(accessToken -> {
                    accessToken.setSubject(authServerUserId);
                    accessToken.setGivenName(givenName);
                    accessToken.setFamilyName(familyName);
                })
                .idToken(idToken -> {
                    idToken.setSubject(authServerUserId);
                    idToken.setGivenName(givenName);
                    idToken.setFamilyName(familyName);
                });

How do I do this with 2.4.1? The roles and name methods no longer exist it seems.

Make use of @AutoConfiguration

Implement spring-boot auto-configuration so that explicitly @Import(ServletSecurityBeans) or @Import(ReactiveSecurityBeans) is not required anymore.

spring-security-oauth2-webmvc-addons or spring-security-oauth2-webflux-addons being on the classpath should be enough.

The new way to declare auto-configuration, as anouced with spring 2.7, should be used

Define a way with `WithMockKeycloakAuth` to populate accessToken information

In the version 2.1.0, is possible to define sessionState

@WithMockKeycloakAuth(authorities=["something"]), accessToken=WithAccessToken(sessionState="00000000-0000-0000-0000-000000000000")

But in the version 2.4.0, I don't found a way to define AccessToken.sessionState (session_state). Also, I need define too AccessToken.id (jti) and AccessToken.subject (sub).

I found how to define the sub, but not others

@WithMockKeycloakAuth(authorities=[CLASSROOM_TURMAS_CADASTRAR_COMANDO], id=IdTokenClaims(sub="00000000-0000-0000-0000-000000000000"))

Maybe a way to do this is defining a hashmap or something like to add all the (custom) token values that are needed

(Not a bug) Really excellent work!

I happened upon this repo while looking for a way to mock Keycloak tokens in Spring MVC controller tests. Your WithMockKeycloakAuth annotation worked like a charm and saved me a ton of hassle. Just wanted to post a little note saying thanks again and keep up the good work!

(This isn't exactly a bug...but I wasn't sure where else to put it :/ )

More configuration properties

Expose configuration properties for:

  • enabling anonymous (true by default)
  • disabling CSRF (true by default)
  • enabling stateless sessions (true by default)
  • returning 401 instead of 302 when anonymous trying to access restricted content

Using annotations in a cucumber test environment with MockMvcSupport does not create a Principal in Spring Boot

Describe the bug
We have Keycloak configured in our Spring Boot micro-service. We use Cucumber to test our backend rest service endpoints. We use JWT tokens to secure endpoints. I use a very simple @WithMockKeycloakAuth({ "ROLE_user" }) annotation. When stepping in the controller, the Principal was not created (null). Hence, the annotation on a service method @RolesAllowed("user") fails.

Code sample

  1. failing test
@WithMockKeycloakAuth({ "ROLE_user" })
    public List<ProductLightDTO> securedProductSearch(ProductFilter filter, String username, String password) throws Exception {
        ObjectMapper objectMapper = new ObjectMapper();
        String body = objectMapper.writeValueAsString(filter);
        MvcResult result = api.perform(post("/products").contentType(APPLICATION_JSON_UTF8)
                .content(body)).andReturn();
        List<ProductLightDTO> productLightDTOS = Arrays.asList(objectMapper.readValue(result.getResponse().getContentAsString(), ProductLightDTO.class));
        return productLightDTOS;
    }
  1. Component under test
@RestController
@RequestMapping("/products")
public class ProductController {

    private final ProductService productService;

    @PostMapping("")
    public List<ProductLightDTO> searchProducts(@RequestBody ProductFilter productFilter, Principal principal) {
        return productService.search(productFilter);
    }

The service method (never gets ther since Spring Security is doing it's job)

@Service
@Log4j2
public class ProductService {

    @RolesAllowed("user")
    public List<ProductLightDTO> search(ProductFilter productFilter) {
..
    }
  1. spring-security configuration involved (runtime and tests)

Runtime config:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(jsr250Enabled = true)
public class WebSecurityConfig extends KeycloakWebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        super.configure(http);
        http.cors().and().authorizeRequests()
                .anyRequest()
                .authenticated()
                .and()
                .httpBasic();
        http.csrf().disable();
    }

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) {
        KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
        keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
        auth.authenticationProvider(keycloakAuthenticationProvider);
    }

    @Bean
    @Override
    protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
        return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
    }

    @Bean
    public KeycloakConfigResolver KeycloakConfigResolver() {
        return new KeycloakSpringBootConfigResolver();
    }

}

Test config:

@Component
@Import({ ServletKeycloakAuthUnitTestingSupport.UnitTestConfig.class })
public class SearchApi {
...
    @Autowired
    MockMvcSupport api;

    @WithMockKeycloakAuth({ "ROLE_user" })
    public List<ProductLightDTO> securedProductSearch(ProductFilter filter) throws Exception {
        ObjectMapper objectMapper = new ObjectMapper();
        String body = objectMapper.writeValueAsString(filter);
        MvcResult result = api.perform(post("/products").contentType(APPLICATION_JSON_UTF8)
                .content(body)).andReturn();
        List<ProductLightDTO> productLightDTOS = Arrays.asList(objectMapper.readValue(result.getResponse().getContentAsString(), ProductLightDTO.class));
        return productLightDTOS;
    }

Expected behavior

I expect to find a Principal in the controler with a role "user".

Split spring-security-oauth2-addons in 3

spring-security-oauth2-addons contains code (and dependencies) for both servlet & reactive apps (webmvc and webflux). It should be split in 3:

  • spring-security-oauth2-addons with common code being neither servlet nor reactive APIs dependent
  • spring-security-oauth2-webmvc-addons
  • spring-security-oauth2-webflux-addons

Help with forced update of access token

@ch4mpy This issue is not so much a bug report, but a request on whether the spring-addons tools are useful in this use-case.

My client uses keycloak IDP and adds roles to a user at some moments. I would like to trigger a refresh of the user principal in the same session when this happens. One way I see would be to manually update the keycloakSecurityContext in the Principal. Another would be to trigger a refresh of the access token with the normal OAuth2 workflow. Do your Authorities addons support any of these situations?

Please be aware that I am not very experienced in Spring Security so after reading the README of the relevant section it is not entirely clear to me what (if at all) your lib can do for my needs.

JDK-Suffix breaks semver contract

Describe the bug
Not necessarily a bug in the library itself, but an inconvenience in the interaction with other tools:

I use this library as jdk-11 Dependency. Update mechanisms like dependabot think the jdk-17 variant is newer than jdk-11 and try to bump versions to it. Please think about re-structuring your naming scheme such that the jdk ist part of group or artifact id instead of version. I would prefer to see this:

    <groupId>com.c4-soft.springaddons</groupId>
    <artifactId>spring-security-oauth2-test-webmvc-addons-jdk11</artifactId>
    <version>3.1.16</version>

// Edit: This also goes for mvnrepository itself. See https://mvnrepository.com/artifact/com.c4-soft.springaddons/spring-security-oauth2-test-webmvc-addons/3.1.16-jdk11 for example

Note: There is a new version for this artifact. New Version 3.1.16-jdk17

Error when using @WithMockKeycloakAuth

Describe the bug
When I use the @WithMockKeycloakAuth tag above my test, I get the following error:

java.lang.NoSuchMethodError: 'void org.keycloak.representations.AccessToken$Authorization.setPermissions(java.util.Collection)'
	at com.c4_soft.springaddons.security.oauth2.test.annotations.keycloak.AccessTokenBuilderHelper.authorization(AccessTokenBuilderHelper.java:61) ~[spring-security-oauth2-test-addons-3.1.5-jdk17.jar:na]
	at com.c4_soft.springaddons.security.oauth2.test.annotations.keycloak.AccessTokenBuilderHelper.feed(AccessTokenBuilderHelper.java:36) ~[spring-security-oauth2-test-addons-3.1.5-jdk17.jar:na]
	at com.c4_soft.springaddons.security.oauth2.test.annotations.keycloak.WithMockKeycloakAuth$Factory.lambda$authentication$0(WithMockKeycloakAuth.java:109) ~[spring-security-oauth2-test-addons-3.1.5-jdk17.jar:na]
	at com.c4_soft.springaddons.security.oauth2.test.keycloak.KeycloakAuthenticationTokenTestingBuilder.accessToken(KeycloakAuthenticationTokenTestingBuilder.java:79) ~[spring-security-oauth2-test-addons-3.1.5-jdk17.jar:na]
	at com.c4_soft.springaddons.security.oauth2.test.annotations.keycloak.WithMockKeycloakAuth$Factory.authentication(WithMockKeycloakAuth.java:109) ~[spring-security-oauth2-test-addons-3.1.5-jdk17.jar:na]
	at com.c4_soft.springaddons.security.oauth2.test.annotations.keycloak.WithMockKeycloakAuth$Factory.createSecurityContext(WithMockKeycloakAuth.java:101) ~[spring-security-oauth2-test-addons-3.1.5-jdk17.jar:na]
	at com.c4_soft.springaddons.security.oauth2.test.annotations.keycloak.WithMockKeycloakAuth$Factory.createSecurityContext(WithMockKeycloakAuth.java:84) ~[spring-security-oauth2-test-addons-3.1.5-jdk17.jar:na]
	at org.springframework.security.test.context.support.WithSecurityContextTestExecutionListener.lambda$createTestSecurityContext$0(WithSecurityContextTestExecutionListener.java:123) ~[spring-security-test-5.6.0.jar:5.6.0]
	at ...

It seems that the .setPermissions method expects a list instead of a collection.

Code sample
I use Keycloak 15

Test:

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ContextConfiguration(classes = {Application.class})
@Transactional
@AutoConfigureMockMvc
@Import({ KeycloakSpringBootConfigResolver.class })
public class ProjectControllerTest {

        ...

        @Test
        @WithMockKeycloakAuth
        public void getExistingProject() throws Exception {
            mvc.perform(get("/projects/1"))
                .andExpect(status().isOk());
        }
}

SecurityConfig:

@KeycloakConfiguration
@EnableWebSecurity
@EnableGlobalMethodSecurity(
        // securedEnabled = true,
        // jsr250Enabled = true,
        prePostEnabled = true)
@Import(KeycloakSpringBootConfigResolver.class)
public class WebSecurityConfig extends KeycloakWebSecurityConfigurerAdapter {

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) {
        KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
        // adding proper authority mapper for prefixing role with "ROLE_"
        keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
        auth.authenticationProvider(keycloakAuthenticationProvider);
    }

    @Bean
    @Override
    protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
        return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
    }

    @Bean
    public org.keycloak.adapters.KeycloakConfigResolver KeycloakConfigResolver() {
        return new KeycloakSpringBootConfigResolver();
    }


    @Override
    protected void configure(HttpSecurity http) throws Exception {
        super.configure(http);
        http.cors().and().csrf().disable()
                .authorizeRequests()
                .anyRequest().authenticated();
    }

}
```



**Expected behavior**
I would expect no error. Do I need a specific version of something?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.