【问题标题】:Spring Security - Custom Principal from JWTSpring Security - 来自 JWT 的自定义主体
【发布时间】:2023-04-08 22:07:01
【问题描述】:

我在我的 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

谁能帮我怎么办?谢谢

【问题讨论】:

    标签: spring spring-security oauth-2.0 jwt principal


    【解决方案1】:

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

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

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

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

    我的例子

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

    【讨论】:

      【解决方案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()
              }
                  .csrf{it.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"
      }
      

      【讨论】:

        猜你喜欢
        • 2011-02-02
        • 2013-07-18
        • 2019-02-07
        • 2013-04-17
        • 2016-02-09
        • 2018-01-13
        • 2016-09-04
        • 2020-09-24
        • 1970-01-01
        相关资源
        最近更新 更多