在 Spring 启动时忽略特定 Url 的 Bearer Token 验证

Ignore Bearer Token Validation for specific Urls in Spring Boot

我正在将微服务配置为资源服务器,它使用 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 请求的授权 header 中发送过期令牌,令牌验证仍然会发生并且我收到令牌过期错误。

有没有办法更改配置,使令牌验证仅对某些端点发生而对其他端点忽略?

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

.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"
    }

}