首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >在反应式Spring Gateway安全性中检索Keycloak角色

在反应式Spring Gateway安全性中检索Keycloak角色
EN

Stack Overflow用户
提问于 2021-04-22 16:11:49
回答 2查看 373关注 0票数 0

我从Zuul Gateway迁移到Spring Gateway。这迫使我放弃Servlets,转而使用Webflux。我使用KeyCloak和KeyCloak角色进行身份验证和授权。

没有正式的响应式KeyCloak实现,所以我使用Spring OAuth2。除了检索角色之外,它工作得很好。

我不能使用servlet拦截器,因为WebFlux不允许使用servlet。另外,似乎Spring Gateway一般不允许拦截响应体。

因此,我的问题仍然存在:如何在Spring Gateway中检索KeyCloak角色,以便其安全性可以使用它们?

下面是我使用的一些示例代码:在SecurityConfig.java类中:

@Bean public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) { http.csrf().disable().authorizeExchange(exchanges -> exchanges.pathMatchers("/**").hasAnyRole("DIRECTOR")); }

application.yml:

spring.security.oauth2.client.provider.keycloak.issuer-uri: ..../realms/default

EN

回答 2

Stack Overflow用户

发布于 2021-05-18 03:24:05

我自己也有同样的问题。我得到它的一个问题是得到像JWT标签这样的东西的副本,即Keycloak对你的设置进行编码的文本

代码语言:javascript
复制
    @GetMapping("/whoami")
    @ResponseBody
    public Map<String, Object> index(
            @RegisteredOAuth2AuthorizedClient OAuth2AuthorizedClient authorizedClient,
                    Authentication auth) {
            log.error("XXAuth is {}",auth);
            log.error("XXClient is {}", authorizedClient.getClientRegistration());
            log.error("XXClient access  is {}", authorizedClient.getAccessToken());
            log.error("Token {}",authorizedClient.getAccessToken().getTokenValue());
   }

这段代码将获取对话中的一些值,令牌部分是JWT令牌,您可以将其复制并粘贴到jwt.io中,并找出Keycloak实际发送了什么。

这通常看起来像

代码语言:javascript
复制
{
  "exp": 1622299931,
  "iat": 1622298731,
  "auth_time": 1622298258,
  "jti": "635ca59f-c87b-40da-b4ae-39774ed8098a",
  "iss": "http://clunk:8080/auth/realms/spring-cloud-gateway-realm",
  "sub": "6de0d95f-95b0-419d-87a4-b2862e8d0763",
  "typ": "Bearer",
  "azp": "spring-cloud-gateway-client",
  "nonce": "2V8_3siQjTOIRbfs68BHwzvz3-dWeqXGUultzhJUWrA",
  "session_state": "dd226823-90bc-429e-9cac-bb575b7d4fa0",
  "acr": "0",
  "realm_access": {
      "roles": [
          "ROLE_ANYONE"
      ]
  },
  "resource_access": {
      "spring-cloud-gateway-client": {
          "roles": [
              "ROLE_ADMIN_CLIENT"
          ]
      }
  },
  "scope": "openid email profile roles",
  "email_verified": true,
  "preferred_username": "anon"

}

正如您所看到的,Keycloak支持两种不同类型的角色令牌,但它们不是在顶层定义的,而是在realm_accessresource_access下,不同之处在于资源访问定义了属于资源的角色,而real_access定义了跨所有领域定义的角色。

要定义这些值,需要定义一个映射器,如下所示

要将这些值加载到Spring security中,您需要定义一个userAuthoritiesMapper Bean,并将属性中的设置导出为SimpleGrantedAuthority,如下所示。

代码语言:javascript
复制
package foo.bar.com;

import lombok.extern.slf4j.Slf4j;
import net.minidev.json.JSONArray;
import net.minidev.json.JSONObject;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableReactiveMethodSecurity;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
import org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;

@Slf4j
@EnableWebFluxSecurity
@EnableReactiveMethodSecurity

public class RoleConfig {
        

        
        @Bean
        GrantedAuthoritiesMapper userAuthoritiesMapper() {
                String ROLES_CLAIM = "roles";
                return authorities -> {
                        
                        Set<GrantedAuthority> mappedAuthorities = new HashSet<>();
                        
                        for (Object authority : authorities) {
                                boolean isOidc = authority instanceof OidcUserAuthority;
                                
                                if (isOidc) {
                                        log.error("Discovered an Oidc type of object");
                                        var oidcUserAuthority = (OidcUserAuthority) authority;
                                        java.util.Map<String, Object> attribMap = oidcUserAuthority.getAttributes();
                                        JSONObject jsonClaim;
                                        for (String attrib : attribMap.keySet()) {
                                                log.error("Attribute name  {}  type {} ", attrib, attrib.getClass().getName());
                                                Object claim = attribMap.get(attrib);
                                                if (attrib.equals("realm_access")) {
                                                        log.error("Define on roles for  entire client");
                                                        
                                                        jsonClaim = (JSONObject) claim;
                                                        if (!jsonClaim.isEmpty()) {
                                                                log.error("JobClaim is {}", jsonClaim);
                                                                Object roleStr = jsonClaim.get("roles");
                                                                if (roleStr != null) {
                                                                        log.error("Role String {}", roleStr.getClass().getName());
                                                                        JSONArray theRoles = (JSONArray) roleStr; //jsonClaim.get("roles");
                                                                        for (Object roleName : theRoles) {
                                                                                log.error("Name {} ", roleName);
                                                                        }
                                                                        
                                                                }
                                                        }
                                                }
                                                if (attrib.equals("resource_access")) {
                                                        log.error("Unique to attrib client");
                                                        jsonClaim = (JSONObject) claim;
                                                        if (!jsonClaim.isEmpty()) {
                                                                log.error("Job is {}", jsonClaim);
                                                                
                                                                String clientName = jsonClaim.keySet().iterator().next();
                                                                log.error("Client name {}", clientName);
                                                                JSONObject roleObj = (JSONObject) jsonClaim.get(clientName);
                                                                Object roleNames = roleObj.get("roles");
                                                                log.error("Role names {}", roleNames.getClass().getName());
                                                                JSONArray theRoles = (JSONArray) roleObj.get("roles");
                                                                for (Object roleName : theRoles) {
                                                                        log.error("Name {} ", roleName);
                                                                }
                                                                
                                                        }
                                                }
                                                
                                        }
                                        
                                        var userInfo = oidcUserAuthority.getUserInfo();
                                        log.error("UserInfo {}", userInfo);
                                        for (String key : userInfo.getClaims().keySet()) {
                                                log.error("UserInfo keys {}", key);
                                        }
                                        if (userInfo.containsClaim(ROLES_CLAIM)) {
                                                var roles = userInfo.getClaimAsStringList(ROLES_CLAIM);
                                                mappedAuthorities.addAll(generateAuthoritiesFromClaim(roles));
                                        } else {
                                                log.error("userInfo DID NOT FIND A claim");
                                        }
                                } else {
                                        
                                        var oauth2UserAuthority = (SimpleGrantedAuthority) authority;
                                        log.error("Authority name " + authority.getClass().getName());
                                }
                        }
                        
                        return mappedAuthorities;
                };
        }
        
        private Collection<GrantedAuthority> generateAuthoritiesFromClaim(Collection<String> roles) {
                return roles.stream()
                        .map(role -> new SimpleGrantedAuthority("ROLE_" + role))
                        .collect(Collectors.toList());
        }
}

请注意,这段代码是基于在OAuth2 Login with custom granted authorities from UserInfo找到的一个示例,对属性的访问是我自己的工作。

如果找不到realm_access,将在最高级别生成错误消息resource_access,因为我假设使用此代码的原因是想要解码Keycloak引用。

正常工作时,它会生成以下输出

代码语言:javascript
复制
2021-05-29 15:32:11.249 ERROR 7394 --- [or-http-epoll-5] com.jdriven.gateway.RoleConfig           : Discovered an Oidc type of object
2021-05-29 15:32:11.249 ERROR 7394 --- [or-http-epoll-5] com.jdriven.gateway.RoleConfig           : Attribute name  at_hash  type java.lang.String 
2021-05-29 15:32:11.249 ERROR 7394 --- [or-http-epoll-5] com.jdriven.gateway.RoleConfig           : Attribute name  sub  type java.lang.String 
2021-05-29 15:32:11.249 ERROR 7394 --- [or-http-epoll-5] com.jdriven.gateway.RoleConfig           : Attribute name  resource_access  type java.lang.String 
2021-05-29 15:32:11.249 ERROR 7394 --- [or-http-epoll-5] com.jdriven.gateway.RoleConfig           : Unique to attrib client
2021-05-29 15:32:11.249 ERROR 7394 --- [or-http-epoll-5] com.jdriven.gateway.RoleConfig           : Job is {"spring-cloud-gateway-client":{"roles":["ROLE_ADMIN_CLIENT"]}}
2021-05-29 15:32:11.249 ERROR 7394 --- [or-http-epoll-5] com.jdriven.gateway.RoleConfig           : Client name spring-cloud-gateway-client
2021-05-29 15:32:11.249 ERROR 7394 --- [or-http-epoll-5] com.jdriven.gateway.RoleConfig           : Role names net.minidev.json.JSONArray
2021-05-29 15:32:11.249 ERROR 7394 --- [or-http-epoll-5] com.jdriven.gateway.RoleConfig           : Name ROLE_ADMIN_CLIENT 
2021-05-29 15:32:11.249 ERROR 7394 --- [or-http-epoll-5] com.jdriven.gateway.RoleConfig           : Attribute name  email_verified  type java.lang.String 
2021-05-29 15:32:11.249 ERROR 7394 --- [or-http-epoll-5] com.jdriven.gateway.RoleConfig           : Attribute name  iss  type java.lang.String 
2021-05-29 15:32:11.249 ERROR 7394 --- [or-http-epoll-5] com.jdriven.gateway.RoleConfig           : Attribute name  typ  type java.lang.String 
2021-05-29 15:32:11.249 ERROR 7394 --- [or-http-epoll-5] com.jdriven.gateway.RoleConfig           : Attribute name  preferred_username  type java.lang.String 
2021-05-29 15:32:11.249 ERROR 7394 --- [or-http-epoll-5] com.jdriven.gateway.RoleConfig           : Attribute name  nonce  type java.lang.String 
2021-05-29 15:32:11.249 ERROR 7394 --- [or-http-epoll-5] com.jdriven.gateway.RoleConfig           : Attribute name  aud  type java.lang.String 
2021-05-29 15:32:11.249 ERROR 7394 --- [or-http-epoll-5] com.jdriven.gateway.RoleConfig           : Attribute name  acr  type java.lang.String 
2021-05-29 15:32:11.249 ERROR 7394 --- [or-http-epoll-5] com.jdriven.gateway.RoleConfig           : Attribute name  realm_access  type java.lang.String 
2021-05-29 15:32:11.249 ERROR 7394 --- [or-http-epoll-5] com.jdriven.gateway.RoleConfig           : Define on roles for  entire client
2021-05-29 15:32:11.249 ERROR 7394 --- [or-http-epoll-5] com.jdriven.gateway.RoleConfig           : JobClaim is {"roles":["ROLE_ANYONE"]}
2021-05-29 15:32:11.249 ERROR 7394 --- [or-http-epoll-5] com.jdriven.gateway.RoleConfig           : Role String net.minidev.json.JSONArray
2021-05-29 15:32:11.249 ERROR 7394 --- [or-http-epoll-5] com.jdriven.gateway.RoleConfig           : Name ROLE_ANYONE 
2021-05-29 15:32:11.250 ERROR 7394 --- [or-http-epoll-5] com.jdriven.gateway.RoleConfig           : Attribute name  azp  type java.lang.String 
2021-05-29 15:32:11.250 ERROR 7394 --- [or-http-epoll-5] com.jdriven.gateway.RoleConfig           : Attribute name  auth_time  type java.lang.String 
2021-05-29 15:32:11.250 ERROR 7394 --- [or-http-epoll-5] com.jdriven.gateway.RoleConfig           : Attribute name  exp  type java.lang.String 
2021-05-29 15:32:11.250 ERROR 7394 --- [or-http-epoll-5] com.jdriven.gateway.RoleConfig           : Attribute name  session_state  type java.lang.String 
2021-05-29 15:32:11.250 ERROR 7394 --- [or-http-epoll-5] com.jdriven.gateway.RoleConfig           : Attribute name  iat  type java.lang.String 
2021-05-29 15:32:11.250 ERROR 7394 --- [or-http-epoll-5] com.jdriven.gateway.RoleConfig           : Attribute name  jti  type java.lang.String 
2021-05-29 15:32:11.250 ERROR 7394 --- [or-http-epoll-5] com.jdriven.gateway.RoleConfig           : UserInfo org.springframework.security.oauth2.core.oidc.OidcUserInfo@8be9a0b8
2021-05-29 15:32:11.250 ERROR 7394 --- [or-http-epoll-5] com.jdriven.gateway.RoleConfig           : UserInfo keys sub
2021-05-29 15:32:11.250 ERROR 7394 --- [or-http-epoll-5] com.jdriven.gateway.RoleConfig           : UserInfo keys email_verified
2021-05-29 15:32:11.250 ERROR 7394 --- [or-http-epoll-5] com.jdriven.gateway.RoleConfig           : UserInfo keys preferred_username
2021-05-29 15:32:11.250 ERROR 7394 --- [or-http-epoll-5] com.jdriven.gateway.RoleConfig           : userInfo DID NOT FIND A claim
2021-05-29 15:32:11.252 ERROR 7394 --- [or-http-epoll-5] com.jdriven.gateway.RoleConfig           : Authority name org.springframework.security.core.authority.SimpleGrantedAuthority
2021-05-29 15:32:11.252 ERROR 7394 --- [or-http-epoll-5] com.jdriven.gateway.RoleConfig           : Authority name org.springframework.security.core.authority.SimpleGrantedAuthority
2021-05-29 15:32:11.252 ERROR 7394 --- [or-http-epoll-5] com.jdriven.gateway.RoleConfig           : Authority name org.springframework.security.core.authority.SimpleGrantedAuthority
2021-05-29 15:32:11.252 ERROR 7394 --- [or-http-epoll-5] com.jdriven.gateway.RoleConfig           : Authority name org.springframework.security.core.authority.SimpleGrantedAuthority
2021-05-29 15:32:11.252 DEBUG 7394 --- [or-http-epoll-5] o.s.w.r.f.client.ExchangeFunctions       : [34ff3355] Cancel signal (to close connection)
2021-05-29 15:32:11.252 DEBUG 7394 --- [or-http-epoll-5] o.s.w.r.f.client.ExchangeFunctions       : [1b083d68] Cancel signal (to close connection)
2021-05-29 15:32:11.254 DEBUG 7394 --- [or-http-epoll-5] ebSessionServerSecurityContextRepository : Saved SecurityContext 'SecurityContextImpl [Authentication=OAuth2AuthenticationToken [Principal=Name: [anon], Granted Authorities: [[ROLE_USER, SCOPE_email, SCOPE_openid, SCOPE_profile, SCOPE_roles]], User Attributes: [{at_hash=GCz2JybWiLc-42ACnjLJ6w, sub=6de0d95f-95b0-419d-87a4-b2862e8d0763, resource_access={"spring-cloud-gateway-client":{"roles":["ROLE_ADMIN_CLIENT"]}}, email_verified=true, iss=http://clunk:8080/auth/realms/spring-cloud-gateway-realm, typ=ID, preferred_username=anon, nonce=2V8_3siQjTOIRbfs68BHwzvz3-dWeqXGUultzhJUWrA, aud=[spring-cloud-gateway-client], acr=0, realm_access={"roles":["ROLE_ANYONE"]}, azp=spring-cloud-gateway-client, auth_time=2021-05-29T14:24:18Z, exp=2021-05-29T14:52:11Z, session_state=dd226823-90bc-429e-9cac-bb575b7d4fa0, iat=2021-05-29T14:32:11Z, jti=7d479a85-d76e-4930-9c86-b384a56d7af5}], Credentials=[PROTECTED], Authenticated=true, Details=null, Granted Authorities=[]]]' in WebSession: 'org.springframework.web.server.session.InMemoryWebSessionStore$InMemoryWebSession@69c3d462'
票数 0
EN

Stack Overflow用户

发布于 2021-05-19 14:32:06

@Dave谢谢你提醒我这个问题。从那以后,我在WebFlux中找到了一种解决方法。我已经覆盖了ReactiveOAuth2UserService。默认情况下,它有两种风格,一种是OAuth风格,另一种是Oidc风格。在我的例子中,我已经覆盖了Oidc:

代码语言:javascript
复制
@Component public class ReactiveKeycloakUserService extends OidcReactiveOAuth2UserService {
 @Override
 public Mono<OidcUser> loadUser(OidcUserRequest userRequest) throws ... {
  // Call super and then replace result with roles
 }
}

Spring将注入我的实例,而不是默认的实例。从userRequest中,您可以检索角色,在对超类调用相同的方法之后,您可以截获结果并将角色添加到结果中。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/67209294

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档