我正在构建一个新的微服务,并使用Keycloak的访问令牌对其进行保护。到目前为止,我已经取得了成功,只能使用Keycloak的有效令牌访问端点/ token / test,应用程序属性如下所示:
keycloak.auth-server-url=http://localhost:8888/auth
keycloak.realm=realm
keycloak.resource=api
keycloak.public-client=true
keycloak.securityConstraints[0].authRoles[0]=basic-token
keycloak.securityConstraints[0].securityCollections[0].name=secured by basic access token
keycloak.securityConstraints[0].securityCollections[0].patterns[0]=/*
当使用mvn spring-boot:run启动项目时,这工作正常(我正在使用spring-boot-starter,keycloak-spring-boot-starter和 without spring-boot-starter我想避免的安全性。
现在我正在为它的乐趣编写一些测试,而Keycloak的安全约束根本无法正常工作。我遵循了https://github.com/cremich/testcontainers-keycloak(带有更新的版本和JUnit 5)中的测试设置,唯一的区别是该示例使用spring-boot-starter-security手动进行了很多Keycloak设置。为什么使用-security后,它只能在测试中起作用?为什么我的方法似乎不起作用?
我想念什么吗?
谢谢。
答案 0 :(得分:0)
经过几个小时的调试,我终于弄清楚了。问题在于Keycloak的身份验证(无论出于何种原因,大声笑)是在Tomcat阀中而不是在过滤器中完成的。 MockMvc不会通过servlet容器(及其阀门),因此甚至从未到达将要进行身份验证的位置。
不过,TestRestTemplate可以(如果使用入门级安全性,它也是一个过滤器而不是一个阀门)。我不知道使用阀门而不是过滤器背后的设计决定,但是您可以使用keycloak-starter中的配置并通过TestRestTemplate对其进行测试,也可以将更“昂贵”的starter-security配置与MockMvc结合使用。 / p>
答案 1 :(得分:0)
您需要在SecurityContext中使用每个测试中的模拟或正确类型的实例设置身份验证:SecurityContextHolder.getContext().setAuthentication(authentication)
我写了一组libs to ease this。它包含一个@WithMockKeycloackAuth
批注,以及Keycloak专用的MockMvc
请求后处理器和WebTestClient
配置器/ mutator
示例@WithMockKeycloackAuth
的使用情况:
@RunWith(SpringRunner.class)
@WebMvcTest(controllers = GreetingController.class)
@Import({
ServletKeycloakAuthUnitTestingSupport.UnitTestConfig.class,
KeycloakSpringBootSampleApp.KeycloakConfig.class })
// because this sample stands in the middle of non spring-boot-keycloak projects, keycloakproperties are isolated in
// application-keycloak.properties
@ActiveProfiles("keycloak")
public class GreetingControllerAnnotatedTest {
private static final String GREETING = "Hello %s! You are granted with %s.";
@MockBean
MessageService messageService;
@MockBean
JwtDecoder jwtDecoder;
@Autowired
MockMvcSupport api;
@Before
public void setUp() {
when(messageService.greet(any())).thenAnswer(invocation -> {
final var auth = invocation.getArgument(0, Authentication.class);
return String.format(GREETING, auth.getName(), auth.getAuthorities());
});
}
@Test
@WithMockKeycloakAuth
public void whenAuthenticatedWithoutAuthorizedPersonnelThenSecuredRouteIsForbidden() throws Exception {
api.get("/secured-route").andExpect(status().isForbidden());
}
@Test
@WithMockKeycloakAuth({ "AUTHORIZED_PERSONNEL" })
public void whenAuthenticatedWithAuthorizedPersonnelThenSecuredRouteIsOk() throws Exception {
api.get("/secured-route").andExpect(status().isOk());
}
@Test
@WithMockKeycloakAuth(
authorities = { "USER", "AUTHORIZED_PERSONNEL" },
id = @IdTokenClaims(sub = "42"),
oidc = @OidcStandardClaims(
email = "ch4mp@c4-soft.com",
emailVerified = true,
nickName = "Tonton-Pirate",
preferredUsername = "ch4mpy"),
accessToken = @KeycloakAccessToken(
realmAccess = @KeycloakAccess(roles = { "TESTER" }),
authorization = @KeycloakAuthorization(
permissions = @KeycloakPermission(rsid = "toto", rsname = "truc", scopes = "abracadabra"))),
privateClaims = @ClaimSet(stringClaims = @StringClaim(name = "foo", value = "bar")))
public void whenAuthenticatedWithKeycloakAuthenticationTokenThenCanGreet() throws Exception {
api.get("/greet")
.andExpect(status().isOk())
.andExpect(content().string(startsWith("Hello ch4mpy! You are granted with ")))
.andExpect(content().string(containsString("AUTHORIZED_PERSONNEL")))
.andExpect(content().string(containsString("USER")))
.andExpect(content().string(containsString("TESTER")));
}
}
maven-central提供了不同的库,请根据您的用例选择以下之一:
<dependency>
<groupId>com.c4-soft.springaddons</groupId>
<artifactId>spring-security-oauth2-test-addons</artifactId>
<version>2.3.4</version>
<scope>test</test>
</dependency>
<dependency>
<groupId>com.c4-soft.springaddons</groupId>
<artifactId>spring-security-oauth2-test-webmvc-addons</artifactId>
<version>2.3.4</version>
<scope>test</test>
</dependency>