如何让OAuth2资源服务器每次请求都与授权服务器校验?
如何让OAuth2资源服务器每次请求都校验令牌
我搭建了OAuth2授权服务器和资源服务器,目前资源服务器仅在首次请求时与授权服务器进行令牌校验,即使关闭授权服务器,资源服务器仍能正常处理请求。请问有没有办法让资源服务器每次请求都与授权服务器校验令牌?
授权服务器配置
@EnableWebSecurity @Configuration class WebSecurityConfig { @Bean fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { http .cors { cors -> cors.disable() } .csrf { csrf -> csrf.disable() } .authorizeRequests().anyRequest().authenticated() http.formLogin() return http.build() } @Bean fun passwordEncoder() = BCryptPasswordEncoder() } @Configuration class AuthServerConfig { @Bean @Order(Ordered.HIGHEST_PRECEDENCE) fun clientSecurityFilterChain(http: HttpSecurity): SecurityFilterChain? { OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http) return http.formLogin().and().build() } @Bean fun providerSettings(): ProviderSettings = ProviderSettings .builder() .issuer("http://localhost:8283") .build() @Bean fun jwkSource(): JWKSource<SecurityContext> { val rsaKey = generateRsaKey() val set = JWKSet(rsaKey) return JWKSource { selector: JWKSelector, _: SecurityContext? -> selector.select(set) } } private fun generateRsaKey(): RSAKey { val keyPair = KeyPairGenerator.getInstance("RSA") keyPair.initialize(2048) val keys = keyPair.generateKeyPair() return try { RSAKey.Builder(keys.public as RSAPublicKey).privateKey(keys.private) .keyID(UUID.randomUUID().toString()).build() } catch (e: NoSuchAlgorithmException) { throw RuntimeException("Error while generating rsa keys") } } }
UserDetailsService 与 RegisteredClientRepository
@Component class CustomUserDetailsService( private val authUserRepository: AuthUserRepository ) : UserDetailsService { override fun loadUserByUsername(username: String) = authUserRepository.findByEmailAndDeletedFalse(username) } @Component class CustomRegisteredClientRepository( private val clientRepository: ClientRepository, private val encoder: BCryptPasswordEncoder ) : RegisteredClientRepository { override fun save(registeredClient: RegisteredClient?) { registeredClient?.run { val client = CustomRegisteredClient( clientId, clientIdIssuedAt?.toEpochMilli(), encoder.encode(clientSecret), clientSecretExpiresAt?.toEpochMilli(), clientName, clientAuthenticationMethods.map { it.value }, authorizationGrantTypes.map { it.value }, redirectUris.toList(), scopes.toList(), clientSettings.isRequireAuthorizationConsent, tokenSettings.accessTokenTimeToLive, tokenSettings.refreshTokenTimeToLive ) clientRepository.save(client) } } override fun findById(id: String?): RegisteredClient? { id?.let { val client = clientRepository.findById(it) if (client.isPresent) return client.get().toRegisteredClient() } return null } override fun findByClientId(clientId: String?): RegisteredClient? { clientId?.let { val client = clientRepository.findByClientId(clientId) return client?.toRegisteredClient() } return null } } // 初始化用户与客户端,保存至MongoDB @Component class ContextRefreshEvent( private val clientRepository: ClientRepository, private val passwordEncoder: BCryptPasswordEncoder, private val userRepository: AuthUserRepository ) { @EventListener(ContextRefreshedEvent::class) fun contextRefreshedEvent() { if (clientRepository.findByClientName("demo") == null) { val client = CustomRegisteredClient( "demo", Date().time, passwordEncoder.encode("12345"), Date().time.plus(3600), "demo", listOf(ClientAuthenticationMethod.CLIENT_SECRET_BASIC.value), listOf(AuthorizationGrantType.AUTHORIZATION_CODE.value, AuthorizationGrantType.REFRESH_TOKEN.value), listOf("http://127.0.0.1:3000/authorized"), listOf(OidcScopes.OPENID), true, Duration.ofHours(1), Duration.ofHours(3), ) clientRepository.save(client) println("====== 授权客户端已初始化 ======") } if (userRepository.findByEmailAndDeletedFalse("botir") == null) { val user = AuthUser( "minfin", passwordEncoder.encode("12345"), "botir", true, UserRole.ADMIN ) userRepository.save(user) println("用户已初始化") } } }
客户端应用(资源服务器)安全配置
@Configuration class WebSecurityConfig { @Bean fun filterChain(http: HttpSecurity): SecurityFilterChain { http .cors { cors -> cors.disable() } .csrf { csrf -> csrf.disable() } .sessionManagement { session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS) } .oauth2ResourceServer { server -> server.jwt().jwkSetUri("http://localhost:8283/oauth2/jwks") } .authorizeRequests { auth -> auth.anyRequest().authenticated() } return http.build() } } @RestController class TestController { @GetMapping fun test() = "Test ..." }
解决方案
当前资源服务器使用的是JWT本地验证:它会从授权服务器的JWKS端点获取公钥并缓存,之后所有令牌校验都在本地完成,无需再请求授权服务器,因此关闭授权服务器后仍能正常工作。
要实现每次请求都与授权服务器校验,需要改用**令牌内省(Token Introspection)**机制,让资源服务器每次都向授权服务器的内省端点发送请求,验证令牌的有效性(包括是否被撤销)。具体步骤如下:
1. 确认授权服务器启用内省端点
Spring Authorization Server默认已启用内省端点(/oauth2/introspect),只需确保资源服务器对应的客户端配置了合法的认证信息(clientId、clientSecret),且客户端权限允许访问内省端点。
2. 修改资源服务器配置为不透明令牌验证
替换原有的JWT配置,改为使用opaqueToken配置,指向授权服务器的内省端点,并配置资源服务器作为客户端的认证信息:
@Configuration class WebSecurityConfig { @Value("\${auth-server.introspect-uri}") private lateinit var introspectUri: String @Value("\${resource-server.client-id}") private lateinit var clientId: String @Value("\${resource-server.client-secret}") private lateinit var clientSecret: String @Bean fun filterChain(http: HttpSecurity): SecurityFilterChain { http .cors { cors -> cors.disable() } .csrf { csrf -> csrf.disable() } .sessionManagement { session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS) } .oauth2ResourceServer { server -> server.opaqueToken { opaque -> // 配置令牌内省端点 opaque.introspectionUri(introspectUri) // 配置资源服务器向授权服务器认证的客户端信息 opaque.introspectionClientCredentials(clientId, clientSecret) } } .authorizeRequests { auth -> auth.anyRequest().authenticated() } return http.build() } }
3. 添加配置文件参数
在资源服务器的application.yml中添加以下配置:
auth-server: introspect-uri: http://localhost:8283/oauth2/introspect resource-server: client-id: demo client-secret: 12345
原理说明
使用不透明令牌验证时,资源服务器会将接收到的令牌发送到授权服务器的/oauth2/introspect端点,授权服务器返回令牌的详细有效性信息。每次请求都会触发该内省请求,因此只要授权服务器不可用,资源服务器就无法完成校验,会直接拒绝请求。
内容的提问来源于stack exchange,提问作者Botir Adoshboyev
相关产品推荐
相关产品推荐

