Keycloak 在反应式Spring网关安全性中检索密钥斗篷角色
我从Zuul网关迁移到Spring网关。这迫使我放弃了Webflux的servlet。我使用keydove和keydove角色进行身份验证和授权 没有官方的反应式keydove实现,所以我使用SpringOAuth2。除了检索角色外,它还可以正常工作 我不能使用servlet拦截器,因为WebFlux不允许使用servlet。另外,SpringGateway似乎一般不允许拦截响应体 因此,我的问题仍然存在:如何在SpringGateway中检索keyClope角色,以便其安全性可以使用它们 以下是我使用的一些示例代码: 在SecurityConfig.java类中:Keycloak 在反应式Spring网关安全性中检索密钥斗篷角色,keycloak,spring-cloud-gateway,security-roles,Keycloak,Spring Cloud Gateway,Security Roles,我从Zuul网关迁移到Spring网关。这迫使我放弃了Webflux的servlet。我使用keydove和keydove角色进行身份验证和授权 没有官方的反应式keydove实现,所以我使用SpringOAuth2。除了检索角色外,它还可以正常工作 我不能使用servlet拦截器,因为WebFlux不允许使用servlet。另外,SpringGateway似乎一般不允许拦截响应体 因此,我的问题仍然存在:如何在SpringGateway中检索keyClope角色,以便其安全性可以使用它们 以下
@Bean public security webfilterchain springsecurity filterchain(ServerHttpSecurity http){http.csrf().disable().authorizeExchange(exchanges->exchanges.pathMatchers(“/**”).hasAnyRole(“DIRECTOR”);}
application.yml:
spring.security.oauth2.client.provider.keydove.issuer-uri:..../realms/default
我自己也有同样的问题。我遇到的问题之一是,获取诸如JWT
标记之类的内容的副本,即keydape
具有编码设置的文本
@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());
}
}
正如您所看到的,keydape
支持两种不同类型的角色令牌,但它们不是在顶层定义的,而是在realm\u access
和resource\u access
下定义的,不同之处在于资源访问定义了作为资源一部分的角色,而real\u access定义了跨所有领域定义的角色
要定义这些值,需要定义映射器,如下所示
要将这些值加载到Spring security,您需要定义一个userAuthoritiesMapper
Bean,并将属性中的设置导出为SimpleGrantedAuthority
,如下所示
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());
}
}
@戴夫
谢谢你提醒我这个问题。此后,我在WebFlux中找到了一个解决方法。我已重写ReactiveOAuth2UserService。默认情况下,它有两种风格:OAuth风格和Oidc风格。就我而言,我已经覆盖了Oidc:
@Component public class ReactiveKeycloakUserService extends OidcReactiveOAuth2UserService {
@Override
public Mono<OidcUser> loadUser(OidcUserRequest userRequest) throws ... {
// Call super and then replace result with roles
}
}
@Component公共类reactivekeydeposeurservice扩展了OidcReactiveOAuth2UserService{
@凌驾
公共Mono loadUser(OidcUserRequest userRequest)抛出{
//调用super,然后将结果替换为角色
}
}
Spring将注入我的实例,而不是默认的实例。从userRequest可以检索角色,在调用超类上的相同方法后,可以截取结果并在其上添加角色
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'
@Component public class ReactiveKeycloakUserService extends OidcReactiveOAuth2UserService {
@Override
public Mono<OidcUser> loadUser(OidcUserRequest userRequest) throws ... {
// Call super and then replace result with roles
}
}