You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Spring Security中解码并验证EdDSA JWT

Spring Security中EdDSA JWT解码与验证解决方案

1. 能否通过Spring标准机制获取publicJWK?

可以复用Spring Security依赖的nimbus-jose库中的RemoteJWKSet从JWKS URI获取公钥集合,这是Spring生态下的标准工具(Spring Security默认JwtDecoder就是基于它实现的)。你不需要自行编写HTTP请求逻辑,直接实例化RemoteJWKSet并传入JWKS URI即可,它会自动处理JWK的缓存和刷新。

2. 是否需要fork nimbus-jose库添加EdDSA支持?

不需要。你计划用Tink做Ed25519签名验证,完全可以在自定义JwtDecoder中结合nimbus的JWK解析能力和Tink的验证能力,无需修改或fork nimbus-jose库。如果你的Spring Security版本较新(如6.0+),nimbus-jose已内置EdDSA支持;若版本较低,用Tink替代签名验证逻辑即可。

3. 验证步骤必须放在JwtDecoder中吗?

建议放在JwtDecoder中,这符合Spring Security的职责划分:JwtDecoder的核心职责就是完成JWT的解码、签名验证、过期校验等合法性检查。若把验证放在后续认证环节(如AuthenticationProvider),会打破职责单一性,不符合框架设计惯例。极端情况下可拆分,但不推荐。

4. 验证步骤的具体实现

以下是整合JWKS获取、Tink Ed25519验证逻辑的自定义JwtDecoder:

import com.google.crypto.tink.subtle.Ed25519Verifier
import com.nimbusds.jose.JOSEException
import com.nimbusds.jose.JWSAlgorithm
import com.nimbusds.jose.jwk.JWK
import com.nimbusds.jose.jwk.source.JWKSource
import com.nimbusds.jose.jwk.source.RemoteJWKSet
import com.nimbusds.jose.proc.SecurityContext
import com.nimbusds.jwt.SignedJWT
import org.springframework.security.oauth2.jwt.Jwt
import org.springframework.security.oauth2.jwt.JwtDecoder
import java.net.URL
import java.nio.charset.StandardCharsets
import java.util.Base64

class CustomEdDsaJwtDecoder(jwksUri: String) : JwtDecoder {
    // 基于nimbus实现的远程JWKS获取工具,自动缓存JWK集合
    private val jwkSource: JWKSource<SecurityContext> = RemoteJWKSet(URL(jwksUri))

    override fun decode(token: String): Jwt {
        val signedJwt = SignedJWT.parse(token)
        val kid = signedJwt.header.keyID ?: throw IllegalArgumentException("JWT缺失'kid'头字段")
        
        // 根据kid从JWKS中匹配对应公钥
        val jwkSet = jwkSource.getJWKSet(SecurityContext())
        val jwk = jwkSet.getKeyByKeyId(kid) ?: throw IllegalArgumentException("未找到对应kid的JWK: $kid")
        
        // 校验算法是否为EdDSA
        if (jwk.algorithm != JWSAlgorithm.EdDSA) {
            throw IllegalArgumentException("不支持的算法: ${jwk.algorithm}")
        }

        // 将JWK转换为Tink Ed25519Verifier所需的公钥字节数组
        val publicKeyBytes = when {
            jwk.isOctetKeyPair -> jwk.toOctetKeyPair().publicKey.toByteArray()
            else -> throw IllegalArgumentException("不支持的EdDSA JWK类型")
        }

        // 使用Tink验证JWT签名
        val verifier = Ed25519Verifier(publicKeyBytes)
        val signingInput = "${signedJwt.header.toJSONString()}.${signedJwt.payload.toJSONString()}"
        try {
            verifier.verify(signingInput.toByteArray(StandardCharsets.UTF_8), signedJwt.signature.toByteArray())
        } catch (ex: Exception) {
            throw RuntimeException("JWT签名无效: ${ex.message}", ex)
        }

        // 校验过期、生效时间等标准Claims
        val claimsSet = signedJwt.jwtClaimsSet
        val now = System.currentTimeMillis()
        if (claimsSet.expirationTime.time < now) {
            throw RuntimeException("JWT已过期")
        }
        if (claimsSet.issueTime.time > now) {
            throw RuntimeException("JWT尚未生效")
        }

        // 返回解析后的标准Jwt对象
        return Jwt(
            token,
            claimsSet.issueTime.toInstant(),
            claimsSet.expirationTime.toInstant(),
            signedJwt.header.toJSONObject(),
            claimsSet.claims
        )
    }
}

在Spring配置中注册该Decoder:

import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration

@Configuration
class SecurityConfig {
    @Bean
    fun jwtDecoder(): JwtDecoder {
        return CustomEdDsaJwtDecoder("https://你的认证服务器地址/.well-known/jwks.json")
    }
}

5. 更简便的方法:升级Spring Security版本

如果项目可以升级到Spring Security 6.0+,无需自定义Decoder——Spring Security 6.0开始原生支持EdDSA(依赖的nimbus-jose-jwt版本已更新至支持EdDSA的版本)。只需按普通JWT资源服务器配置即可:

import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.oauth2.jwt.JwtDecoders

@Configuration
@EnableWebSecurity
class SecurityConfig {
    @Bean
    fun filterChain(http: HttpSecurity) = http
        .authorizeHttpRequests { auth ->
            auth.anyRequest().authenticated()
        }
        .oauth2ResourceServer { oauth2 ->
            oauth2.jwt { jwt ->
                jwt.decoder(JwtDecoders.fromIssuerLocation("https://你的认证服务器地址/"))
            }
        }
        .build()
}

该配置会自动从认证服务器的JWKS URI获取Ed25519公钥并完成验证,无需任何自定义代码。

内容的提问来源于stack exchange,提问作者D-Wire

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 10:07:11