【问题标题】:Spring security, how to restrict user access certain resources based on dynamic roles?Spring security,如何根据动态角色限制用户访问某些资源?
【发布时间】:2017-03-01 16:15:03
【问题描述】:

给定一个场景,控制器中有一个HTML内容一些方法,它只允许“a”角色访问。

从上面,我们通过使用 @hasRole("a")

来实现

但是,就我而言,角色是动态的:

例如,管理员添加一个新角色“b”,并且能够访问这些内容。 那么该怎么做呢?

  1. 我尝试了 ACL,但这只是保护具有 id 的域对象。

  2. 有一个名为 hasAuthority 的注解,但我无法搜索 来自互联网的任何东西。

  3. 有一个 ObjectIdentityImpl,不是真的 如何实现。

【问题讨论】:

    标签: spring spring-mvc spring-security


    【解决方案1】:

    编辑:我的解决方案

    1. 经过研究,ACL 更多的是在对象的安全列表上。 示例:您想保护员工表,某些员工记录(如 CEO、经理)只能由更高管理层访问。其余的员工记录可供所有人查看。这就是 ACL 要做的事情。

      但是,当我们需要保护一些方法、控制器、url、静态内容...时,ACL 不适合这种情况。我们需要使用 hasAuthority 或 hasPermission 或 hasRole 或 ......

    2. 在一些网络系统中,只有很少的角色,管理员和用户。对于这种情况, hasAuthority 或 hasRole 就足够了。您只需为您想要保护的资源添加 @hasRole('admin') 注释。

      但是,在某些系统中,存在动态角色,例如:admin 创建一个新角色“temporary_user”,但控制器或方法由@hasRole('user'),“temporary_user”无法访问。

      在这种情况下,根据我的理解,有几种方法可以做。

    3. 根据您想要保护的资源数量创建多个角色。例如:将“role_getRecord”分配给getRecords(),将“role_writeRecord”分配给writeRecord()。这是一种无需更改 Spring 安全机制的方法,但会在您的数据库表上拥有很多角色,并且更复杂的系统会有更多角色。

    4. @hasPermission - 这是我现在使用的。我创建了一个 CustomGrantedAuthority,以便更灵活地实现。我确实有一个 CustomUserDetailsS​​erviceCustomSpringSecurityUser,当用户登录时将创建 CustomSpringSecurityUser 并收集 CustomGrantedAuthority 然后返回 CustomSpringSecurityUser 到 CustomUserDetailsS​​ervice。而且我确实有一个 CustomPermission 来验证权限。

      投票支持,如果您的想法有用,如果我错了或有更好的方法,请发表评论。

    这是我的代码

    CustomSpringSecurityUser

    public class CustomSpringSecurityUser implements UserDetails, CredentialsContainer {
        private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
    
        private String password;
        private final String username;
        private final Set<GrantedAuthority> authorities;
        private final boolean accountNonExpired;
        private final boolean accountNonLocked;
        private final boolean credentialsNonExpired;
        private final boolean enabled;
    
        public CustomSpringSecurityUser(String username, String password, Collection<? extends GrantedAuthority> authorities) {
            this(username, password, true, true, true, true, authorities);
        }
        public CustomSpringSecurityUser(String username, String password, boolean enabled, boolean accountNonExpired,
                boolean credentialsNonExpired, boolean accountNonLocked, Collection<? extends GrantedAuthority> authorities) {
    
            if (((username == null) || "".equals(username)) || (password == null)) {
                throw new IllegalArgumentException("Cannot pass null or empty values to constructor");
            }
    
            this.username = username;
            this.password = password;
            this.enabled = enabled;
            this.accountNonExpired = accountNonExpired;
            this.credentialsNonExpired = credentialsNonExpired;
            this.accountNonLocked = accountNonLocked;
          //  this.authorities = Collections.unmodifiableSet(sortAuthorities(authorities));
            this.authorities = new HashSet<GrantedAuthority>(authorities);
        }
        public Collection<GrantedAuthority> getAuthorities() {
            return authorities;
        }
    
        public String getPassword() {
            return password;
        }
    
        public String getUsername() {
            return username;
        }
    
        public boolean isEnabled() {
            return enabled;
        }
    
        public boolean isAccountNonExpired() {
            return accountNonExpired;
        }
    
        public boolean isAccountNonLocked() {
            return accountNonLocked;
        }
    
        public boolean isCredentialsNonExpired() {
            return credentialsNonExpired;
        }
    
        public void eraseCredentials() {
            password = null;
        }
    
        private static SortedSet<GrantedAuthority> sortAuthorities(Collection<? extends GrantedAuthority> authorities) {
            Assert.notNull(authorities, "Cannot pass a null GrantedAuthority collection");
            SortedSet<GrantedAuthority> sortedAuthorities =
                new TreeSet<GrantedAuthority>(new AuthorityComparator());
    
            for (GrantedAuthority grantedAuthority : authorities) {
                Assert.notNull(grantedAuthority, "GrantedAuthority list cannot contain any null elements");
                sortedAuthorities.add(grantedAuthority);
            }
    
            return sortedAuthorities;
        }
    
        private static class AuthorityComparator implements Comparator<GrantedAuthority>, Serializable {
            private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
    
            public int compare(GrantedAuthority g1, GrantedAuthority g2) {
                if (g2.getAuthority() == null) {
                    return -1;
                }
    
                if (g1.getAuthority() == null) {
                    return 1;
                }
    
                return g1.getAuthority().compareTo(g2.getAuthority());
            }
        }
    
        @Override
        public boolean equals(Object rhs) {
            if (rhs instanceof CustomSpringSecurityUser) {
                return username.equals(((CustomSpringSecurityUser) rhs).username);
            }
            return false;
        }
    
        @Override
        public int hashCode() {
            return username.hashCode();
        }
    
        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder();
            sb.append(super.toString()).append(": ");
            sb.append("Username: ").append(this.username).append("; ");
            sb.append("Password: [PROTECTED]; ");
            sb.append("Enabled: ").append(this.enabled).append("; ");
            sb.append("AccountNonExpired: ").append(this.accountNonExpired).append("; ");
            sb.append("credentialsNonExpired: ").append(this.credentialsNonExpired).append("; ");
            sb.append("AccountNonLocked: ").append(this.accountNonLocked).append("; ");
    
            if (!authorities.isEmpty()) {
                sb.append("Granted Authorities: ");
    
                boolean first = true;
                for (GrantedAuthority auth : authorities) {
                    if (!first) {
                        sb.append(",");
                    }
                    first = false;
    
                    sb.append(auth);
                }
            } else {
                sb.append("Not granted any authorities");
            }
    
            return sb.toString();
        }
    }
    

    CustomGrantedAuthority

    public class CustomGrantedAuthority implements GrantedAuthority{
        private String role;
        private String permission,action;
    
        public String getPermission() {
            return permission;
        }
    
        public void setPermission(String permission) {
            this.permission = permission;
        }
    
        public String getAction() {
            return action;
        }
    
        public void setAction(String action) {
            this.action = action;
        }
    
        public String getRole() {
            return role;
        }
    
        public void setRole(String role) {
            this.role = role;
        }
    
        @Override
        public String getAuthority() {
            return role;
        }
    
    }
    

    CustomeUserDetailsS​​ervice

    @Service
    @Transactional(readOnly = true)
    public class CustomUserDetailsService implements UserDetailsService {
    
        @Autowired
            private OcUserService userService;
            private static final Logger logger = LoggerFactory.getLogger(CustomUserDetailsService.class);
    
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            try {
                sg.com.xx.xx.table.OcUser u = userService.findByLoginname(username);
    
                            String pass = sg.com.xx.xx.table.OcUser.byteToHex(u.getPassword());
                Collection<? extends GrantedAuthority> permissionList = userService.getPermissionByUserId(u.getId());
    
                boolean enabled = true;
                boolean accountNonExpired = true;
                boolean credentialsNonExpired = true;
                boolean accountNonLocked = true;
                            CustomSpringSecurityUser user = new CustomSpringSecurityUser(u.getLoginname(), 
                        pass,
                        enabled,
                        accountNonExpired,
                        credentialsNonExpired,
                        accountNonLocked,
                        permissionList);
    
                return user;
    
            } catch (Exception e) {
                logger.error("==============================================");
                            logger.error(e.toString());
                            return null;
            }
        }
    }
    

    自定义权限

    public class CustomPermission implements PermissionEvaluator {
    
        @Override
        public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) {
            Collection<? extends GrantedAuthority> x = authentication.getAuthorities();
    
            for(Object o : x)
            {
                CustomGrantedAuthority y = (CustomGrantedAuthority) o ;
                if(y.getPermission().equals(targetDomainObject) )
                    if( y.getAction().equals(permission) )
                        return true;
            }
            return false;
        }
    
        @Override
        public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
            int a = 5;
            return true;
        }
    
    }
    

    【讨论】:

    • 如果您处理权限,那么最好创建一个自定义的AuthenticationProvider 而不是UserDetailsService,并使代码更简洁。您覆盖的 AuthProvider 身份验证方法已经有一个身份验证对象,它让您返回分配权限的UsernamePasswordAuthenticationToken。安全是一个非常敏感的话题,很难弄清楚什么是最好的方法,所以如果可能的话,永远不要自己做。我发现 Stormpath 有趣的 api 也许你可以看看:stormpath.com
    • 我做 CustomUserDetailsS​​ervice,因为我使用 Jasig CAS(单点登录),所以我的 xml 将是 。对于 Stormpath,我认为它不太适合我们公司的产品。感谢您的建议:)
    • 正是我想实现同样的事情,请你帮我做同样的事情。
    • 非常好...但是如何动态地将 url 传递给权限?
    【解决方案2】:

    我不知道你在资源下是什么意思,但我发现在 spring 中使用它的最好方法是授予用户权限(权限)而不是角色,你仍然有角色,但他们只是在那里捆绑权限。设置完成后,您可以为视图和方法分配实际权限。我在这里找到了一个数据模型:

    http://springinpractice.com/2010/10/27/quick-tip-spring-security-role-based-authorization-and-permissions/

    【讨论】:

    • 嗨 Vaelyr,抱歉这么久,然后回复。因为我花了几天时间学习 ACL 和 Spring EL,并且明白 ACL 不是我想要的,我认为你的参考对我来说是一个很好的答案。我会尝试实现。如果我成功将更新。谢谢你的回答!
    【解决方案3】:

    如果您使用 Java 反射来获取每个控制器方法,然后将这些方法中的任何一个分配给角色关系以构建“动态角色”,该怎么办?这样,您可以随时向任何角色添加或删除任何操作。也许这种方式不需要 Spring Security。

    【讨论】:

      猜你喜欢
      • 2012-12-25
      • 2020-12-19
      • 2014-04-11
      • 1970-01-01
      • 2023-04-08
      • 2012-12-27
      • 2018-11-28
      • 2018-10-03
      • 1970-01-01
      相关资源
      最近更新 更多