我有一个 Spring Security 类,用于验证用户的令牌。我从 Auth0 网站获取了代码,并修改了
antMatcher
部分以适应我的配置。这是代码:
@EnableWebSecurity
public class SecurityConfig {
@Value("${auth0.audience}")
private String audience;
@Value("${spring.security.oauth2.resourceserver.jwt.issuer-uri}")
private String issuer;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
/*
This is where we configure the security required for our endpoints and setup our app to serve as
an OAuth2 Resource Server, using JWT validation.
*/
http
.csrf().disable()
.authorizeRequests()
.antMatchers(HttpMethod.GET, "/data/actuator/**").permitAll()
.antMatchers(HttpMethod.PUT, "/data/**").hasAuthority("SCOPE_data:write")
.anyRequest().authenticated()
.and().cors()
.and().oauth2ResourceServer().jwt();
return http.build();
}
@Bean
JwtDecoder jwtDecoder() {
/*
By default, Spring Security does not validate the "aud" claim of the token, to ensure that this token is
indeed intended for our app. Adding our own validator is easy to do:
*/
NimbusJwtDecoder jwtDecoder = (NimbusJwtDecoder)
JwtDecoders.fromOidcIssuerLocation(issuer);
OAuth2TokenValidator<Jwt> audienceValidator =
new com.nuance.pindata.health.importer.security.AudienceValidator(audience);
OAuth2TokenValidator<Jwt> withIssuer = JwtValidators.createDefaultWithIssuer(issuer);
OAuth2TokenValidator<Jwt> withAudience = new DelegatingOAuth2TokenValidator<>(withIssuer, audienceValidator);
jwtDecoder.setJwtValidator(withAudience);
return jwtDecoder;
}
}
我现在正在尝试编写单元测试,但是没有好的方法来测试它。我实际上可以测试更改方法/路径,但如何编写此单元测试并不简单,它可以通过集成(自动化)测试来完成。
从Spring Security HttpSecurity 配置测试,他建议不要为此类安全配置编写单元测试。这里正确的方法是什么?如果我应该编写单元测试,我怎样才能实现这一目标?
您只能在集成测试中测试执行器端点访问控制 (
@SpringBootTest
)。对于您自己的安全@Components
,您也可以在单元测试中执行此操作(此存储库中的许多示例):
@Controller
与 @WebMvcTest
(如果您使用的是反应式应用程序,则为 @WebfluxTest
)@ExtendWith(SpringExtension.class)
、@EnableMethodSecurity
和 @Import
的普通 JUnit(@Service
或 @Repository
,具有像 @PreAuthorize
表达式这样的方法安全性),以获得具有安全性的自动装配实例spring-security-test
附带了一些 MockMvc 请求后处理器(在您的情况下请参阅 org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.jwt
)以及 WebTestClient 变异器(请参阅org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.mockJwt
)来配置正确类型的身份验证(在您的情况下为JwtAuthenticationToken
)并设置它在测试安全上下文中,但这仅限于 MockMvc 和 WebTestClient 以及 @Controller
测试。
执行器启动的集成测试(
@SpringBootTest
)中的示例用法(但您了解单元测试的想法):
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.jwt;
@SpringBootTest(webEnvironment = WebEnvironment.MOCK)
@AutoConfigureMockMvc
class ApplicationIntegrationTest {
@Autowired
MockMvc api;
@Test
void givenUserIsAnonymous_whenGetLiveness_thenOk() throws Exception {
api.perform(get("/data/actuator/health/liveness"))
.andExpect(status().isOk());
}
@Test
void givenUserIsAnonymous_whenGetMachin_thenUnauthorized() throws Exception {
api.perform(get("/data/machin"))
.andExpect(status().isUnauthorized());
}
@Test
void givenUserIsGrantedWithDataWrite_whenGetMachin_thenOk() throws Exception {
api.perform(get("/data/machin")
.with(jwt().jwt(jwt -> jwt.authorities(List.of(new SimpleGrantedAuthority("SCOPE_data:write"))))))
.andExpect(status().isOk());
}
@Test
void givenUserIsAuthenticatedButNotGrantedWithDataWrite_whenGetMachin_thenForbidden() throws Exception {
api.perform(get("/data/machin")
.with(jwt().jwt(jwt -> jwt.authorities(List.of(new SimpleGrantedAuthority("SCOPE_openid"))))))
.andExpect(status().isForbidden());
}
}
您也可以使用
@WithMockJwtAuth
中的我维护的库。该存储库包含相当多的用于任何类型 @Component
的单元和集成测试的示例(当然还有 @Controllers
或用方法安全性装饰的 @Services
)。以上示例变为:
@Repositories
<dependency>
<groupId>com.c4-soft.springaddons</groupId>
<artifactId>spring-addons-oauth2-test</artifactId>
<version>6.0.12</version>
<scope>test</scope>
</dependency>
Spring 插件启动器
... ). 使用非常简单,
切换到另一个 OIDC 授权服务器所需更改的只是属性。例如,这种情况可能会发生,因为您被迫忙碌(如果他们认为 Auth0 太昂贵或不再可信),或者可能是因为您发现在开发计算机上使用独立的 Keycloak 更方便(它是可以离线使用,我经常这样做)。 不要直接导入
@SpringBootTest(webEnvironment = WebEnvironment.MOCK)
@AutoConfigureMockMvc
class ApplicationIntegrationTest {
@Autowired
MockMvc api;
@Test
void givenUserIsAnonymous_whenGetLiveness_thenOk() throws Exception {
api.perform(get("/data/actuator/health/liveness"))
.andExpect(status().isOk());
}
@Test
void givenUserIsAnonymous_whenGetMachin_thenUnauthorized() throws Exception {
api.perform(get("/data/machin"))
.andExpect(status().isUnauthorized());
}
@Test
@WithMockJwtAuth("SCOPE_data:write")
void givenUserIsGrantedWithDataWrite_whenGetMachin_thenOk() throws Exception {
api.perform(get("/data/machin"))
.andExpect(status().isOk());
}
@Test
@WithMockJwtAuth("SCOPE_openid")
void givenUserIsAuthenticatedButNotGrantedWithDataWrite_whenGetMachin_thenForbidden() throws Exception {
api.perform(get("/data/machin"))
.andExpect(status().isForbidden());
}
}
,而是导入一个薄包装器(
仅由 3 个文件组成):
spring-boot-starter-oauth2-resource-server
默认情况下,用户必须经过身份验证才能访问除
<dependency>
<groupId>com.c4-soft.springaddons</groupId>
<artifactId>spring-addons-webmvc-jwt-resource-server</artifactId>
<version>6.0.12</version>
</dependency>
属性中列出的路由之外的任何路由(见下文)。将所有 Java 配置替换为:
com.c4-soft.springaddons.security.permit-all
您可以删除所有
@Configuration
@EnableMethodSecurity
public class SecurityConfig {
// If not using method-security or to configure actuator RBAC
// You might define a bean of type ExpressionInterceptUrlRegistryPostProcessor
// and Fine tune AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry
}
属性,它会被忽略(如果您验证受众,则
spring.security.oauth2.resourceserver
除外)。可以使用的属性有:spring.security.oauth2.resourceserver.jwt.audiences
傻瓜,不是吗?