Spring Security - 来自 JWT 的自定义主体

Posted

技术标签:

【中文标题】Spring Security - 来自 JWT 的自定义主体【英文标题】:Spring Security - Custom Principal from JWT 【发布时间】:2021-03-31 15:30:02 【问题描述】:

我在我的 Angular 应用程序中使用 oauth2 和 sso。我的其余后端验证每个请求发送的身份验证令牌。现在我想使用 oid 声明从我的数据库中加载用户并将其保存在主体中。我还想在“GrantedAuthorities”中添加用户权限。

FooService

[...]
public Foo getFooByOid(String oid) throws FooNotFoundException 
    return fooRepository.findByOid(oid)
            .orElseThrow(() -> new FooNotFoundException("Foo with oid: " + oid + " not found"));

安全配置

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter 

    @Override
    protected void configure(HttpSecurity http) throws Exception 
        http.authorizeRequests(auth -> auth
            .anyRequest().authenticated())
            .oauth2ResourceServer(oauth2 -> 
            oauth2.jwt();
        );
       

spring.security.oauth2.resourceserver.jwt.jwk-set-uri 和 spring.security.oauth2.resourceserver.jwt.issuer-uri 在 application.properties 中定义

之后,我的主体包含所有声明、标头和令牌:

Principal

谁能帮我怎么办?谢谢

【问题讨论】:

【参考方案1】:

如果您编写如下代码,您可以通过自定义 AuthenticationManager 执行此操作:

.oauth2ResourceServer.authenticationManagerResolver(request -> new CustomAuthenticationManager(request));

您在 oauth2ResourceServer 上设置的属性会影响 Spring 的 BearerTokenAuthenticationFilter class 的行为

然后您需要自己验证 JWT,并在顶部添加您的自定义声明处理,然后使用相同的令牌缓存后续请求的结果,这很棘手。

我的例子

我有一个相当完整的示例,它的行为类似于这样,它可以在您的 PC 上运行,并且您可以从中借鉴一些想法 - 虽然它是一个相当高级的示例:

Spring Security Configuration

【讨论】:

【参考方案2】:

Gary Archer 的回答是正确的,但也有一些陷阱。

一个完整的 Kotlin 简单示例:

特定于您的应用程序的用户界面:


interface User  
    val id : String
    val email : String
    val displayName : String
    val avatar : String
    val roles : Set<String>
    val domain : String
        get() = this.email.split('@').last()


主体应实现此接口:

class ClaimsNotFull(claim: String) : Exception("Claims not full, not found $claim")

data class IdelPrincipal(
    override val id: String,
    override val email: String,
    override val displayName: String,
    override val avatar: String,
    override val roles: Set<String>,
) : User 
    companion object 
        fun fromJwt(jwt: Jwt): IdelPrincipal 
            fun loadClaim(key: String): String = jwt.claims.getOrElse(key) throw ClaimsNotFull(key) as String
            return IdelPrincipal(
                id = jwt.subject,
                email = loadClaim("email"),
                displayName = loadClaim("displayName"),
                avatar = loadClaim("avatar"),
                roles = loadClaim("roles").split(",").toSet()
            )
        

        fun copyToClaims(user : User, jwt: JWTClaimsSet.Builder) : JWTClaimsSet.Builder 
            return jwt.subject(user.id)
                .claim("email", user.email)
                .claim("displayName",user.displayName)
                .claim("avatar",user.avatar)
                .claim("roles",user.roles.joinToString(","))
        
    



自定义令牌和 JwtConvertor:


class IdelAuthenticationToken(val jwt: Jwt, val user: IdelPrincipal) :
    AbstractAuthenticationToken(IdelAuthorities.from(user.roles)) 

    override fun getCredentials() = jwt // borrowed from JwtAuthenticationToken

    override fun getPrincipal() = user

    override fun isAuthenticated() = true // decoding of jwt is authentication


class IdelPrincipalJwtConvertor : Converter<Jwt, IdelAuthenticationToken> 
    override fun convert(jwt: Jwt): IdelAuthenticationToken 
        val principal = IdelPrincipal.fromJwt(jwt)
        return IdelAuthenticationToken(jwt,principal)
    


配置 Spring Security:

    override fun configure(http: HttpSecurity) 
        http.authorizeRequests 
            it.anyRequest().authenticated()
        
            .csrfit.ignoringAntMatchers("/token")
            .oauth2ResourceServer it.jwt()
            .oauth2ResourceServer().jwt cstm ->
                cstm.jwtAuthenticationConverter(IdelPrincipalJwtConvertor())
            
        http.exceptionHandling 
            it
                .authenticationEntryPoint(BearerTokenAuthenticationEntryPoint())
                .accessDeniedHandler(BearerTokenAccessDeniedHandler())
        
        http .sessionManagement it.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
        http.anonymous().disable()


以及控制器中用于测试的方法:

    @GetMapping("/me2")
    fun me2(@AuthenticationPrincipal user : User) : User 
        val result = MeResult(
            id = user.id,
            domain = user.domain,
            displayName = user.displayName,
            avatar = user.avatar,
            email = user.email,
            authorities = user.roles.toList()
        )
        return user
    

那就是回报:


    "id": "10777",
    "email": "leonid.vygovsky@gmail.com",
    "displayName": "Leonid Vygovskiy",
    "avatar": "",
    "roles": [
        "ROLE_USER"
    ],
    "domain": "gmail.com"

【讨论】:

以上是关于Spring Security - 来自 JWT 的自定义主体的主要内容,如果未能解决你的问题,请参考以下文章

社交登录,spring-security-oauth2 和 spring-security-jwt?

如何在spring security中为来自两个不同表的不同用户配置身份验证?

Spring Security----JWT详解

单点登录JWT与Spring Security OAuth

带有 spring-boot 和 spring-security 的 JWT

Spring Security OAuth2 v5:NoSuchBeanDefinitionException:'org.springframework.security.oauth2.jwt.Jwt