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