spring security +kotlin 实现oauth2.0 认证

基于OAuth 2.0的认证功能实现(Kotlin + Spring Security)

以下是使用 AbstractAuthenticationProcessingFilterAuthenticationProviderAbstractAuthenticationTokenAuthenticationSuccessHandler 实现 OAuth 2.0 认证的完整代码设计。


1. 自定义认证令牌:OAuth2AuthenticationToken

import org.springframework.security.authentication.AbstractAuthenticationToken
import org.springframework.security.core.GrantedAuthority

class OAuth2AuthenticationToken(
    private val code: String,          // 授权码(Credentials)
    private val clientId: String,      // 客户端ID(Principal)
    authorities: List<GrantedAuthority> = emptyList()
) : AbstractAuthenticationToken(authorities) {

    init {
        isAuthenticated = false       // 初始状态未认证
    }

    override fun getPrincipal(): Any = clientId
    override fun getCredentials(): Any = code

    // 认证成功后调用此方法设置权限
    fun setAuthenticated(authorized: Boolean, authorities: List<GrantedAuthority>) {
        require(authorized) { "Cannot set to unauthenticated" }
        super.setAuthenticated(true)
        super.setDetails(authorities)
    }
}

2. 自定义认证过滤器:OAuth2AuthenticationFilter

import org.springframework.security.core.Authentication
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse

class OAuth2AuthenticationFilter(defaultFilterProcessesUrl: String) :
    AbstractAuthenticationProcessingFilter(defaultFilterProcessesUrl) {

    override fun attemptAuthentication(
        request: HttpServletRequest,
        response: HttpServletResponse
    ): Authentication {
        // 从请求中提取 OAuth2 参数
        val code = request.getParameter("code") ?: throw MissingCodeException()
        val clientId = request.getParameter("client_id") ?: throw MissingClientIdException()

        // 创建未认证的 Token
        val authRequest = OAuth2AuthenticationToken(code, clientId)
        
        // 提交给 AuthenticationManager 进行认证
        return authenticationManager.authenticate(authRequest)
    }
}

3. 自定义认证提供器:OAuth2AuthenticationProvider

import org.springframework.security.authentication.AuthenticationProvider
import org.springframework.security.core.Authentication
import org.springframework.security.core.authority.SimpleGrantedAuthority

class OAuth2AuthenticationProvider(
    private val oAuth2Service: OAuth2Service // 自定义的 OAuth2 服务
) : AuthenticationProvider {

    override fun supports(authentication: Class<*>): Boolean {
        return OAuth2AuthenticationToken::class.java.isAssignableFrom(authentication)
    }

    override fun authenticate(authentication: Authentication): Authentication {
        val token = authentication as OAuth2AuthenticationToken
        val code = token.credentials as String
        val clientId = token.principal as String

        // 调用 OAuth2 服务验证授权码并获取用户信息
        val userInfo = oAuth2Service.exchangeCodeForUserInfo(code, clientId)
        
        // 构建认证成功的 Token
        return OAuth2AuthenticationToken(
            code = code,
            clientId = clientId,
            authorities = userInfo.roles.map { SimpleGrantedAuthority("ROLE_$it") }
        ).apply {
            setAuthenticated(true, authorities)
            details = userInfo // 附加用户详细信息
        }
    }
}

4. 自定义成功处理器:OAuth2AuthenticationSuccessHandler

import com.fasterxml.jackson.databind.ObjectMapper
import org.springframework.security.core.Authentication
import org.springframework.security.web.authentication.AuthenticationSuccessHandler
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse

class OAuth2AuthenticationSuccessHandler(
    private val objectMapper: ObjectMapper
) : AuthenticationSuccessHandler {

    override fun onAuthenticationSuccess(
        request: HttpServletRequest,
        response: HttpServletResponse,
        authentication: Authentication
    ) {
        response.contentType = "application/json"
        response.characterEncoding = "UTF-8"
        
        // 生成响应数据(如 JWT 或用户信息)
        val userInfo = authentication.details as UserInfo
        val accessToken = generateJwtToken(userInfo)
        
        val result = mapOf(
            "access_token" to accessToken,
            "user_id" to userInfo.id,
            "roles" to userInfo.roles
        )
        
        response.writer.write(objectMapper.writeValueAsString(result))
    }

    private fun generateJwtToken(userInfo: UserInfo): String {
        // 实现 JWT 生成逻辑(示例使用 jjwt)
        return Jwts.builder()
            .setSubject(userInfo.id)
            .claim("roles", userInfo.roles)
            .signWith(SignatureAlgorithm.HS256, "your-secret-key")
            .compact()
    }
}

5. 配置 Spring Security

@Configuration
@EnableWebSecurity
class SecurityConfig(
    private val oAuth2Service: OAuth2Service,
    private val objectMapper: ObjectMapper
) : WebSecurityConfigurerAdapter() {

    // 注册认证过滤器
    @Bean
    fun oauth2Filter(): OAuth2AuthenticationFilter {
        val filter = OAuth2AuthenticationFilter("/oauth2/login")
        filter.setAuthenticationSuccessHandler(successHandler())
        filter.setAuthenticationManager(authenticationManagerBean())
        return filter
    }

    // 注册认证提供器
    @Bean
    override fun authenticationManagerBean(): AuthenticationManager {
        return ProviderManager(listOf(oAuth2AuthenticationProvider()))
    }

    @Bean
    fun oAuth2AuthenticationProvider(): OAuth2AuthenticationProvider {
        return OAuth2AuthenticationProvider(oAuth2Service)
    }

    @Bean
    fun successHandler(): OAuth2AuthenticationSuccessHandler {
        return OAuth2AuthenticationSuccessHandler(objectMapper)
    }

    override fun configure(http: HttpSecurity) {
        http
            .csrf().disable()
            .authorizeRequests()
            .antMatchers("/oauth2/login").permitAll()
            .anyRequest().authenticated()
            .and()
            .addFilterBefore(oauth2Filter(), UsernamePasswordAuthenticationFilter::class.java)
    }
}

6. 辅助类定义

// OAuth2 服务接口
interface OAuth2Service {
    fun exchangeCodeForUserInfo(code: String, clientId: String): UserInfo
}

// 用户信息数据类
data class UserInfo(
    val id: String,
    val name: String,
    val roles: List<String>
)

// 自定义异常
class MissingCodeException : AuthenticationException("Missing 'code' parameter")
class MissingClientIdException : AuthenticationException("Missing 'client_id' parameter")

核心流程说明

  1. 客户端请求
    发送请求到 /oauth2/login?code=xxx&client_id=client1,携带授权码和客户端 ID。

  2. 过滤器拦截
    OAuth2AuthenticationFilter 提取参数并创建 OAuth2AuthenticationToken

  3. 认证提供器处理
    OAuth2AuthenticationProvider 调用 OAuth2 服务验证授权码,返回用户信息并构建认证成功的 Token。

  4. 成功响应
    OAuth2AuthenticationSuccessHandler 生成 JWT 令牌并返回 JSON 响应。


安全增强建议

  1. HTTPS 强制使用

    http.requiresChannel().anyRequest().requiresSecure()
    
  2. 令牌有效期管理

    Jwts.builder()
        .setExpiration(Date(System.currentTimeMillis() + 3600 * 1000))
    
  3. 密钥安全存储
    使用环境变量或配置服务器管理密钥:

    @Value("\${jwt.secret}")
    private lateinit var jwtSecret: String
    

通过以上设计,可实现基于 OAuth 2.0 授权码模式的认证流程,并灵活扩展为其他授权类型(如隐式模式、密码模式)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值