基于OAuth 2.0的认证功能实现(Kotlin + Spring Security)
以下是使用 AbstractAuthenticationProcessingFilter
、AuthenticationProvider
、AbstractAuthenticationToken
和 AuthenticationSuccessHandler
实现 OAuth 2.0 认证的完整代码设计。
1. 自定义认证令牌:OAuth2AuthenticationToken
kotlin
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
kotlin
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
kotlin
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
kotlin
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
kotlin
@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. 辅助类定义
kotlin
// 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")
核心流程说明
-
客户端请求
发送请求到
/oauth2/login?code=xxx&client_id=client1
,携带授权码和客户端 ID。 -
过滤器拦截
OAuth2AuthenticationFilter
提取参数并创建OAuth2AuthenticationToken
。 -
认证提供器处理
OAuth2AuthenticationProvider
调用 OAuth2 服务验证授权码,返回用户信息并构建认证成功的 Token。 -
成功响应
OAuth2AuthenticationSuccessHandler
生成 JWT 令牌并返回 JSON 响应。
安全增强建议
-
HTTPS 强制使用
kotlinhttp.requiresChannel().anyRequest().requiresSecure()
-
令牌有效期管理
kotlinJwts.builder() .setExpiration(Date(System.currentTimeMillis() + 3600 * 1000))
-
密钥安全存储
使用环境变量或配置服务器管理密钥:
kotlin@Value("\${jwt.secret}") private lateinit var jwtSecret: String
通过以上设计,可实现基于 OAuth 2.0 授权码模式的认证流程,并灵活扩展为其他授权类型(如隐式模式、密码模式)。