聊聊spring security的role hierarchy
序
本文就來研究一下spring security的role hierarchy
背景
預設情況下,userDetailsService建立的使用者,他們的許可權是沒有繼承關係的
@Bean
@Override
protected UserDetailsService userDetailsService(){
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(User.withUsername("demoUser1").password("123456")
.authorities("ROLE_USER","read_x").build());
manager.createUser(User.withUsername("admin").password("123456")
.authorities("ROLE_ADMIN").build());
return manager;
}
比如這兩個
@GetMapping("/admin")
@Secured("ROLE_ADMIN")
public String admin(){
return "admin";
}
@GetMapping("/user")
@Secured("ROLE_USER")
public String user(){
return "user";
}
admin登入只能訪問/admin,訪問不了/user;而user登入只能訪問/user
這通常不大符合我們的業務需求,一般admin擁有所有許可權的,也就是它應該能訪問/user。這個問題擴充套件開來就是角色許可權的繼承問題,role hierarchy
RoleHierarchy
spring-security-core-4.2.3.RELEASE-sources.jar!/org/springframework/security/access/hierarchicalroles/RoleHierarchy.java
ROLE_ADMIN > ROLE_STAFF
ROLE_STAFF > ROLE_USER
ROLE_USER > ROLE_GUEST
spring security提供了RoleHierarchy,可以讓你去定義各類角色的層級關係
config
@EnableGlobalMethodSecurity(
securedEnabled = true,
jsr250Enabled = true,
prePostEnabled = true
)
@Configuration
public class RoleConfig extends GlobalMethodSecurityConfiguration{
@Override
protected AccessDecisionManager accessDecisionManager() {
List<AccessDecisionVoter<? extends Object>> decisionVoters = new ArrayList<AccessDecisionVoter<? extends Object>>();
ExpressionBasedPreInvocationAdvice expressionAdvice = new ExpressionBasedPreInvocationAdvice();
expressionAdvice.setExpressionHandler(getExpressionHandler());
// if (prePostEnabled()) {
decisionVoters
.add(new PreInvocationAuthorizationAdviceVoter(expressionAdvice));
// }
// if (jsr250Enabled()) {
decisionVoters.add(new Jsr250Voter());
// }
// decisionVoters.add(new RoleVoter());
decisionVoters.add(roleHierarchyVoter());
decisionVoters.add(new AuthenticatedVoter());
return new AffirmativeBased(decisionVoters);
}
@Bean
public RoleHierarchyVoter roleHierarchyVoter() {
return new RoleHierarchyVoter(roleHierarchy());
}
@Bean
public RoleHierarchy roleHierarchy(){
RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
roleHierarchy.setHierarchy(
"ROLE_ADMIN > ROLE_USER\n"+
" ROLE_USER > ROLE_ANONYMOUS\n"
);
return roleHierarchy;
}
}
這裡通過重寫GlobalMethodSecurityConfiguration的accessDecisionManager方法,給decisionVoters新增roleHierarchyVoter
預設是使用RoleVoter,它不支援繼承關係,這裡替換為roleHierarchyVoter
這樣就大功告成了,admin也可以訪問user許可權的頁面/介面
RoleHierarchyVoter
spring-security-core-4.2.3.RELEASE-sources.jar!/org/springframework/security/access/vote/RoleHierarchyVoter.java
/**
* Extended RoleVoter which uses a {@link RoleHierarchy} definition to determine the roles
* allocated to the current user before voting.
*
* @author Luke Taylor
* @since 2.0.4
*/
public class RoleHierarchyVoter extends RoleVoter {
private RoleHierarchy roleHierarchy = null;
public RoleHierarchyVoter(RoleHierarchy roleHierarchy) {
Assert.notNull(roleHierarchy, "RoleHierarchy must not be null");
this.roleHierarchy = roleHierarchy;
}
/**
* Calls the <tt>RoleHierarchy</tt> to obtain the complete set of user authorities.
*/
@Override
Collection<? extends GrantedAuthority> extractAuthorities(
Authentication authentication) {
return roleHierarchy.getReachableGrantedAuthorities(authentication
.getAuthorities());
}
}
這類繼承了RoleVoter,重寫了extractAuthorities,使用roleHierarchy去獲取grantedAuthorities
繼承關係的構建
spring-security-core-4.2.3.RELEASE-sources.jar!/org/springframework/security/access/hierarchicalroles/RoleHierarchyImpl.java
/**
* Set the role hierarchy and pre-calculate for every role the set of all reachable
* roles, i.e. all roles lower in the hierarchy of every given role. Pre-calculation
* is done for performance reasons (reachable roles can then be calculated in O(1)
* time). During pre-calculation, cycles in role hierarchy are detected and will cause
* a <tt>CycleInRoleHierarchyException</tt> to be thrown.
*
* @param roleHierarchyStringRepresentation - String definition of the role hierarchy.
*/
public void setHierarchy(String roleHierarchyStringRepresentation) {
this.roleHierarchyStringRepresentation = roleHierarchyStringRepresentation;
logger.debug("setHierarchy() - The following role hierarchy was set: "
+ roleHierarchyStringRepresentation);
buildRolesReachableInOneStepMap();
buildRolesReachableInOneOrMoreStepsMap();
}
設定層級關係之後,通過buildRolesReachableInOneStepMap以及buildRolesReachableInOneOrMoreStepsMap這兩個方法去構建對映
buildRolesReachableInOneStepMap
/**
* rolesReachableInOneStepMap is a Map that under the key of a specific role name
* contains a set of all roles reachable from this role in 1 step
*/
private Map<GrantedAuthority, Set<GrantedAuthority>> rolesReachableInOneStepMap = null;
/**
* Parse input and build the map for the roles reachable in one step: the higher role
* will become a key that references a set of the reachable lower roles.
*/
private void buildRolesReachableInOneStepMap() {
Pattern pattern = Pattern.compile("(\\s*([^\\s>]+)\\s*>\\s*([^\\s>]+))");
Matcher roleHierarchyMatcher = pattern
.matcher(this.roleHierarchyStringRepresentation);
this.rolesReachableInOneStepMap = new HashMap<GrantedAuthority, Set<GrantedAuthority>>();
while (roleHierarchyMatcher.find()) {
GrantedAuthority higherRole = new SimpleGrantedAuthority(
roleHierarchyMatcher.group(2));
GrantedAuthority lowerRole = new SimpleGrantedAuthority(
roleHierarchyMatcher.group(3));
Set<GrantedAuthority> rolesReachableInOneStepSet;
if (!this.rolesReachableInOneStepMap.containsKey(higherRole)) {
rolesReachableInOneStepSet = new HashSet<GrantedAuthority>();
this.rolesReachableInOneStepMap.put(higherRole,
rolesReachableInOneStepSet);
}
else {
rolesReachableInOneStepSet = this.rolesReachableInOneStepMap
.get(higherRole);
}
addReachableRoles(rolesReachableInOneStepSet, lowerRole);
logger.debug("buildRolesReachableInOneStepMap() - From role " + higherRole
+ " one can reach role " + lowerRole + " in one step.");
}
}
private void addReachableRoles(Set<GrantedAuthority> reachableRoles,
GrantedAuthority authority) {
for (GrantedAuthority testAuthority : reachableRoles) {
String testKey = testAuthority.getAuthority();
if ((testKey != null) && (testKey.equals(authority.getAuthority()))) {
return;
}
}
reachableRoles.add(authority);
}
Map<GrantedAuthority, Set<GrantedAuthority>> rolesReachableInOneStepMap ,這裡將第一級的直連關係存到這個map當中
假設級聯關係是
A > B
B > C
C > D
D > E
D > F
那麼這個map就類似於
A --> [B]
B --> [C]
C --> [D]
D --> [E,F]
buildRolesReachableInOneOrMoreStepsMap
/**
* rolesReachableInOneOrMoreStepsMap is a Map that under the key of a specific role
* name contains a set of all roles reachable from this role in 1 or more steps
*/
private Map<GrantedAuthority, Set<GrantedAuthority>> rolesReachableInOneOrMoreStepsMap = null;
/**
* For every higher role from rolesReachableInOneStepMap store all roles that are
* reachable from it in the map of roles reachable in one or more steps. (Or throw a
* CycleInRoleHierarchyException if a cycle in the role hierarchy definition is
* detected)
*/
private void buildRolesReachableInOneOrMoreStepsMap() {
this.rolesReachableInOneOrMoreStepsMap = new HashMap<GrantedAuthority, Set<GrantedAuthority>>();
// iterate over all higher roles from rolesReachableInOneStepMap
for (GrantedAuthority role : this.rolesReachableInOneStepMap.keySet()) {
Set<GrantedAuthority> rolesToVisitSet = new HashSet<GrantedAuthority>();
if (this.rolesReachableInOneStepMap.containsKey(role)) {
rolesToVisitSet.addAll(this.rolesReachableInOneStepMap.get(role));
}
Set<GrantedAuthority> visitedRolesSet = new HashSet<GrantedAuthority>();
while (!rolesToVisitSet.isEmpty()) {
// take a role from the rolesToVisit set
GrantedAuthority aRole = rolesToVisitSet.iterator().next();
rolesToVisitSet.remove(aRole);
addReachableRoles(visitedRolesSet, aRole);
if (this.rolesReachableInOneStepMap.containsKey(aRole)) {
Set<GrantedAuthority> newReachableRoles = this.rolesReachableInOneStepMap
.get(aRole);
// definition of a cycle: you can reach the role you are starting from
if (rolesToVisitSet.contains(role)
|| visitedRolesSet.contains(role)) {
throw new CycleInRoleHierarchyException();
}
else {
// no cycle
rolesToVisitSet.addAll(newReachableRoles);
}
}
}
this.rolesReachableInOneOrMoreStepsMap.put(role, visitedRolesSet);
logger.debug("buildRolesReachableInOneOrMoreStepsMap() - From role " + role
+ " one can reach " + visitedRolesSet + " in one or more steps.");
}
}
Map<GrantedAuthority, Set<GrantedAuthority>> rolesReachableInOneOrMoreStepsMap這個將間接的層級關係拉平
這裡實際用了遞迴來完成層級的所有級聯關係對映,rolesToVisitSet不斷remove和有條件地add,遞迴終止條件是rolesToVisitSet為empty
一級的map如下
A --> [B]
B --> [C]
C --> [D]
D --> [E,F]
構造完之後如下
A --> [B,C,D,E,F]
B --> [C,D,E,F]
C --> [D,E,F]
D --> [E,F]
RoleHierarchyImpl
spring-security-core-4.2.3.RELEASE-sources.jar!/org/springframework/security/access/hierarchicalroles/RoleHierarchyImpl.java
public Collection<GrantedAuthority> getReachableGrantedAuthorities(
Collection<? extends GrantedAuthority> authorities) {
if (authorities == null || authorities.isEmpty()) {
return AuthorityUtils.NO_AUTHORITIES;
}
Set<GrantedAuthority> reachableRoles = new HashSet<GrantedAuthority>();
for (GrantedAuthority authority : authorities) {
addReachableRoles(reachableRoles, authority);
Set<GrantedAuthority> additionalReachableRoles = getRolesReachableInOneOrMoreSteps(
authority);
if (additionalReachableRoles != null) {
reachableRoles.addAll(additionalReachableRoles);
}
}
if (logger.isDebugEnabled()) {
logger.debug("getReachableGrantedAuthorities() - From the roles "
+ authorities + " one can reach " + reachableRoles
+ " in zero or more steps.");
}
List<GrantedAuthority> reachableRoleList = new ArrayList<GrantedAuthority>(
reachableRoles.size());
reachableRoleList.addAll(reachableRoles);
return reachableRoleList;
}
// SEC-863
private Set<GrantedAuthority> getRolesReachableInOneOrMoreSteps(
GrantedAuthority authority) {
if (authority.getAuthority() == null) {
return null;
}
for (GrantedAuthority testAuthority : this.rolesReachableInOneOrMoreStepsMap
.keySet()) {
String testKey = testAuthority.getAuthority();
if ((testKey != null) && (testKey.equals(authority.getAuthority()))) {
return this.rolesReachableInOneOrMoreStepsMap.get(testAuthority);
}
}
return null;
}
getReachableGrantedAuthorities方法通過之前構造好的rolesReachableInOneOrMoreStepsMap來獲取所有級聯層級關係
這樣就大功告成了
doc
相關文章
- 聊聊spring security的permitAll以及webIgnoreSpringMITWeb
- 聊聊Oracle Default RoleOracle
- spring aop The hierarchy of the type is inconsistentSpring
- 基於Spring Security Role過濾Jackson JSON輸出內容SpringJSON
- Spring SecuritySpring
- Spring Security原始碼分析八:Spring Security 退出Spring原始碼
- 二、Spring Security的使用Spring
- Spring Boot —— Spring SecuritySpring Boot
- Spring Security原始碼分析九:Spring Security Session管理Spring原始碼Session
- Spring Security 上Spring
- 初探Spring SecuritySpring
- Spring Security(二)Spring
- spring security(一)Spring
- Spring Security(6)Spring
- Spring Security(7)Spring
- Spring Security(8)Spring
- Spring Security + JWTSpringJWT
- Spring Boot SecuritySpring Boot
- Spring Security研究Spring
- Spring Security原始碼分析七:Spring Security 記住我Spring原始碼
- Spring security(四)-spring boot +spring security簡訊認證+redis整合Spring BootRedis
- 如何從Spring Security 5遷移到Spring Security 6/Spring Boot 3Spring Boot
- Spring Boot整合Spring SecuritySpring Boot
- [收藏]Spring Security中的ACLSpring
- Spring Security 中的 BCryptPasswordEncoderSpring
- 比較Spring Security6.X 和 Spring Security 5.X的不同Spring
- Spring Security原始碼分析一:Spring Security認證過程Spring原始碼
- Spring Security原始碼分析二:Spring Security授權過程Spring原始碼
- 聊聊spring的UnexpectedRollbackExceptionSpringException
- Spring Security詳解Spring
- Spring Security進階Spring
- Spring Security OAuth 2.0SpringOAuth
- 初識Spring SecuritySpring
- Spring Security 安全框架Spring框架
- Spring Security學習Spring
- Spring Security入門(3-1)Spring Security的登入頁面定製Spring
- Spring Security原始碼分析十一:Spring Security OAuth2整合JWTSpring原始碼OAuthJWT
- Spring Security原始碼分析十:初識Spring Security OAuth2Spring原始碼OAuth