Spring Security中的Role和GrantedAuthority之间的区别

330

Spring Security中有概念和实现,比如GrantedAuthority接口来获取授权/控制访问。

我想将其用于可行的操作,例如创建子用户删除帐户,我允许一个管理员(拥有角色ROLE_ADMIN)进行操作。

看到在线教程/演示文稿时,我感到很困惑。我试图联系我所读的内容,但我认为我们可以互换对待这两个概念。

我看到hasRole使用GrantedAuthority字符串?我绝对在理解上做错了。从Spring Security的概念上讲,它们是什么?

如何将用户的角色与该角色的权限分开存储?

我还查看了org.springframework.security.core.userdetails.UserDetails接口,它用于身份验证提供程序引用的DAO,该DAO使用User(请注意最后的GrantedAuthority):

public User(String username, 
            String password, 
            boolean enabled, 
            boolean accountNonExpired,
            boolean credentialsNonExpired, 
            boolean accountNonLocked, 
            Collection<? extends GrantedAuthority> authorities)

还有其他区分这两者的方法吗?或者它不被支持,我们需要自己开发?

4个回答

487
将GrantedAuthority视为“权限”或“权利”。这些“权限”通常表示为字符串(使用getAuthority()方法)。这些字符串可让您标识权限,并让您的投票者决定是否授予对某些内容的访问权限。
您可以通过将它们放入安全上下文中来向用户授予不同的GrantedAuthority(权限)。通常,您可以通过实现自己的UserDetailsService并返回一个返回所需GrantedAuthorities的UserDetails实现来实现这一点。

在许多示例中使用的角色(Roles)只是具有命名约定的“权限”,这个命名约定表明,角色是以前缀ROLE_开头的GrantedAuthority。没有其他的东西。角色只是一个GrantedAuthority - 一种“权限” - 一种“权利”。在Spring Security中,你会看到很多地方特别处理带有ROLE_前缀的角色,比如在RoleVoter中,ROLE_前缀被用作默认值。这使你可以提供不带ROLE_前缀的角色名称。在Spring Security 4之前,“角色”的这种特殊处理并不一致,权限和角色经常被视为相同的概念(例如,在SecurityExpressionRoothasAuthority()方法的实现中,它只是调用了hasRole())。在Spring Security 4中,对角色的处理更加一致,处理“角色”的代码(如RoleVoter、hasRole表达式等)总是为你添加ROLE_前缀。因此,hasAuthority('ROLE_ADMIN')hasRole('ADMIN')意思相同,因为ROLE_前缀会自动添加。请参阅Spring Security 3到4的migration guide获取更多信息。

但仍然需要注意:角色只是具有特殊前缀“ROLE_”的权限。因此,在Spring Security 3中,@PreAuthorize("hasRole('ROLE_XYZ')")@PreAuthorize("hasAuthority('ROLE_XYZ')")相同,在Spring Security 4中,@PreAuthorize("hasRole('XYZ')")@PreAuthorize("hasAuthority('ROLE_XYZ')")相同。
关于您的用例:

用户拥有角色,角色可以执行某些操作。

您可以在GrantedAuthorities中找到用户所属角色和角色可以执行的操作。角色的GrantedAuthorities具有前缀ROLE_,而操作具有前缀OP_。操作权限的示例可以是OP_DELETE_ACCOUNTOP_CREATE_USEROP_RUN_BATCH_JOB等。角色可以是ROLE_ADMINROLE_USERROLE_OWNER等。

您可以像这个(伪代码)示例中一样让实体实现GrantedAuthority

@Entity
class Role implements GrantedAuthority {
    @Id
    private String id;

    @ManyToMany
    private final List<Operation> allowedOperations = new ArrayList<>();

    @Override
    public String getAuthority() {
        return id;
    }

    public Collection<GrantedAuthority> getAllowedOperations() {
        return allowedOperations;
    }
}

@Entity
class User {
    @Id
    private String id;

    @ManyToMany
    private final List<Role> roles = new ArrayList<>();

    public Collection<Role> getRoles() {
        return roles;
    }
}

@Entity
class Operation implements GrantedAuthority {
    @Id
    private String id;

    @Override
    public String getAuthority() {
        return id;
    }
}

您在数据库中创建的角色和操作的ID将成为GrantedAuthority表示,例如ROLE_ADMINOP_DELETE_ACCOUNT等。当用户通过身份验证时,请确保从UserDetails.getAuthorities()方法返回其所有角色和相应操作的所有GrantedAuthorities。
例如:具有ID ROLE_ADMIN的管理员角色分配了OP_DELETE_ACCOUNTOP_READ_ACCOUNTOP_RUN_BATCH_JOB等操作。具有ID ROLE_USER的用户角色分配了操作OP_READ_ACCOUNT
如果管理员登录,则生成的安全上下文将具有以下GrantedAuthorities:ROLE_ADMINOP_DELETE_ACCOUNTOP_READ_ACCOUNTOP_RUN_BATCH_JOB
如果用户登录,则生成的安全上下文将具有以下GrantedAuthorities:ROLE_USEROP_READ_ACCOUNT
UserDetailsService将负责收集所有角色及其所有操作,并通过返回的UserDetails实例中的getAuthorities()方法使它们可用。

2
谢谢!我一直在到处寻找为什么在Spring 4中“hasRole('rolename')”无法正常工作->他们的文档并不容易浏览。只需要快速进行“查找和替换”,我就回到了正轨! - Jørgen Skår Fischer
25
这是一个很好的答案。需要解释清楚的一点是,Spring Security 4 中的 hasRole('xyz') 需要在角色名前加上 ROLE_ 前缀,而 hasAuthority('xyz') 不需要前缀,并且准确地评估传入的内容。我曾经使用了这个解决方案,但在使用 hasRole('OP_MY_PERMISSION') 时遇到问题,因为需要 ROLE_ 前缀。相反,应该使用 hasAuthority('OP_MY_PERMISSION'),因为我没有前缀。 - randal4
1
在JSTL springframework.org/security/tags中,<sec:authorize access="hasRole('ADMIN')"><sec:authorize access="hasRole('ROLE_ADMIN')">是相同的。 - Alex78191
1
是的。OP_只是一个任意的前缀。ROLE_在一些Spring实现中有其特殊含义。 - James
2
@gautam:不,不,它是正确的。@PreAuthorize("hasRole('ROLE_XYZ')")实际上会检查是否有一个名为ROLE_ROLE_XYZ的权限,因为它会为您添加前缀ROLE_ - James
显示剩余8条评论

12
据我所知,Spring Security 中的 GrantedAuthority 和 roles 是相同的。GrantedAuthority 的 getAuthority() 字符串就是角色(根据默认实现的 SimpleGrantedAuthority)。
对于您的情况,您可以使用分层角色。
<bean id="roleVoter" class="org.springframework.security.access.vote.RoleHierarchyVoter">
    <constructor-arg ref="roleHierarchy" />
</bean>
<bean id="roleHierarchy"
        class="org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl">
    <property name="hierarchy">
        <value>
            ROLE_ADMIN > ROLE_createSubUsers
            ROLE_ADMIN > ROLE_deleteAccounts 
            ROLE_USER > ROLE_viewAccounts
        </value>
    </property>
</bean>

这不是你寻找的确切解决方案,但希望能对你有所帮助。

编辑:回复你的评论

在Spring Security中,“角色”类似于权限。使用intercept-url和hasRole可以非常细致地控制哪个操作允许哪个角色/权限进行。

我们应用程序处理的方式是,我们为每个操作(或rest url)定义权限(即角色),例如查看账户,删除账户,添加账户等。然后,我们为每个用户创建逻辑配置文件,例如管理员(admin)、来宾用户(guest_user)、普通用户(normal_user)。这些配置文件只是权限的逻辑分组,与Spring Security无关。 当添加新用户时,会为其分配配置文件(拥有所有可允许的权限)。现在,每当用户尝试执行某个操作时,会检查该操作的权限/角色是否与用户被授予的权限匹配。

此外,默认的RoleVoter使用前缀ROLE_,因此以ROLE_开头的任何权限都被视为角色,您可以通过在角色投票器中使用自定义RolePrefix并将其用于Spring Security来更改此默认行为。


1
谢谢你的帮助。这个层次结构看起来有些不同。我现在考虑的方法(如果必须使用Spring Security),是将角色和权限都存储在同一个列表中,并使用hasRole谓词来执行检查。思考更多——这可能是Spring Security团队故意留下的吗?除了使用hasRole对角色和权限/权利的完整列表进行检查之外,还有可能使用intercept-url或其他授权应用程序吗?此外,为什么需要前缀ROLE_?这是传统吗? - Chinmay

12
另一种理解这些概念的方法是将角色(ROLE)视为权限(Authorities)的容器。
权限是细粒度的,针对某个特定操作的权限,有时还带有特定的数据范围或上下文。例如,Read、Write、Manage 可以表示针对给定信息范围的各种权限级别。
此外,在请求处理的深层流程中强制执行权限,而在到达控制器之前通过请求过滤器来过滤角色。最佳实践建议在业务层实现权限执行的控制器之后。
另一方面,角色是一组权限的粗略表示。一个 ROLE_READER 只会拥有 Read 或 View 权限,而 ROLE_EDITOR 则既有 Read 也有 Write 权限。角色主要用于请求处理的边缘进行第一次筛选,例如 http. ... .antMatcher(...).hasRole(ROLE_MANAGER)。
权限在请求的处理流程深处得到执行,允许更细粒度地应用权限。例如,用户可能对第一级资源具有读写权限,但对子资源仅具有读取权限。拥有 ROLE_READER 将限制其编辑第一级资源的权限,因为他需要 Write 权限才能编辑此资源,但 @PreAuthorize 拦截器可以阻止他尝试编辑子资源。
杰克

5

正如其他人所提到的,我认为角色是更细粒度权限的容器。

虽然我发现分层角色实现缺乏对这些细粒度权限的精细控制。
因此我创建了一个库来管理关系,并将权限作为已授予权限注入安全上下文。

我可能在应用程序中有一组权限,例如CREATE、READ、UPDATE、DELETE,然后将其与用户的角色相关联。

或者更具体的权限,例如READ_POST、READ_PUBLISHED_POST、CREATE_POST、PUBLISH_POST

这些权限相对静态,但与角色的关系可能是动态的。

例子 -

@Autowired 
RolePermissionsRepository repository;

public void setup(){
  String roleName = "ROLE_ADMIN";
  List<String> permissions = new ArrayList<String>();
  permissions.add("CREATE");
  permissions.add("READ");
  permissions.add("UPDATE");
  permissions.add("DELETE");
  repository.save(new RolePermissions(roleName, permissions));
}

您可以创建API来管理这些权限与角色的关系。
我不想复制/粘贴另一个答案,所以这里是有关SO的更完整解释的链接。 https://dev59.com/J4zda4cB1Zd3GeqPmXDE#60251931 为了重用我的实现,我创建了一个存储库。 请自由贡献! https://github.com/savantly-net/spring-role-permissions

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接