忽略 Spring Boot 中特定 Url 的承载令牌验证

Posted

技术标签:

【中文标题】忽略 Spring Boot 中特定 Url 的承载令牌验证【英文标题】:Ignore Bearer Token Validation for specific Urls in Spring Boot 【发布时间】:2021-04-21 11:55:15 【问题描述】:

我正在将微服务配置为资源服务器,它使用 JWK 端点来验证 JWT 令牌的签名。

我已将配置设置为允许服务中的所有GET 请求。所有其他请求都根据范围和角色进行保护。这是我正在使用的配置。

@EnableReactiveMethodSecurity
class SecurityConfig : WebFluxConfigurer 

    @Bean
    fun authenticationEntryPoint(): ServerAuthenticationEntryPoint 
        return JwtBearerTokenServerAuthenticationEntryPoint()
    

    @Bean
    fun accessDeniedHandler(): ServerAccessDeniedHandler 
        return JwtTokenAccessDeniedHandler()
    

    @Bean
    fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain 
        http
            .authorizeExchange()
            .pathMatchers(HttpMethod.GET).permitAll()
            .pathMatchers("/docs/**", "/v2/api-docs/**", "/").permitAll()
            // Client should have the required scope to write to products
            .pathMatchers(HttpMethod.POST).hasAuthority(PRODUCT_WRITE_SCOPE)
            .pathMatchers(HttpMethod.PUT).hasAuthority(PRODUCT_WRITE_SCOPE)
            .pathMatchers(HttpMethod.DELETE).hasAuthority(PRODUCT_WRITE_SCOPE)
            // health and info urls will be open(permitted to all) others will be checked for authorization
            .matchers(EndpointRequest.to(HealthEndpoint::class.java, InfoEndpoint::class.java)).permitAll()
            .anyExchange().authenticated()
            .and()
            .csrf().disable()
            .formLogin().disable()
            .oauth2ResourceServer()
            .authenticationEntryPoint(authenticationEntryPoint())
            .accessDeniedHandler(accessDeniedHandler())
            .jwt()
            .jwtAuthenticationConverter 
                jwtAuthenticationConverter(it)
            

        return http.build()
    

    private fun jwtAuthenticationConverter(jwt: Jwt): Mono<AbstractAuthenticationToken>? 
        val jwtAuthConverter = ReactiveJwtAuthenticationConverter()
        jwtAuthConverter.setJwtGrantedAuthoritiesConverter 
            val jwtGrantedAuthoritiesConverter = JwtAuthoritiesConverter()
            val reactiveJwtGrantedAuthoritiesConverterAdapter =
                ReactiveJwtGrantedAuthoritiesConverterAdapter(jwtGrantedAuthoritiesConverter)
            reactiveJwtGrantedAuthoritiesConverterAdapter.convert(it)
        
        return jwtAuthConverter.convert(jwt)
    

    companion object 
        private const val PRODUCT_WRITE_SCOPE = "SCOPE_product:write"
    



我面临的问题是,如果我在 GET 请求的授权标头中发送过期令牌,令牌验证仍然会发生并且我收到令牌过期错误。

有没有办法改变配置,使令牌验证只发生在某些端点上,而忽略其他端点?

【问题讨论】:

为什么不在所有 get 请求中发送授权标头? 我在所有请求中发送授权标头。这就是问题出现的地方。如果令牌已过期,则 GET 请求失败,说明令牌已过期。 是的,我确实理解您的问题,但正如我所说,当您不想获得授权时,更好的解决方案是不发送令牌。 @Toerktumlare 这当然是一个选项,但我认为它可能会在客户端实现中添加更多条件检查,因此希望避免这种情况。如果没有办法做到这一点,那就是我会去的。 从语义上考虑,如果您发送 Authorization 标头,您是在告诉后端“嗨,这是我的请求,这是证明我是谁的标头”。现在您想发送一个身份验证标头,即使您不想要或不应该,而是忽略它?对我来说,这听起来像是给自己制造更多问题。我在春天看不到任何明显的东西可以让你做到这一点,而且很确定春天同意我的看法。如果您不想被授权,请不要发送授权标头。 【参考方案1】:

这是我为解决问题所做的。您可以选择指定安全配置应应用于哪些路径。这是指定它的代码 sn-p。

.securityMatcher 
                ServerWebExchangeMatchers.matchers(
                    ServerWebExchangeMatchers.pathMatchers(HttpMethod.POST, "/**"),
                    ServerWebExchangeMatchers.pathMatchers(HttpMethod.PUT, "/**"),
                    ServerWebExchangeMatchers.pathMatchers(HttpMethod.DELETE, "/**")
                ).matches(it)
            

这是完整的配置。

@EnableReactiveMethodSecurity
class SecurityConfig : WebFluxConfigurer 

    @Bean
    fun authenticationEntryPoint(): ServerAuthenticationEntryPoint 
        return JwtBearerTokenServerAuthenticationEntryPoint()
    

    @Bean
    fun accessDeniedHandler(): ServerAccessDeniedHandler 
        return JwtTokenAccessDeniedHandler()
    

    @Bean
    fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain 
        http
            .securityMatcher 
                ServerWebExchangeMatchers.matchers(
                    ServerWebExchangeMatchers.pathMatchers(HttpMethod.POST, "/**"),
                    ServerWebExchangeMatchers.pathMatchers(HttpMethod.PUT, "/**"),
                    ServerWebExchangeMatchers.pathMatchers(HttpMethod.DELETE, "/**")
                ).matches(it)
            
            .authorizeExchange()
            .pathMatchers("/docs/**", "/v2/api-docs/**", "/").permitAll()
            // Client should have the required scope to write to products
            .pathMatchers(HttpMethod.POST).hasAuthority(PRODUCT_WRITE_SCOPE)
            .pathMatchers(HttpMethod.PUT).hasAuthority(PRODUCT_WRITE_SCOPE)
            .pathMatchers(HttpMethod.DELETE).hasAuthority(PRODUCT_WRITE_SCOPE)
            // health and info urls will be open(permitted to all) others will be checked for authorization
            .matchers(EndpointRequest.to(HealthEndpoint::class.java, InfoEndpoint::class.java)).permitAll()
            .anyExchange().authenticated()
            .and()
            .csrf().disable()
            .formLogin().disable()
            .oauth2ResourceServer()
            .authenticationEntryPoint(authenticationEntryPoint())
            .accessDeniedHandler(accessDeniedHandler())
            .jwt()
            .jwtAuthenticationConverter 
                jwtAuthenticationConverter(it)
            

        return http.build()
    

    private fun jwtAuthenticationConverter(jwt: Jwt): Mono<AbstractAuthenticationToken>? 
        val jwtAuthConverter = ReactiveJwtAuthenticationConverter()
        jwtAuthConverter.setJwtGrantedAuthoritiesConverter 
            val jwtGrantedAuthoritiesConverter = JwtAuthoritiesConverter()
            val reactiveJwtGrantedAuthoritiesConverterAdapter =
                ReactiveJwtGrantedAuthoritiesConverterAdapter(jwtGrantedAuthoritiesConverter)
            reactiveJwtGrantedAuthoritiesConverterAdapter.convert(it)
        
        return jwtAuthConverter.convert(jwt)
    

    companion object 
        private const val PRODUCT_WRITE_SCOPE = "SCOPE_product:write"
    


【讨论】:

这种配置的缺点是你需要opt-in来保证安全,而安全应该总是opt-out

以上是关于忽略 Spring Boot 中特定 Url 的承载令牌验证的主要内容,如果未能解决你的问题,请参考以下文章

在 spring-boot 中禁用特定 url 的 Keycloak 身份验证

在aws上忽略基于url到spring boot 2应用程序的黑客攻击是不是安全?

特定 url 的多个身份验证提供程序 - Spring Boot Security

如何将 Spring Boot @RepositoryRestResource 映射到特定的 url?

带有嵌入式 Tomcat 的 Spring Boot 忽略了方法角色

如何将Spring Boot RepositoryRestResource映射到特定的URL