基于spring-security-4.1.0.RELEASE和spring-security-oauth2-2.3.5.RELEASE
如果Spring提供的对外访问的某个接口需要具备某种角色才可以访问时,例如需要具备普通用户(user)角色,你可以配置为如下代码,假设访问的地址为/api/testInterface
@EnableResourceServer
@Configuration
public class ResourceConfig extends ResourceServerConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/api/testInterface").hasRole("user");
}
}
但你也可能希望管理员(admin)角色能够执行普通用户可以执行的所有操作时,这就牵扯的一个层级角色的问题,也就是Spring Security中提到的Hierarchical Roles,所以这里我们这样配置:
@EnableResourceServer
@Configuration
public class ResourceConfig extends ResourceServerConfigurerAdapter {
@Bean
public RoleHierarchy roleHierarchy() {
RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
// 该>符号可以被认为是“包括”的意思admin包括user
roleHierarchy.setHierarchy("ROLE_admin > ROLE_user");
return roleHierarchy;
}
public OAuth2WebSecurityExpressionHandler oAuth2WebSecurityExpressionHandler() {
OAuth2WebSecurityExpressionHandler oAuth2WebSecurityExpressionHandler = new OAuth2WebSecurityExpressionHandler();
oAuth2WebSecurityExpressionHandler.setRoleHierarchy(roleHierarchy());
return oAuth2WebSecurityExpressionHandler;
}
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.expressionHandler(oAuth2WebSecurityExpressionHandler());
}
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/api/testInterface").hasRole("user");
}
}
为啥要设置一个expressionHandler,因为Spring Security中在ResourceServerSecurityConfigurer 中有一个默认的expressionHandler就是OAuth2WebSecurityExpressionHandler,但OAuth2WebSecurityExpressionHandler它的roleHierarchy属性并没有默认的实现,也就是为null,如果我们不手动给它设置,在进行判断认证对象是否包含指定角色时,它只会判断认证对象是否具备user,它不会获取user具备层级角色的关系在进行判断,从而导致通过admin来访问时,被拒绝访问。相关的判断的逻辑大体如下:
public abstract class SecurityExpressionRoot implements SecurityExpressionOperations {
private String defaultRolePrefix = "ROLE_";
/** 上面省略部分代码 */
public final boolean hasRole(String role) {
return hasAnyRole(role);
}
public final boolean hasAnyRole(String... roles) {
return hasAnyAuthorityName(defaultRolePrefix, roles);
}
private boolean hasAnyAuthorityName(String prefix, String... roles) {
Set<String> roleSet = getAuthoritySet();
for (String role : roles) {
String defaultedRole = getRoleWithDefaultPrefix(prefix, role);
if (roleSet.contains(defaultedRole)) {
return true;
}
}
return false;
}
/** 中间省略部分代码 */
private Set<String> getAuthoritySet() {
if (roles == null) {
roles = new HashSet<String>();
Collection<? extends GrantedAuthority> userAuthorities = authentication
.getAuthorities();
if (roleHierarchy != null) {
userAuthorities = roleHierarchy
.getReachableGrantedAuthorities(userAuthorities);
}
roles = AuthorityUtils.authorityListToSet(userAuthorities);
}
return roles;
}
/** 下面省略部分代码 */
}
可以看到在getAuthoritySet方法中有一行针对roleHierarchy判断是否为空,如果不为空会把user对应的层级关系添加到roles集合中,这时roles中存在两个角色[ROLE_admin, ROLE_user],这时在通过admin来访问时就没有问题了。