1. 介绍
集成测试对验证应用正常运行至关重要,认证模块作为敏感部分必须严格测试。Testcontainers允许我们在测试阶段启动Docker容器,用真实技术栈运行测试。
本文将展示如何使用Testcontainers测试真实的Keycloak实例。
2. 搭建Spring Security与Keycloak集成
需要配置Spring Security、Keycloak和Testcontainers三部分。
2.1. 初始化Spring Boot和Spring Security
首先添加安全依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
创建一个返回用户信息的简单控制器:
@RestController
@RequestMapping("/users")
public class UserController {
@GetMapping("me")
public UserDto getMe() {
return new UserDto(1L, "janedoe", "Doe", "Jane", "jane.doe@example.com");
}
}
现在我们有一个响应*/users/me
的安全控制器。启动时Spring Security会生成用户'user'的密码(日志可见)。
2.2. 配置Keycloak
**最简单的方式是用Docker**:
docker run -p 8081:8080 -e KEYCLOAK_ADMIN=admin -e KEYCLOAK_ADMIN_PASSWORD=admin quay.io/keycloak/keycloak:17.0.1 start-dev
访问http://localhost:8081进入控制台:
现在已配置好包含baeldung-api
客户端和Jane Doe用户的realm。
2.3. 集成配置
添加OAuth2资源服务器依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
配置Spring Security:
@Configuration
@ConditionalOnProperty(name = "keycloak.enabled", havingValue = "true", matchIfMissing = true)
public class WebSecurityConfiguration {
@Bean
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new NullAuthenticatedSessionStrategy();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http.csrf()
.disable()
.cors()
.and()
.authorizeHttpRequests(auth -> auth.anyRequest()
.authenticated())
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt)
.build();
}
}
关键点:
- 使用
NullAuthenticatedSessionStrategy
实现无状态认证 @ConditionalOnProperty
支持通过keycloak.enabled=false
禁用Keycloak
在application.properties
添加连接配置:
keycloak.enabled=true
spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:8180/auth/realms/baeldung-api
现在应用已受保护,每次请求都会向Keycloak验证认证信息。
3. 使用Testcontainers配置Keycloak
3.1. 导出Realm配置
Keycloak容器启动时无配置,需通过JSON文件导入。从运行实例导出配置:
注意:管理界面不导出用户,需手动编辑realm-export.json
添加用户:
"users": [
{
"username": "janedoe",
"email": "jane.doe@example.com",
"firstName": "Jane",
"lastName": "Doe",
"enabled": true,
"credentials": [
{
"type": "password",
"value": "s3cr3t"
}
],
"clientRoles": {
"account": [
"view-profile",
"manage-account"
]
}
}
]
将文件放入src/test/resources/keycloak
目录。
3.2. 配置Testcontainers
添加依赖:
<dependency>
<groupId>com.github.dasniko</groupId>
<artifactId>testcontainers-keycloak</artifactId>
<version>2.1.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers</artifactId>
<version>1.16.3</version>
</dependency>
创建基础测试类:
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public abstract class KeycloakTestContainers {
static {
keycloak = new KeycloakContainer().withRealmImportFile("keycloak/realm-export.json");
keycloak.start();
}
}
静态初始化确保容器只启动一次,withRealmImportFile
指定启动时导入的realm配置。
3.3. Spring Boot测试配置
Keycloak使用随机端口,需动态覆盖配置:
@DynamicPropertySource
static void registerResourceServerIssuerProperty(DynamicPropertyRegistry registry) {
registry.add("spring.security.oauth2.resourceserver.jwt.issuer-uri",
() -> keycloak.getAuthServerUrl() + "/realms/baeldung");
}
4. 创建集成测试
4.1. 获取访问令牌
在抽象类添加获取令牌的方法:
URI authorizationURI = new URIBuilder(keycloak.getAuthServerUrl() + "/realms/baeldung/protocol/openid-connect/token").build();
WebClient webclient = WebClient.builder().build();
MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
formData.put("grant_type", Collections.singletonList("password"));
formData.put("client_id", Collections.singletonList("baeldung-api"));
formData.put("username", Collections.singletonList("jane.doe@example.com"));
formData.put("password", Collections.singletonList("s3cr3t"));
String result = webclient.post()
.uri(authorizationURI)
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.body(BodyInserters.fromFormData(formData))
.retrieve()
.bodyToMono(String.class)
.block();
解析响应提取令牌:
JacksonJsonParser jsonParser = new JacksonJsonParser();
return "Bearer " + jsonParser.parseMap(result)
.get("access_token")
.toString();
4.2. 创建集成测试
添加RestAssured依赖:
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<scope>test</scope>
</dependency>
编写测试用例:
@Test
void givenAuthenticatedUser_whenGetMe_shouldReturnMyInfo() {
given().header("Authorization", getJaneDoeBearer())
.when()
.get("/users/me")
.then()
.body("username", equalTo("janedoe"))
.body("lastname", equalTo("Doe"))
.body("firstname", equalTo("Jane"))
.body("email", equalTo("jane.doe@example.com"));
}
测试将Keycloak获取的访问令牌添加到请求头。
5. 总结
本文展示了如何使用Testcontainers测试真实的Keycloak实例,通过导入realm配置确保测试环境一致性。完整代码示例见GitHub。