Webflux 和 keycloak 使用 jwt 中的方法安全级别 @Preauthorize 自定义声明而不是默认范围

Posted

技术标签:

【中文标题】Webflux 和 keycloak 使用 jwt 中的方法安全级别 @Preauthorize 自定义声明而不是默认范围【英文标题】:Webflux and keycloak using for method security level @Preauthorize custom claim in jwt instead of default scope 【发布时间】:2021-07-20 21:32:15 【问题描述】:

根据标题,我正在像这样设置 webflux 配置

@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
public class WebSecurityConfig 
String jwkSetUri = "http://localhost:8080/auth/realms/demo/protocol/openid-connect/certs";
@Bean 
public ReactiveJwtDecoder jwtDecoder() 

return 
ReactiveJwtDecoders.fromIssuerLocation("http://localhost:8080/auth/realms/demo");

 @Bean

public SecurityWebFilterChain configure(ServerHttpSecurity http) 

     http.authorizeExchange()

           
            .oauth2Login()

            .and()
          .
            ...
             .oauth2ResourceServer()
             .jwt()

    .jwkSetUri(jwkSetUri)

            // .jwtDecoder(jwtDecoder())

             .jwtAuthenticationConverter(grantedAuthoritiesExtractor());
            return http.build();


@Bean

Converter<Jwt, Mono<AbstractAuthenticationToken>> grantedAuthoritiesExtractor() 

    GrantedAuthoritiesExtractor extractor = new GrantedAuthoritiesExtractor();

    return new ReactiveJwtAuthenticationConverterAdapter(extractor);


static class GrantedAuthoritiesExtractor

       extends  JwtAuthenticationConverter 


    public Collection<GrantedAuthority> extractAuthorities(Jwt jwt) 

        Collection<?> authorities = (Collection<?>)

                jwt.getClaims().getOrDefault("roles", Collections.emptyList());


        return authorities.stream()

                .map(Object::toString)

                .map(SimpleGrantedAuthority::new)

                .collect(Collectors.toList());
    

使用以下 application.yml

security:
   oauth2:

     client:
       provider:
         keycloak-local:
           issuer-uri: http://localhost:8080/auth/realms/demo
       registration:
         keycloak-local:
           client-id: some-id
           client-secret: ...

     resourceserver:
       jwt:
     
         issuer-uri: http://localhost:8080/auth/realms/demo
         jwk-set-uri: http://localhost:8080/auth/realms/demo/protocol/openid- 
         connect/certs

 

token格式是这样的

"realm_access": 
"roles": [
 "offline_access",
  "uma_authorization"
  ]
   ,
 "resource_access": 
 "someclient": 
  "roles": [
    "uma_protection"
   ]
  ,
  "account": 
    "roles": [
    "manage-account",
    "manage-account-links",
    "view-profile"
  ]
   
   ,
   "scope": "profile  email ",
   "organizationId": "605b74813bd72c65a24a1704",
   "accountId": "605b74813bd72c65a24a1709",
   "email_verified": false,
   "roles": [
   "ROLE_ADMIN"    
     ],
   "preferred_username": "someUsername",
   "userId": "605b74813bd72c65a24a1706",
   "email": "email.com"
    `


    
  

当然,“角色”数组是我最希望访问的数组(是我在数据库中定义的角色)。

在设置之后似乎什么都没有改变。 所以在我的控制器中,我尝试使用 @Preauthorize 注释以访问某些方法,但它继续使用范围声明。

@PreAuthorize("hasAuthority('SCOPE_email')")

有人知道我错过了什么吗?请注意,我参考了官方文档,也参考了this tutorial。 对于 webflux。

谢谢

【问题讨论】:

【参考方案1】:

默认情况下,Spring Security 转换 scopescp 声明中的项目并使用 SCOPE_ 前缀。如果您的应用程序是纯 OAuth2 资源服务器,您可以通过定义自定义 ReactiveJwtAuthenticationConverter bean 来更改这两种约定。

例如,要从roles 范围导出权限并使用`` 前缀(因为ROLE_ 已经是您角色名称的一部分),您可以定义以下bean。 Spring Security 会自动获取它,你不需要从你的 SecurityWebFilterChain 配置中引用它。

@Bean
public ReactiveJwtAuthenticationConverter jwtAuthenticationConverter() 
    var jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
    jwtGrantedAuthoritiesConverter.setAuthorityPrefix("ROLE_");
    jwtGrantedAuthoritiesConverter.setAuthoritiesClaimName("roles");

    var jwtAuthenticationConverter = new ReactiveJwtAuthenticationConverter();
    jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(
            new ReactiveJwtGrantedAuthoritiesConverterAdapter(jwtGrantedAuthoritiesConverter));

    return jwtAuthenticationConverter;

由于您的应用程序也配置为 OAuth2 客户端(使用 oauth2Login()),因此您需要一种不同的方法。您可以定义GrantedAuthoritiesMapperOAuth2UserService。可以在Spring Security documentation 中找到这两个选项的示例。

【讨论】:

您好,Thomas 感谢您的回答。我试图摆脱 .jwtAuthenticationConverter(converter()) 的 SecurityWebFilterChain 并使用您建议的那个 bean。但是,@Preauthorize 仍在使用“范围”声明。我是否在 keycloak 方面遗漏了一些 jwtDecoder 或其他配置?请注意,角色数组类似于角色:[ROLE_ADMIN, ROLE_OTHER] 不是像范围声明那样的空格分隔值,但我不确定它是否适用。 我更新了我的答案(我之前的代码 sn-p 用于非反应性应用程序,现在应该是正确的)。你能再试一次吗? 仍然没有领域名称为空,只是更改了声明名称(请参阅我的编辑),但 @Preauthorize 仍然适用于范围声明。我错过了一些 jwtDecoder 吗? 我没有注意到您的应用程序同时配置为 OAuth2 客户端和 OAuth2 资源服务器,这是我的错误。 JwtAuthenticationConverter 方法仅适用于纯资源服务器。在您的情况下,您需要定义 GrantedAuthoritiesMapper 或 OAuth2UserService。我更新了我的答案以包含所有这些场景的信息。 我正在尝试离开资源服务器和 OAuth2 客户端(我也试图摆脱第一个客户端)并实现 Bean GrantedAuthoritiesMapper 但仍然没有(如果看起来有问题,请参阅 EDIT2)

以上是关于Webflux 和 keycloak 使用 jwt 中的方法安全级别 @Preauthorize 自定义声明而不是默认范围的主要内容,如果未能解决你的问题,请参考以下文章

Keycloak:使用新创建的客户端和用户获取 JWT

在 Spring Boot 中使用 Keycloak 实现 JWT、JWE 和 JWS(签名 JWT)

SpringSecurity - WebFlux环境下整合JWT使用 Token 认证授权

Kong + Keycloak + OAuth:jwt-keycloak 还是 oauth2 插件?

如何使用 Webflux 在 Spring API 处理程序方法中访问 JWT 声明?

使用带有spring security的keycloak JWT令牌时如何修复403