专栏首页码匠的流水账聊聊spring security的role hierarchy

聊聊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

假设级联关系是

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 这里实际用了递归来完成层级的所有级联关系映射,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

  • authz-hierarchical-roles

本文分享自微信公众号 - 码匠的流水账(geek_luandun),作者:patterncat

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2017-12-22

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 聊聊rocketmq的DLedgerRoleChangeHandler

    本文主要研究一下rocketmq的DLedgerRoleChangeHandler

    codecraft
  • 聊聊rocketmq的DLedgerRoleChangeHandler

    本文主要研究一下rocketmq的DLedgerRoleChangeHandler

    codecraft
  • spring security oauth2 allowFormAuthenticationForClients原理解析

    本文主要解析一下spring security oauth2中AuthorizationServerConfigurerAdapter的allowFormAut...

    codecraft
  • 数据分析Excel技能之求和

    鼠标选中 B8单元格 -> 开始 -> 编辑 -> 自动求和,excel会自动感应要求和的行和列。

    有福
  • DeepMind盈利时间或再延后:新报告质疑其医疗数据垄断

    新智元
  • 5亿美元续命!Uber自动驾驶存亡之秋喜获丰田投资

    如今,丰田将向Uber投资5亿美元 (约合34亿人民币) ,与之合作开发自动驾驶技术。

    量子位
  • 马尔可夫链模型是什么?

    它是随机过程中的一种过程,一个统计模型,到底是哪一种过程呢?好像一两句话也说不清楚,还是先看个例子吧。

    西西木木
  • JAXB应用实例

    过往的项目中数据存储都离不开数据库,不过最近做的一个项目的某些数据(比如人员信息、菜单、权限等等)却完全没有涉及任何数据库操作,直接XML搞定。这里无意比较优...

    用户1615728
  • SharePoint 2013 Backup Farm Automatically With a Powershell and Windows Task Schedule

    In this post,I will show you SharePoint 2013 How to Backup Farm Automatically w...

    用户1161731
  • 腾讯云快速搭建微信小程序服务

    小程序后台服务需要通过 HTTPS 访问,在实验开始之前,我们要准备域名和 SSL 证书。

    云上云

扫码关注云+社区

领取腾讯云代金券