【问题标题】:How to write unit test for SecurityConfig for spring security如何为 spring security 编写 SecurityConfig 的单元测试
【发布时间】:2023-02-04 05:12:30
【问题描述】:

我有一个 spring 安全类,验证来自用户的令牌。我从 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 Configuration Testing,他建议不要为此类安全配置编写单元测试。这里正确的方法是什么?如果我应该编写单元测试,我该如何实现?

【问题讨论】:

  • 您要测试 JwtDecoder 吗?

标签: java spring-boot unit-testing spring-security auth0


【解决方案1】:

您只能在集成测试中测试执行器端点访问控制 (@SpringBootTest)。对于您自己的安全 @Components,您也可以在单元测试中进行(this repo 中的许多示例):

  • @Controller@WebMvcTest@WebfluxTest 如果您使用的是响应式应用程序)
  • 普通 JUnit,带有测试组件的 @ExtendWith(SpringExtension.class)@EnableMethodSecurity@Import@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/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());
    }
}

您也可以使用 this libs I maintain 中的 @WithMockJwtAuth。这个 repo 包含相当多的单元和集成测试示例,用于任何类型的@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>
@SpringBootTest(webEnvironment = WebEnvironment.MOCK)
@AutoConfigureMockMvc
class ApplicationIntegrationTest {

    @Autowired
    MockMvc api;

    @Test
    void givenUserIsAnonymous_whenGetLiveness_thenOk() throws Exception {
        api.perform(get("/data/actuator/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 givenUserIsGrantedWithApiRead_whenGetMachin_thenOk() throws Exception {
        api.perform(get("/data/machin"))
            .andExpect(status().isOk());
    }

    @Test
    @WithMockJwtAuth("SCOPE_openid")
    void givenUserIsAuthenticatedButNotGrantedWithApiRead_whenGetMachin_thenForbidden() throws Exception {
        api.perform(get("/data/machin"))
            .andExpect(status().isForbidden());
    }
}

Spring-addons 启动器

在与测试注释相同的 repo 中,您会发现简化您的资源服务器安全配置的启动器(并且还可以改进您的 CORS 配置并同步会话和 CSRF 保护禁用第二个不应在活动会话中被禁用...)。

使用超级简单要切换到另一个 OIDC 授权服务器,您需要更改的只是属性.例如,这可能是因为您被忙碌所迫(如果他们认为 Auth0 太贵或不再受信任)或者可能是因为您发现在您的开发机器上使用独立的 Keycloak 更方便(它是离线可用,我经常这样做)。

不要直接导入 spring-boot-starter-oauth2-resource-server,而是在它周围导入一个薄包装(仅限 composed of 3 files):

<dependency>
    <groupId>com.c4-soft.springaddons</groupId>
    <artifactId>spring-addons-webmvc-jwt-resource-server</artifactId>
    <version>6.0.12</version>
</dependency>

默认情况下,用户必须经过身份验证才能访问除com.c4-soft.springaddons.security.permit-all 属性中列出的任何路由(见下文)。将所有 Java conf 替换为:

@Configuration
@EnableMethodSecurity
public class SecurityConfig {
}

spring.security.oauth2.resourceserver 属性替换为:

# Single OIDC JWT issuer but you can add as many as you like
com.c4-soft.springaddons.security.issuers[0].location=https://dev-ch4mpy.eu.auth0.com/

# Mimic spring-security default converter: map authorities with "SCOPE_" prefix
# Difference with your current conf is authorities source is not only "scope" claim but also "roles" and "permissions" ones 
# I would consider map authorities without "SCOPE_" prefix (the default behaviour of my starters) and update access control expressions accordingly
com.c4-soft.springaddons.security.issuers[0].authorities.claims=scope,roles,permissions
com.c4-soft.springaddons.security.issuers[0].authorities.prefix=SCOPE_

# Fine-grained CORS configuration can be set per path as follow:
com.c4-soft.springaddons.security.cors[0].path=/data/api/**
com.c4-soft.springaddons.security.cors[0].allowed-origins=https://localhost,https://localhost:8100,https://localhost:4200
com.c4-soft.springaddons.security.cors[0].allowedOrigins=*
com.c4-soft.springaddons.security.cors[0].allowedMethods=*
com.c4-soft.springaddons.security.cors[0].allowedHeaders=*
com.c4-soft.springaddons.security.cors[0].exposedHeaders=*

# Comma separated list of ant path matchers for resources accessible to anonymous
com.c4-soft.springaddons.security.permit-all=/data/actuator/**

骗子,不是吗?

【讨论】:

    猜你喜欢
    • 2020-09-18
    • 1970-01-01
    • 1970-01-01
    • 2015-05-17
    • 2020-12-02
    • 2014-01-16
    • 1970-01-01
    • 2019-02-12
    • 2018-01-17
    相关资源
    最近更新 更多