如何用TestRestTemplate禁用Spring Security测试主应用?是否需测试该类?
解决方案
一、在集成测试中禁用Spring Security(避免依赖Auth0服务)
提供三种可行方案,按需选择:
方法1:用@MockBean替代真实SecurityConfig
直接在测试类上添加@MockBean(SecurityConfig.class),Spring Boot会自动用Mock实例替换真实的安全配置类,跳过OAuth2相关逻辑加载,测试可正常访问actuator端点:
@ExtendWith(SpringExtension.class) @SpringBootTest(classes = HealthDataImporterApplication.class, webEnvironment = WebEnvironment.RANDOM_PORT) @TestPropertySource(locations = "classpath:application-test.properties") @MockBean(SecurityConfig.class) // 添加该行 public class HealthDataImporterApplicationTest { @Autowired private TestRestTemplate testRestTemplate; @LocalServerPort private int serverPort; @Test public void health() { assertThat(read(get("health"), "$.status")).isEqualTo("UP"); } private String get(final String resource) { return testRestTemplate.getForObject(String.format("http://localhost:%s/actuator/%s", serverPort, resource), String.class); } }
方法2:编写测试专用安全配置
创建一个完全开放权限的测试用安全配置,覆盖真实配置:
// 测试专用安全配置类 @EnableWebSecurity public class TestSecurityConfig { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.csrf().disable() .authorizeRequests() .anyRequest().permitAll(); // 允许所有请求访问 return http.build(); } }
修改测试类,指定加载该测试配置:
@ExtendWith(SpringExtension.class) @SpringBootTest(classes = {HealthDataImporterApplication.class, TestSecurityConfig.class}, webEnvironment = WebEnvironment.RANDOM_PORT) @TestPropertySource(locations = "classpath:application-test.properties") public class HealthDataImporterApplicationTest { // 原有测试代码保持不变 }
方法3:通过配置属性禁用OAuth2资源服务器
在application-test.properties中添加以下配置,让Spring Security使用无操作JWT解码器,避免调用Auth0服务(需Spring Security 5.6+版本支持):
spring.security.oauth2.resourceserver.jwt.decoder=noop
二、是否有必要测试SecurityConfig?
非常有必要,但建议将安全配置测试与应用集成测试分离,采用单元测试或专项测试验证:
1. 测试SecurityFilterChain规则
验证安全规则是否符合预期,比如actuator端点权限、其他接口权限要求等:
@ExtendWith(SpringExtension.class) @ContextConfiguration(classes = SecurityConfig.class) public class SecurityConfigTest { @Autowired private SecurityFilterChain securityFilterChain; @Autowired private WebApplicationContext context; @Test public void testActuatorGetRequestsPermitted() throws Exception { MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(context) .apply(springSecurity(securityFilterChain)) .build(); // 验证actuator/health允许匿名访问 mockMvc.perform(get("/actuator/health")) .andExpect(status().isOk()); } @Test public void testOtherRequestsRequireScope() throws Exception { MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(context) .apply(springSecurity(securityFilterChain)) .build(); // 验证非actuator请求需权限 mockMvc.perform(get("/api/data")) .andExpect(status().isUnauthorized()); // 验证携带正确scope的请求可正常访问 mockMvc.perform(get("/api/data") .with(jwt().jwt(jwt -> jwt.claim("scope", "scope:scope")))) .andExpect(status().isOk()); } }
2. 测试JwtDecoder验证逻辑
单独验证JWT的受众(aud)、签发者(iss)等声明的校验逻辑:
public class JwtDecoderTest { private JwtDecoder jwtDecoder; private final String testIssuer = "https://test-issuer.com"; private final String testAudience = "test-audience"; @BeforeEach void setUp() { // 提取原SecurityConfig中JwtDecoder的构建逻辑,便于测试 NimbusJwtDecoder decoder = JwtDecoders.fromOidcIssuerLocation(testIssuer); decoder.setJwtValidator(new DelegatingOAuth2TokenValidator<>( JwtValidators.createDefaultWithIssuer(testIssuer), new AudienceValidator(testAudience) )); this.jwtDecoder = decoder; } @Test void testValidJwtWithCorrectAudience() { // 生成包含正确aud和iss的测试JWT(可使用JWT生成库或硬编码测试用token) String validJwt = generateTestJwt(testIssuer, testAudience); Jwt jwt = jwtDecoder.decode(validJwt); assertThat(jwt.getAudience()).contains(testAudience); } @Test void testInvalidJwtWithWrongAudience() { String invalidJwt = generateTestJwt(testIssuer, "wrong-audience"); assertThatThrownBy(() -> jwtDecoder.decode(invalidJwt)) .isInstanceOf(OAuth2TokenValidationException.class); } // 辅助方法:生成测试用JWT private String generateTestJwt(String issuer, String audience) { // 实现JWT生成逻辑,例如使用JJWT库 return Jwts.builder() .setIssuer(issuer) .setAudience(Collections.singletonList(audience)) .setIssuedAt(new Date()) .signWith(SignatureAlgorithm.HS256, "test-secret") .compact(); } }
内容的提问来源于stack exchange,提问作者Jonathan Hagen
相关产品推荐
相关产品推荐

