如何在 Spring Security 中从 oauth 服务器返回的用户声明中设置用户权限
Posted
技术标签:
【中文标题】如何在 Spring Security 中从 oauth 服务器返回的用户声明中设置用户权限【英文标题】:How to set user authorities from user claims return by an oauth server in spring security 【发布时间】:2019-08-31 16:58:51 【问题描述】:我最近写了一个使用spring security oauth2的spring boot项目,由于某种原因,身份验证服务器是IdentityServer4,我可以在我的项目中成功登录并获取用户名,但是我找不到任何设置用户权限/角色的方法。
request.isUserInRole 总是返回 false。 @PreAuthorize("hasRole('rolename')") 总是把我带到 403。
我在哪里可以放置一些代码来设置权限?
服务器通过 userinfo 端点返回了一些用户声明,我的项目收到了它们,我什至可以在控制器的原理参数中看到它。
这个方法总是返回 403
@ResponseBody
@RequestMapping("admin")
@PreAuthorize("hasRole('admin')")
public String admin(HttpServletRequest request)
return "welcome, you are admin!" + request.isUserInRole("ROLE_admin");
application.properties
spring.security.oauth2.client.provider.test.issuer-uri = http://localhost:5000
spring.security.oauth2.client.provider.test.user-name-attribute = name
spring.security.oauth2.client.registration.test.client-id = java
spring.security.oauth2.client.registration.test.client-secret = secret
spring.security.oauth2.client.registration.test.authorization-grant-type = authorization_code
spring.security.oauth2.client.registration.test.scope = openid profile
我打印声明
@ResponseBody
@RequestMapping()
public Object index(Principal user)
OAuth2AuthenticationToken token = (OAuth2AuthenticationToken)user;
return token.getPrincipal().getAttributes();
并得到结果显示有一个名为“角色”的声明
"key":"value","role":"admin","preferred_username":"bob"
谁能帮帮我,请给我一个解决方案?
编辑 1: 原因是 oauth2 客户端已经移除了提取器,我必须实现 userAuthoritiesMapper。
最后我通过添加以下类得到了这项工作:
@Configuration
public class AppConfig extends WebSecurityConfigurerAdapter
@Override
protected void configure(HttpSecurity http) throws Exception
http.oauth2Login().userInfoEndpoint().userAuthoritiesMapper(this.userAuthoritiesMapper());
//.oidcUserService(this.oidcUserService());
super.configure(http);
private GrantedAuthoritiesMapper userAuthoritiesMapper()
return (authorities) ->
Set<GrantedAuthority> mappedAuthorities = new HashSet<>();
authorities.forEach(authority ->
if (OidcUserAuthority.class.isInstance(authority))
OidcUserAuthority oidcUserAuthority = (OidcUserAuthority)authority;
OidcUserInfo userInfo = oidcUserAuthority.getUserInfo();
if (userInfo.containsClaim("role"))
String roleName = "ROLE_" + userInfo.getClaimAsString("role");
mappedAuthorities.add(new SimpleGrantedAuthority(roleName));
else if (OAuth2UserAuthority.class.isInstance(authority))
OAuth2UserAuthority oauth2UserAuthority = (OAuth2UserAuthority)authority;
Map<String, Object> userAttributes = oauth2UserAuthority.getAttributes();
if (userAttributes.containsKey("role"))
String roleName = "ROLE_" + (String)userAttributes.get("role");
mappedAuthorities.add(new SimpleGrantedAuthority(roleName));
);
return mappedAuthorities;
;
框架变化太快,网上的demo太老了!
【问题讨论】:
我想我在这里找到了原因:github.com/spring-projects/spring-security/issues/5625 docs.spring.io/spring-security/site/docs/current/reference/… 【参考方案1】:我花了几个小时找到了解决方案。问题在于 spring oauth 安全性,默认情况下,它使用密钥“权限”从令牌中获取用户角色。所以,我实现了一个自定义令牌转换器。
你需要的第一个是自定义用户令牌转换器,这里是类:
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.provider.token.UserAuthenticationConverter;
import org.springframework.util.StringUtils;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
public class CustomUserTokenConverter implements UserAuthenticationConverter
private Collection<? extends GrantedAuthority> defaultAuthorities;
private UserDetailsService userDetailsService;
private final String AUTHORITIES = "role";
private final String USERNAME = "preferred_username";
private final String USER_IDENTIFIER = "sub";
public CustomUserTokenConverter()
public void setUserDetailsService(UserDetailsService userDetailsService)
this.userDetailsService = userDetailsService;
public void setDefaultAuthorities(String[] defaultAuthorities)
this.defaultAuthorities = AuthorityUtils.commaSeparatedStringToAuthorityList(StringUtils.arrayToCommaDelimitedString(defaultAuthorities));
public Map<String, ?> convertUserAuthentication(Authentication authentication)
Map<String, Object> response = new LinkedHashMap();
response.put(USERNAME, authentication.getName());
if (authentication.getAuthorities() != null && !authentication.getAuthorities().isEmpty())
response.put(AUTHORITIES, AuthorityUtils.authorityListToSet(authentication.getAuthorities()));
return response;
public Authentication extractAuthentication(Map<String, ?> map)
if (map.containsKey(USER_IDENTIFIER))
Object principal = map.get(USER_IDENTIFIER);
Collection<? extends GrantedAuthority> authorities = this.getAuthorities(map);
if (this.userDetailsService != null)
UserDetails user = this.userDetailsService.loadUserByUsername((String)map.get(USER_IDENTIFIER));
authorities = user.getAuthorities();
principal = user;
return new UsernamePasswordAuthenticationToken(principal, "N/A", authorities);
else
return null;
private Collection<? extends GrantedAuthority> getAuthorities(Map<String, ?> map)
if (!map.containsKey(AUTHORITIES))
return this.defaultAuthorities;
else
Object authorities = map.get(AUTHORITIES);
if (authorities instanceof String)
return AuthorityUtils.commaSeparatedStringToAuthorityList((String)authorities);
else if (authorities instanceof Collection)
return AuthorityUtils.commaSeparatedStringToAuthorityList(StringUtils.collectionToCommaDelimitedString((Collection)authorities));
else
throw new IllegalArgumentException("Authorities must be either a String or a Collection");
您需要一个自定义令牌转换器,这里是:
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.DefaultAccessTokenConverter;
import org.springframework.stereotype.Component;
import java.util.Map;
@Component
public class CustomAccessTokenConverter extends DefaultAccessTokenConverter
@Override
public OAuth2Authentication extractAuthentication(Map<String, ?> claims)
OAuth2Authentication authentication = super.extractAuthentication(claims);
authentication.setDetails(claims);
return authentication;
最后你的 ResourceServerConfiguration 看起来像这样:
import hello.helper.CustomAccessTokenConverter;
import hello.helper.CustomUserTokenConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.RemoteTokenServices;
@Configuration
@EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter
@Override
public void configure(final HttpSecurity http) throws Exception
// @formatter:off
http.authorizeRequests()
.anyRequest().access("hasAnyAuthority('Admin')");
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception
resources.resourceId("arawaks");
@Bean
@Primary
public RemoteTokenServices tokenServices()
final RemoteTokenServices tokenServices = new RemoteTokenServices();
tokenServices.setClientId("resourceId");
tokenServices.setClientSecret("resource.secret");
tokenServices.setCheckTokenEndpointUrl("http://localhost:5001/connect/introspect");
tokenServices.setAccessTokenConverter(accessTokenConverter());
return tokenServices;
@Bean
public CustomAccessTokenConverter accessTokenConverter()
final CustomAccessTokenConverter converter = new CustomAccessTokenConverter();
converter.setUserTokenConverter(new CustomUserTokenConverter());
return converter;
【讨论】:
您的回答似乎是对的!我稍后会测试它,谢谢! 我正在尝试通过添加一个名为“authorities”且值 = ["ROLE_admin"] 的声明来解决它,但是 spring security 仍然返回 403,令牌是:"preferred_username":"bob" ,"authorities":"[\"ROLE_admin\"]" 您不需要将前缀 ROLE_ 添加到您的角色中,我的第一个解决方案是这样的,在 IdentityServer4 中,在 ProfileService 实现中,我添加了声明“权限”并且它无需更改spring security oauth2的默认TokenConverter,角色名称相同,不带ROLE_前缀。 当局声称似乎被忽略了,我尝试了许多格式的声称值,但没有运气。而我使用的是 spring-boot-starter-oauth2-client 包,没有 UserAuthenticationConverter 类。 我已经解决了这个问题,看看我的帖子的编辑。【参考方案2】:显然@wjsgzcn 回答(EDIT 1)
不起作用,原因如下
如果您打印Oauth2UserAuthirty class
返回的属性,您很快就会注意到JSON
数据的内容没有role
键,而是有一个authorities
键,因此您需要使用该键进行迭代在权限(角色)列表上获取实际的角色名称。
因此以下代码行将不起作用,因为oauth2UserAuthority.getAttributes();
返回的JSON
数据中没有role
键
OAuth2UserAuthority oauth2UserAuthority = (OAuth2UserAuthority)authority;
Map<String, Object> userAttributes = oauth2UserAuthority.getAttributes();
if (userAttributes.containsKey("role"))
String roleName = "ROLE_" + (String)userAttributes.get("role");
mappedAuthorities.add(new SimpleGrantedAuthority(roleName));
所以改为使用以下方法从 getAttributes 中获取实际角色
if (userAttributes.containsKey("authorities"))
ObjectMapper objectMapper = new ObjectMapper();
ArrayList<Role> authorityList =
objectMapper.convertValue(userAttributes.get("authorities"), new
TypeReference<ArrayList<Role>>() );
log.info("authList: ", authorityList);
for(Role role: authorityList)
String roleName = "ROLE_" + role.getAuthority();
log.info("role: ", roleName);
mappedAuthorities.add(new SimpleGrantedAuthority(roleName));
Role
是一个像这样的 pojo 类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Role
@JsonProperty
private String authority;
这样您将能够获得ROLE_
post 前缀,这是在成功通过授权服务器身份验证后授予用户的实际角色,并且客户端返回授予权限(角色)的LIST
。
现在完整的GrantedAuthoritesMapper
如下所示:
private GrantedAuthoritiesMapper userAuthoritiesMapper()
return (authorities) ->
Set<GrantedAuthority> mappedAuthorities = new HashSet<>();
authorities.forEach(authority ->
if (OidcUserAuthority.class.isInstance(authority))
OidcUserAuthority oidcUserAuthority = (OidcUserAuthority)authority;
OidcIdToken idToken = oidcUserAuthority.getIdToken();
OidcUserInfo userInfo = oidcUserAuthority.getUserInfo();
// Map the claims found in idToken and/or userInfo
// to one or more GrantedAuthority's and add it to mappedAuthorities
if (userInfo.containsClaim("authorities"))
ObjectMapper objectMapper = new ObjectMapper();
ArrayList<Role> authorityList = objectMapper.convertValue(userInfo.getClaimAsMap("authorities"), new TypeReference<ArrayList<Role>>() );
log.info("authList: ", authorityList);
for(Role role: authorityList)
String roleName = "ROLE_" + role.getAuthority();
log.info("role: ", roleName);
mappedAuthorities.add(new SimpleGrantedAuthority(roleName));
else if (OAuth2UserAuthority.class.isInstance(authority))
OAuth2UserAuthority oauth2UserAuthority = (OAuth2UserAuthority)authority;
Map<String, Object> userAttributes = oauth2UserAuthority.getAttributes();
log.info("userAttributes: ", userAttributes);
// Map the attributes found in userAttributes
// to one or more GrantedAuthority's and add it to mappedAuthorities
if (userAttributes.containsKey("authorities"))
ObjectMapper objectMapper = new ObjectMapper();
ArrayList<Role> authorityList = objectMapper.convertValue(userAttributes.get("authorities"), new TypeReference<ArrayList<Role>>() );
log.info("authList: ", authorityList);
for(Role role: authorityList)
String roleName = "ROLE_" + role.getAuthority();
log.info("role: ", roleName);
mappedAuthorities.add(new SimpleGrantedAuthority(roleName));
);
log.info("The user authorities: ", mappedAuthorities);
return mappedAuthorities;
;
现在您可以在oauth2Login
中使用userAuthorityMapper
,如下所示
@Override
public void configure(HttpSecurity http) throws Exception
http.antMatcher("/**").authorizeRequests()
.antMatchers("/", "/login**").permitAll()
.antMatchers("/clientPage/**").hasRole("CLIENT")
.anyRequest().authenticated()
.and()
.oauth2Login()
.userInfoEndpoint()
.userAuthoritiesMapper(userAuthoritiesMapper());
【讨论】:
以上是关于如何在 Spring Security 中从 oauth 服务器返回的用户声明中设置用户权限的主要内容,如果未能解决你的问题,请参考以下文章
Apache Oltu Spring Security OAuth2 和 Google 集成
如何在 JSP 页面中从 Java 类中设置 hashmap 的值以便在控制器中进一步使用
如何在 Javascript 中从 Spring 获取 JSON 响应