TNG/keycloak-mock

Fails to generate a valid token

belgoros opened this issue ยท 35 comments

I'm trying to use different methods to generate a token and hit the request in a SpringBoot test as follows:

@SpringBootTest
@AutoConfigureMockMvc
class TestControllerTest {

@Test
    public void shouldRespondWithUnauthorizedForAdmin() throws Exception {
        this.mockMvc.perform(get("/test/admin")).andExpect(status().isUnauthorized());
    }


@Test
    public void shouldRespondOKforAdminIfTokenPresent() throws Exception {
        KeycloakMock keycloakMock = new KeycloakMock();
        Map<String, Object> claims = new HashMap<>();
        claims.put("grant_type", "password");
        claims.put("client_id", "springboot-microservice");
        claims.put("client_secret", "some-secret-string-value");
        claims.put("username", "employee2");
        claims.put("password", "mypassword");

        String accessToken = keycloakMock.getAccessToken(aTokenConfig()
                .withClaims(claims)
                .build());
        mockMvc.perform(get("/test/admin")
                .header("Authorization", "Bearer " + accessToken))
                .andExpect(status().isOk());
    }

but it fails with

MockHttpServletResponse:
           Status = 401
    Error message = Unable to authenticate using the Authorization header
          Headers = [WWW-Authenticate:"Bearer realm="demo", error="invalid_token", error_description="Didn't find publicKey for specified kid"", X-Content-Type-Options:"nosniff", X-XSS-Protection:"1; mode=block", Cache-Control:"no-cache, no-store, max-age=0, must-revalidate", Pragma:"no-cache", Expires:"0", X-Frame-Options:"DENY"]
     Content type = null
             Body = 
    Forwarded URL = null
   Redirected URL = null
          Cookies = []

java.lang.AssertionError: Status expected:<200> but was:<401>
Expected :200
Actual   :401
<Click to see difference>

What am I missing?

My controller is defined as follows:

@RestController
@RequestMapping("/test")
public class TestController {
...
@RolesAllowed("admin")
    @RequestMapping(value = "/admin", method = RequestMethod.GET)
    public ResponseEntity<String> getAdmin(@RequestHeader("Authorization") String authorization) {
        return ResponseEntity.ok("Hello Admin");
    }
...

And the Keycloak configurations looks like that with the roles defined:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class KeycloakSecurityConfig extends KeycloakWebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        super.configure(http);
        http.authorizeRequests()
            .antMatchers("/test/anonymous").permitAll()
            .antMatchers("/test/user").hasAnyRole("user")
            .antMatchers("/test/admin").hasAnyRole("admin")
            .antMatchers("/test/all-user").hasAnyRole("user","admin")
            .anyRequest()
            .permitAll();
        http.csrf().disable();
    }
@Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        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();
    }

Using Postman with the valid token, it works but fails in tests.

Thank you

I think you are missing to set the admin role in the claims you provide.

See for example https://github.com/TNG/keycloak-mock/blob/master/example-backend/src/test/java/com/tngtech/keycloakmock/examplebackend/AuthenticationTest.java
how to set the role

@belgoros You first need to start the keycloak mock, either by using the JUnit rule or by calling .start() on the mock itself. See the readme for examples how to start the mock.

Also, beware that in the current version 0.5.0, you will need both JUnit4 and JUnit5 in the classpath to use the mock-junit module. We plan to fix this in 0.6.0, but for the moment, I suggest you use version 0.4.0 instead.

As a side note: unless you have your own authentication mechanism on top of OpenID connect, you should not set things like grant_type, client_secret or password in your claims. As for setting username and client_id, these fields are called differently in the token, and there are convenience methods for setting them:
If you want to set the username of the token, use TokenConfig.Builder#withSubject or TokenConfig.Builder#withPreferredUsername. If you want to set the client ID of the authorized party (i.e. the service or frontend that requested the token), use TokenConfig.Builder#withAuthorizedParty. If you want to set the client ID of your own service, use TokenConfig.Builder#withAudience.

Thank you, guys, for your responses.
First thing first, - I tried to use @RegisterExtension annotation instead of initializing the Keycloak instance in a test method:

@RegisterExtension
static KeycloakMock keycloakMock = new KeycloakMock();

...
@Test
public void shouldRespondOKforAdminIfTokenPresent() throws Exception {
        //KeycloakMock keycloakMock = new KeycloakMock(); used @RegisterExtension above
        Map<String, Object> claims = new HashMap<>();
        claims.put("grant_type", "password");
        claims.put("client_id", "springboot-microservice");
        claims.put("client_secret", "85a58c55-dd32-4205-a568-f82ae710edd1");
        claims.put("username", "employee2");
        claims.put("password", "mypassword");

        String accessToken = keycloakMock.getAccessToken(aTokenConfig()
                .withClaims(claims)
                .build());
                ...

and it fails with:

org.junit.platform.commons.PreconditionViolationException: Failed to register extension via @RegisterExtension field [static com.tngtech.keycloakmock.api.KeycloakMock com.dinuth.keycloakspringbootmicroservice.controller.TestControllerTest.keycloakMock]: field value's type [com.tngtech.keycloakmock.api.KeycloakMock] must implement an [org.junit.jupiter.api.extension.Extension] API.

	at org.junit.platform.commons.util.Preconditions.condition(Preconditions.java:296)
	at org.junit.jupiter.engine.descriptor.ExtensionUtils.lambda$registerExtensionsFromFields$2(ExtensionUtils.java:110)
	at org.junit.platform.commons.function.Try$Success.ifSuccess(Try.java:258)
	at org.junit.jupiter.engine.descriptor.ExtensionUtils.lambda$registerExtensionsFromFields$3(ExtensionUtils.java:109)
	...

Another try was to call start on the keycloak instance directly in the test method as folllows:

@Test
public void shouldRespondOKforAdminIfTokenPresent() throws Exception {
    KeycloakMock keycloakMock = new KeycloakMock();
    keycloakMock.start();
    ...
    mockMvc.perform(get("/test/admin")
                .header("Authorization", "Bearer " + accessToken))
                .andExpect(status().isOk());
        keycloakMock.stop();
}

it also failed with a differetn error:

MockHttpServletRequest:
      HTTP Method = GET
      Request URI = /test/admin
       Parameters = {}
          Headers = [Authorization:"Bearer eyJraWQiOiJrZXlJZCIsImFsZyI6IlJTMjU2In0.eyJhdWQiOlsic2VydmVyIl0sImlhdCI6MTYwMDQxNDM0NywiYXV0aF90aW1lIjoxNjAwNDE0MzQ3LCJleHAiOjE2MDA0NTAzNDcsImlzcyI6Imh0dHA6Ly9sb2NhbGhvc3Q6ODAwMC9hdXRoL3JlYWxtcy9tYXN0ZXIiLCJzdWIiOiJ1c2VyIiwic2NvcGUiOiJvcGVuaWQiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJjbGllbnQiLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOltdfSwicmVzb3VyY2VfYWNjZXNzIjp7fSwicGFzc3dvcmQiOiJteXBhc3N3b3JkIiwiY2xpZW50X3NlY3JldCI6Ijg1YTU4YzU1LWRkMzItNDIwNS1hNTY4LWY4MmFlNzEwZWRkMSIsImdyYW50X3R5cGUiOiJwYXNzd29yZCIsImNsaWVudF9pZCI6InNwcmluZ2Jvb3QtbWljcm9zZXJ2aWNlIiwidXNlcm5hbWUiOiJlbXBsb3llZTIifQ.hvq6K_l62O_ErJJQBfDZ3HmRwWhyZ7dbtDTIU-nfNM2BGPW5WoFLAPg6GPV4HHnubx9HIKuUgLhA9dxe2ZIc6tU4gdO2n-UTEo0DVNqd3h8weh0X_pPUB2yFFIygZg9pvMgNYfjZ3QZ0uOlpIgn3Mjzy2-C9xWVXnmPut5sojZRSRM7Ttsn0u9f7E-kHR2j7rnKqfWXkhyLMk8oezbxRMKeAfYSZUfg1P-jwTOf5eYPmiEgEEIeNzVH1n6JiIZxWcvoxma4qaeks4aVF8Fl3LAw1gqL1--avTe7btcQKqhDNH90hcAw6xw5dN7utB6aq6Pc2KadEM1EaAXAb4IIqww"]
             Body = null
    Session Attrs = {}
    ...
    MockHttpServletResponse:
           Status = 401
    Error message = Unable to authenticate using the Authorization header
          Headers = [WWW-Authenticate:"Bearer realm="demo", error="invalid_token", error_description="Didn't find publicKey for specified kid"", X-Content-Type-Options:"nosniff", X-XSS-Protection:"1; mode=block", Cache-Control:"no-cache, no-store, max-age=0, must-revalidate", Pragma:"no-cache", Expires:"0", X-Frame-Options:"DENY"]
     Content type = null
     ....
     java.lang.AssertionError: Status expected:<200> but was:<401>
Expected :200
Actual   :401
...

The third tentative wzs to use AuthenticationTest as example.
I copy-pasted some initial code snippets just to check if it works:

import com.tngtech.keycloakmock.api.KeycloakMock;
import io.restassured.RestAssured;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.test.web.servlet.MockMvc;

import static com.tngtech.keycloakmock.api.TokenConfig.aTokenConfig;
import static org.hamcrest.Matchers.equalTo;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
class TestControllerTest {

    @RegisterExtension
    static KeycloakMock keycloakVerificationMock = new KeycloakMock(8000, "master");

    @LocalServerPort
    private int port;

    @Autowired
    private MockMvc mockMvc;

    @BeforeEach
    void setup() {
        RestAssured.enableLoggingOfRequestAndResponseIfValidationFails();
        RestAssured.port = port;
    }

    @Test
    void no_authentication_fails() {
        RestAssured.given().when().get("/test/anonymous").then().statusCode(200);
    }
...
// the rest of the class

it also failed to register the KeycloakMock extension:

[ERROR] Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 0.005 s <<< FAILURE! - in com.dinuth.keycloakspringbootmicroservice.controller.TestControllerTest
[ERROR] com.dinuth.keycloakspringbootmicroservice.controller.TestControllerTest  Time elapsed: 0.005 s  <<< ERROR!
org.junit.platform.commons.PreconditionViolationException: Failed to register extension via @RegisterExtension field [static com.tngtech.keycloakmock.api.KeycloakMock com.dinuth.keycloakspringbootmicroservice.controller.TestControllerTest.keycloakVerificationMock]: field value's type [com.tngtech.keycloakmock.api.KeycloakMock] must implement an [org.junit.jupiter.api.extension.Extension] API.

2020-09-18 09:55:58.089  INFO 4914 --- [extShutdownHook] o.s.s.concurrent.ThreadPoolTaskExecutor  : Shutting down ExecutorService 'applicationTaskExecutor'
[INFO] 
[INFO] Results:
[INFO] 
[ERROR] Errors: 
[ERROR]   TestControllerTest ? PreconditionViolation Failed to register extension via @R...

What am I missing? Thank you.

Could you post your declared test dependencies? My first guess would be that you don't have JUnit5 in your classpath.

@ostrya , here they are:

...
<properties>
		<java.version>11</java.version>
		<keycloak.version>11.0.2</keycloak.version>
	</properties>

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

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
			<scope>runtime</scope>
			<optional>true</optional>
		</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>
		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-test</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>com.tngtech.keycloakmock</groupId>
			<artifactId>mock</artifactId>
			<scope>test</scope>
			<version>0.5.0</version>
		</dependency>
		<!-- https://mvnrepository.com/artifact/io.rest-assured/rest-assured -->
		<dependency>
			<groupId>io.rest-assured</groupId>
			<artifactId>rest-assured</artifactId>
			<version>4.3.1</version>
			<scope>test</scope>
		</dependency>

	</dependencies>

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

I think I'm missing this dependency:

...
<properties>
..
  <junit.jupiter.version>5.6.2</junit.jupiter.version>
...
<dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>${junit.jupiter.version}</version>
            <scope>test</scope>
</dependency>

Right? Even with this one it fails the same way .

Downgrading to 0.4.0 version didn't solve the problem of @RegisterExtension either:

<dependency>
  <groupId>com.tngtech.keycloakmock</groupId>
 <artifactId>mock</artifactId>
 <scope>test</scope>
 <version>0.4.0</version>
</dependency>

You are using the wrong dependency if you want to use @RegisterExtension. Please use artifactId mock-junit5 instead of mock.

Sorry for this confusion, we plan to name the different classes more clearly, so that this kind of confusion will not arise in the future.

@ostrya Hmm, it seems to be deprecated, but it does seem to work:

10:51:35.269 [main] DEBUG io.netty.util.ResourceLeakDetector - -Dio.netty.leakDetection.targetRecords: 4
10:51:35.290 [main] DEBUG io.netty.util.internal.PlatformDependent - Platform: MacOS
10:51:35.291 [main] DEBUG io.netty.util.internal.PlatformDependent0 - -Dio.netty.noUnsafe: false
10:51:35.291 [main] DEBUG io.netty.util.internal.PlatformDependent0 - Java version: 11
10:51:35.293 [main] DEBUG io.netty.util.internal.PlatformDependent0 - sun.misc.Unsafe.theUnsafe: available
10:51:35.294 [main] DEBUG io.netty.util.internal.PlatformDependent0 - sun.misc.Unsafe.copyMemory: available
10:51:35.295 [main] DEBUG io.netty.util.internal.PlatformDependent0 - java.nio.Buffer.address: available
10:51:35.297 [main] DEBUG io.netty.util.internal.PlatformDependent0 - direct buffer constructor: unavailable
java.lang.UnsupportedOperationException: Reflective setAccessible(true) disabled
	at io.netty.util.internal.ReflectionUtil.trySetAccessible(ReflectionUtil.java:31)
	at io.netty.util.internal.PlatformDependent0$4.run(PlatformDependent0.java:225)
	at java.base/java.security.AccessController.doPrivileged(Native Method)
...
[INFO] Running com.dinuth.keycloakspringbootmicroservice.controller.TestControllerTest
[ERROR] Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 0.01 s <<< FAILURE! - in com.dinuth.keycloakspringbootmicroservice.controller.TestControllerTest
[ERROR] com.dinuth.keycloakspringbootmicroservice.controller.TestControllerTest  Time elapsed: 0.009 s  <<< ERROR!
org.junit.platform.commons.PreconditionViolationException: Failed to register extension via @RegisterExtension field [static com.tngtech.keycloakmock.api.KeycloakMock com.dinuth.keycloakspringbootmicroservice.controller.TestControllerTest.keycloakVerificationMock]: field value's type [com.tngtech.keycloakmock.api.KeycloakMock] must implement an [org.junit.jupiter.api.extension.Extension] API.

Yes, sorry about the confusing deprecation as well. The deprecation was introduced in version 0.5.0 but turned out to be an error. We will lift the deprecation with 0.6.0.

As for the error in your test, you still need to change the import from com.tngtech.keycloakmock.api.KeycloakMock to com.tngtech.keycloakmock.junit5.KeycloakMock.

Okay, the deprecation is not a problem. What I'm stuck with is to use @RegisterExtension. Your example project uses grandle, the mine - Maven. I still can't figure out what's the problem. I'm not even in a test method or assertions, settings, etc., - it's just as if @RegisterExtension is not recognized at all by KeycloakMock... Any tips about that? Thank you

Did you change the import?

Yep, I chnaged it to:

import com.tngtech.keycloakmock.junit5.KeycloakMock;

The error is different:

ava.lang.NoClassDefFoundError: io/restassured/path/json/mapper/factory/JsonbObjectMapperFactory

	at io.restassured.config.RestAssuredConfig.<init>(RestAssuredConfig.java:41)
	at io.restassured.RestAssured.<clinit>(RestAssured.java:421)
	at com.dinuth.keycloakspringbootmicroservice.controller.TestControllerTest.setup(TestControllerTest.java:34)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
...
Caused by: java.lang.ClassNotFoundException: io.restassured.path.json.mapper.factory.JsonbObjectMapperFactory
	at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:581)
	at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)
	at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:522)
	... 70 more

and the Keyclkoak mock is bar-stroke as deprecated:
Screenshot 2020-09-18 at 11 43 36

Your error is weird, because keycloak-mock does not have a dependency to restassured in the first place. My guess is you need it in your own tests and need to declare it, see https://github.com/rest-assured/rest-assured/wiki/GettingStarted#jsonpath.

It is included in rest-assured:
Screenshot 2020-09-18 at 12 06 53

But even after including it, it failed ๐Ÿ˜ข

And I did put the rest-assured dependency before all the others (junit, and mock-junit5) as required in their docs:
Screenshot 2020-09-18 at 12 12 04

You're right, those two are transitive dependencies. I can only guess that there may be problems with other dependencies pulling in different versions of this. You could have a look at the dependency tree to make sure of this. Or maybe this helps: https://stackoverflow.com/questions/62371283/java-lang-noclassdeffounderror-io-restassured-path-json-mapper-factory-jsonbobj

I fixed dependencies order racing, now the pom.xml looks like that:

...
<properties>
		<java.version>8</java.version>
		<maven.compiler.source>${java.version}</maven.compiler.source>
		<maven.compiler.target>${maven.compiler.source}</maven.compiler.target>
		<keycloak.version>11.0.2</keycloak.version>
	</properties>

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

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
			<scope>runtime</scope>
			<optional>true</optional>
		</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>
		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-test</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>io.rest-assured</groupId>
			<artifactId>rest-assured</artifactId>
			<version>4.3.1</version>
			<scope>test</scope>
			<exclusions>
				<exclusion>
					<groupId>io.rest-assured</groupId>
					<artifactId>json-path</artifactId>
				</exclusion>
				<exclusion>
					<groupId>io.rest-assured</groupId>
					<artifactId>xml-path</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
		<dependency>
			<groupId>io.rest-assured</groupId>
			<artifactId>json-path</artifactId>
			<version>4.3.1</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>io.rest-assured</groupId>
			<artifactId>xml-path</artifactId>
			<version>4.3.1</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>com.tngtech.keycloakmock</groupId>
			<artifactId>mock-junit5</artifactId>
			<version>0.5.0</version>
		</dependency>
		<dependency>
			<groupId>org.junit.jupiter</groupId>
			<artifactId>junit-jupiter</artifactId>
			<scope>test</scope>
		</dependency>

	</dependencies>

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

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
			<plugin>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>3.8.1</version>
			</plugin>
			<plugin>
				<artifactId>maven-surefire-plugin</artifactId>
				<version>2.22.2</version>
			</plugin>
		</plugins>
	</build>

When running mvn clean test, it raises another error:

2020-09-18 12:33:00.992  INFO 9518 --- [           main] c.d.k.controller.TestControllerTest      : Started TestControllerTest in 2.839 seconds (JVM running for 4.074)
[ERROR] Tests run: 5, Failures: 0, Errors: 2, Skipped: 0, Time elapsed: 3.882 s <<< FAILURE! - in com.dinuth.keycloakspringbootmicroservice.controller.TestControllerTest
[ERROR] no_authentication_fails  Time elapsed: 0.402 s  <<< ERROR!
java.lang.AbstractMethodError: io.restassured.internal.RequestSpecificationImpl.invokeMethod(Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/Object;
	at com.dinuth.keycloakspringbootmicroservice.controller.TestControllerTest.no_authentication_fails(TestControllerTest.java:40)

[ERROR] authentication_with_role_works  Time elapsed: 0.002 s  <<< ERROR!
java.lang.AbstractMethodError: io.restassured.internal.RequestSpecificationImpl.invokeMethod(Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/Object;
	at com.dinuth.keycloakspringbootmicroservice.controller.TestControllerTest.authentication_with_role_works(TestControllerTest.java:45)

2020-09-18 12:33:01.534  INFO 9518 --- [extShutdownHook] o.s.s.concurrent.ThreadPoolTaskExecutor  : Shutting down ExecutorService 'applicationTaskExecutor'
[INFO] 
[INFO] Results:
[INFO] 
[ERROR] Errors: 
[ERROR]   TestControllerTest.authentication_with_role_works:45 ยป AbstractMethod io.resta...
[ERROR]   TestControllerTest.no_authentication_fails:40 ยป AbstractMethod io.restassured....
[INFO] 
[ERROR] Tests run: 5, Failures: 0, Errors: 2, Skipped: 0
[INFO] 
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  7.265 s
[INFO] Finished at: 2020-09-18T12:33:01+02:00
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-surefire-plugin:2.22.2:test (default-test) on project keycloak-springboot-microservice: There are test failures.
[ERROR] 
[ERROR] Please refer to /Users/serguei/projects/github/keycloak-springboot-microservice/target/surefire-reports for the individual test results.
[ERROR] Please refer to dump files (if any exist) [date].dump, [date]-jvmRun[N].dump and [date].dumpstream.
[ERROR] -> [Help 1]
[ERROR] 
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR] 
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoFailureException

Oufff...

The problems you are currently experiencing seem to be related to rest-assured. I think it would be best to raise these issues in the rest-assured project itself. In the meantime, could you check that the mock works as expected when using mockMvc?

Yes, the below tests are all passing:

@Test
    public void shouldRespondWithUnauthorizedForUser() throws Exception {
        this.mockMvc.perform(get("/test/user")).andExpect(status().isUnauthorized());
    }

    @Test
    public void shouldRespondWithUnauthorizedForAdmin() throws Exception {
        this.mockMvc.perform(get("/test/admin")).andExpect(status().isUnauthorized());
    }

    @Test
    public void shouldBeAccessedWithoutToken() throws Exception {
        this.mockMvc.perform(get("/test/anonymous")).andExpect(status().isOk());
    }

Could you please try one more test case (like the one in your initial post) where you actually use the mock to generate tokens? Because if that works as well, I would close this issue.

Yep, all the tests using RestAssured fail, using mockMvc pass:
Screenshot 2020-09-18 at 13 18 23

Here is the full test class:

package com.dinuth.keycloakspringbootmicroservice.controller;

import com.tngtech.keycloakmock.junit5.KeycloakMock;
import io.restassured.RestAssured;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.test.web.servlet.MockMvc;

import java.util.HashMap;
import java.util.Map;

import static com.tngtech.keycloakmock.api.TokenConfig.aTokenConfig;
import static org.hamcrest.Matchers.equalTo;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
class TestControllerTest {

    @RegisterExtension
    static KeycloakMock keycloakMock = new KeycloakMock(8080, "demo");

    @LocalServerPort
    private int port;

    @Autowired
    private MockMvc mockMvc;

    @BeforeEach
    void setup() {
        RestAssured.enableLoggingOfRequestAndResponseIfValidationFails();
        RestAssured.port = port;
    }

    @Test
    void no_authentication_fails() {
        RestAssured.given().when().get("/test/anonymous").then().statusCode(200);
    }

    @Test
    void authentication_with_role_works() {
        RestAssured.given()
                .auth()
                .preemptive()
                .oauth2(
                        keycloakMock.getAccessToken(aTokenConfig().withRealmRole("user").build()))
                .when()
                .get("/test/user")
                .then()
                .statusCode(200)
                .and()
                .body(equalTo("Hello User"));
    }

    @Test
    public void shouldRespondOKforAdminIfTokenPresent() throws Exception {
        Map<String, Object> claims = new HashMap<>();
        claims.put("grant_type", "password");
        claims.put("client_id", "springboot-microservice");
        claims.put("client_secret", "some-secret-string-value");
        claims.put("username", "employee2");
        claims.put("password", "mypassword");

        String accessToken = keycloakMock.getAccessToken(aTokenConfig()
                .withClaims(claims)
                .build());
        mockMvc.perform(get("/test/admin")
                .header("Authorization", "Bearer " + accessToken))
                .andExpect(status().isOk());
    }

    @Test
    public void shouldRespondWithUnauthorizedForUser() throws Exception {
        this.mockMvc.perform(get("/test/user")).andExpect(status().isUnauthorized());
    }

    @Test
    public void shouldRespondWithUnauthorizedForAdmin() throws Exception {
        this.mockMvc.perform(get("/test/admin")).andExpect(status().isUnauthorized());
    }

    @Test
    public void shouldBeAccessedWithoutToken() throws Exception {
        this.mockMvc.perform(get("/test/anonymous")).andExpect(status().isOk());
    }
}

Does shouldRespondOKforAdminIfTokenPresent() become green if you change

        String accessToken = keycloakMock.getAccessToken(aTokenConfig()
                .withClaims(claims)
                .build());

to

        String accessToken = keycloakMock.getAccessToken(aTokenConfig()
                .withClaims(claims)
                .withRealmRole("admin")
                .build());

?

No:

java.lang.AssertionError: Status expected:<200> but was:<401>
Expected :200
Actual   :401

because of the invalid token (see from the stack trace):

...
Headers = [WWW-Authenticate:"Bearer realm="demo", error="invalid_token", error_description="Didn't find publicKey for specified kid"", X-Content-Type-Options:"nosniff", X-XSS-Protection:"1; mode=block", Cache-Control:"no-cache, no-store, max-age=0, must-revalidate", Pragma:"no-cache", Expires:"0", X-Frame-Options:"DENY"]
....

What is your keycloak test configuration? Could you please post all your spring properties starting with keycloak for your test scope?

Sure.

#src/main/resources/application.properties

server.port                         = 8000

keycloak.realm                      = demo
keycloak.auth-server-url            = http://localhost:8080/auth
keycloak.ssl-required               = external
keycloak.resource                   = springboot-microservice
keycloak.use-resource-role-mappings = true
keycloak.bearer-only                = true

Test application.yml:

spring:
  #allow to overwrite httpSessionManager in KeycloakWebSecurityConfigurerAdapter
  main:
    allow-bean-definition-overriding: true
keycloak:
  realm: demo
  bearer-only: true
  auth-server-url: http://localhost:8080/auth
  ssl-required: external
  resource: springboot-microservice
  use-resource-role-mappings: true
  confidential-port: 0
  disable-trust-manager: true

Thanks for the info so far (btw, you may want to remove the client credentials). I'll try to re-create your setup to see if I can pin-point the problem.

@ostrya Thank you very much, as for the credentials, it's local ones, so no worries ๐Ÿ˜„

Hi, I tried to reproduce your problem locally, but I did not get the error you mentioned. Could you please provide a complete minimal example project?

Ok, I pushed the spring-demo app. The failing test is on a separate test/use_keycloak-mock branch.
The app uses Java 1.8.
I also provided demo-realm-export.json so that you could import the Keycloak settings into your local Keycloak server.

The failing test is in TestControllerTest.

I'm available for any other additional information to help with fixing it.
Best regards.

Thanks for the example. It seems it is now only a matter of mismatching configuration:

diff --git a/src/test/java/com/example/controllers/TestControllerTest.java b/src/test/java/com/example/controllers/TestControllerTest.java
index adbf22b..61d3814 100644
--- a/src/test/java/com/example/controllers/TestControllerTest.java
+++ b/src/test/java/com/example/controllers/TestControllerTest.java
@@ -18,7 +18,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
 @AutoConfigureMockMvc
 class TestControllerTest {
 
-    static KeycloakMock keycloakMock = new KeycloakMock(8000, "master");
+    static KeycloakMock keycloakMock = new KeycloakMock(8080, "demo");
 
     @BeforeAll
     static void setUp() {
@@ -48,7 +48,8 @@ class TestControllerTest {
     public void shouldRespondOKforAdminIfTokenPresent() throws Exception {
         TokenConfig tokenConfig = aTokenConfig()
                 .withPreferredUsername("employee2")
-                .withRealmRole("ROLE_ADMIN").build();
+                .withResourceRole("springboot-microservice", "ROLE_admin")
+                .build();
         String accessToken = keycloakMock.getAccessToken(tokenConfig);
 
         mockMvc.perform(get("/test/admin")

First of all (and I guess this is the bug that we will need to fix) the mock was started on port 8000 (which is already in use by the Spring app), but did not throw an exception.

After fixing the port to 8080, the backend complained that the issuer does not match. This is because your config states realm "demo", but the mock is initialized with realm "master".

Then, since you activated use-resource-role-mappings in your configuration, you need to set a resource role instead of a realm role.

And finally, roles are case-sensitive. BTW, if you wish to get rid of the requirement that all roles need to have a "ROLE_" prefix, please have a look at https://github.com/TNG/keycloak-mock/blob/master/example-backend/src/main/java/com/tngtech/keycloakmock/examplebackend/ExampleBackendApplication.java#L28 on how this configuration can be changed.

Wow, it works just fine ๐ŸŽ‰ ๐Ÿ‘ . And I applied your advice to drop the ROLE prefix ๐Ÿ˜„ . Thank you so much!!!

@ostrya Just forgot to ask for some small details.

  1. When you initialize the Keycloak mock as follows:
static KeycloakMock keycloakMock = new KeycloakMock(8080, "demo");

it seems like, I should have a realm named demo really created in a local Keycloak server? I tried to put something else and the previously passing tests failed :(. It will not work in a CI pipeline when running tests ๐Ÿ˜ข .

Moreover, if I leave the local Keycloak server running, the passing tests fail. Why so?

  1. What if I have no roles defined for the client? In this case, the above settings to generate a token will fail:
        TokenConfig tokenConfig = aTokenConfig()
                .withPreferredUsername("employee2")
                .withResourceRole("springboot-microservice", "admin")
                .build();

In the above case, I used a client springboot-microservice having a role admin.

Thank you!

I just passed in an empty String as follows:

        TokenConfig tokenConfig = aTokenConfig()
                .withRealmRole("user")
                .withPreferredUsername("user1")
                .withResourceRole("websocket", "")
                .build();

and it worked.

Thank you for your amazing library, just the docs are to be updated. ๐ŸŽ‰

Hi @belgoros ,

I don't know if I understand you correctly:

it seems like, I should have a realm named demo really created in a local Keycloak server?

The reason you need to use demo in your test is that your keycloak configuration (https://github.com/belgoros/spring-demo/blob/test/use_keycloak-mock/src/main/resources/application.yml#L11) you have configured the realm as demo.

Moreover, if I leave the local Keycloak server running, the passing tests fail. Why so?

This is a simple problem: if there is already a server running on port 8080, the mock cannot bind to that port anymore, so any requests for validating the mock token will actually go to the real Keycloak server, which of course knows nothing about the mock signing key.

As for the documentation, do you mean it is wrong somewhere? Or that something is missing?

@ostrya Hi, thanks a lot, yes, I've already figured out what was the problem, - keycloak-mock really parses application.yml to match the actual Keycloak configuration settings against the ones used in tests. Same for the case when a Keycloak server is running the same port. Thank you for your help and provided feedback. As for the documentation, maybe it would be great to add some more examples or point to some test classes.