Spring 带有 kotlin 的安全授权服务器 0.2.0 为授权代码流提供了 WhiteLabel 错误页面

Spring Security Authorization Server 0.2.0 with kotlin gives WhiteLabel Error Page for Authorization Code Flow

我正在尝试使用 kotlin 实现官方授权服务器模板 (https://github.com/spring-projects/spring-authorization-server/tree/main/samples/default-authorizationserver)。

内存中的用户身份验证工作得很好,但是当我尝试使用授权代码流时,我收到了一个烦人的白标签错误页面:

我正在实施的代码可在 https://github.com/RichardSobreiro/kotlin-spring-security-5-simple

复现过程如下:

使用浏览器发起GET请求:http://localhost:9000/authorize?response_type=code&scope=openid&client_id=yourClientId&state=STATE&redirect_uri=http:/ /127.0.0.1:8080/login/oauth2/code/messaging-client-oidc

您将被重定向到登录页面。 输入凭据用户名“pele”和密码“123456”后,出现 404 错误。

我已经检查了我的项目的包层次结构以避免组件扫描问题,并且还在我的 etc/host 文件 [127.0.0.1 auth-server] 中输入了以下条目,但没有任何帮助我解决我的问题。

这是我的 AuthorizationServerConfig.kt class:

package br.com.cbauthserver.authserverconfig

import br.com.cbauthserver.jwks.KeyGeneratorUtils
import com.nimbusds.jose.jwk.JWKSet
import com.nimbusds.jose.jwk.source.JWKSource
import com.nimbusds.jose.proc.SecurityContext
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.core.Ordered
import org.springframework.core.annotation.Order
import org.springframework.security.config.Customizer
import org.springframework.security.config.Customizer.withDefaults
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration
import org.springframework.security.config.annotation.web.configurers.FormLoginConfigurer
import org.springframework.security.core.userdetails.User
import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
import org.springframework.security.oauth2.core.AuthorizationGrantType
import org.springframework.security.oauth2.core.ClientAuthenticationMethod
import org.springframework.security.oauth2.core.oidc.OidcScopes
import org.springframework.security.oauth2.jwt.JwtDecoder
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository
import org.springframework.security.oauth2.server.authorization.config.ProviderSettings
import org.springframework.security.oauth2.server.authorization.config.TokenSettings
import org.springframework.security.provisioning.InMemoryUserDetailsManager
import org.springframework.security.web.SecurityFilterChain
import java.time.Duration
import java.util.*

import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository

@Configuration(proxyBeanMethods = false)
class AuthorizationServerConfig(
) {
    private val issuerUrl = "http://auth-server:9000"
    private val yourClientId = "yourClientId"
    private val yourSecret = "yourSecret"

    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    fun authorizationServerSecurityFilterChain(http: HttpSecurity): SecurityFilterChain {
        OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
        return http.formLogin(Customizer.withDefaults()).build();

        return http.build()
    }

    @Bean
    fun registeredClientRepository (): RegisteredClientRepository  {
        val registeredClient: RegisteredClient = RegisteredClient.withId(UUID.randomUUID().toString())
            .clientId(yourClientId)
            .clientSecret(yourSecret)
            .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
            .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
            .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
            .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
            .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_POST)
            .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_JWT)
            .redirectUri("http://127.0.0.1:8080/login/oauth2/code/messaging-client-oidc")
            .scope(OidcScopes.OPENID)
            //.clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
            .build()

        return InMemoryRegisteredClientRepository(listOf(registeredClient))
    }

    @Bean
    fun jwkSource(): JWKSource<SecurityContext> {
        val rsaKey = KeyGeneratorUtils.generateRSAKey()
        val jwkSet = JWKSet(rsaKey)
        return JWKSource<SecurityContext> { jwkSelector, _ -> jwkSelector.select(jwkSet) }
    }

    @Bean
    fun jwtDecoder(jwkSource: JWKSource<SecurityContext>): JwtDecoder {
        return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource)
    }

    @Bean
    fun providerSettings(): ProviderSettings {
        return ProviderSettings.builder().issuer(issuerUrl).build()
    }

    @Bean
    fun passwordEncoder() = BCryptPasswordEncoder(10)

    @Bean
    fun users(): UserDetailsService {
        val user = User.builder()
            .username("pele")
            .password("$2a$10$b.Rm.8NeuT7hS3Qwy1RPGuuHNMzjEk01vM7ExvW/h11KAHainYBfK")
            //.password("123456")
            .roles("USER")
            .build()
        val admin = User.builder()
            .username("garrincha")
            .password("$2a$10$b.Rm.8NeuT7hS3Qwy1RPGuuHNMzjEk01vM7ExvW/h11KAHainYBfK")
            //.password("123456")
            .roles("USER", "ADMIN")
            .build()
        return InMemoryUserDetailsManager(user, admin)
    }

}

有人可以帮我吗?

您正在混合密码编码,而没有提供可以处理多种编码的PasswordEncoder

您已经定义了一个 BCryptPasswordEncoder bean,它将替换默认的密码编码器

@Bean
fun passwordEncoder() = BCryptPasswordEncoder(10)

但是,当 RegisteredClient 创建时,它使用明文密码

val registeredClient: RegisteredClient = RegisteredClient.withId(UUID.randomUUID().toString())
        .clientId("yourClientId")
        .clientSecret("yourSecret")

一种解决方案是对 clientSecret 进行编码

val registeredClient: RegisteredClient = RegisteredClient.withId(UUID.randomUUID().toString())
        .clientId("yourClientId")
        .clientSecret(passwordEncoder.encode("yourSecret"))

另一个选择是使用 DelegatingPasswordEncoder 如果您需要使用不同的编码来存储您的密码/秘密。