Permissions-Based Access Control with Apache Shiro – 用Apache Shiro进行基于权限的访问控制

最后修改: 2019年 9月 10日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.介绍

In this tutorial, we’ll look at how to implement fine-grained Permissions-Based Access Control with the Apache Shiro Java security framework.

在本教程中,我们将探讨如何使用Apache ShiroJava 安全框架实现精细的基于权限的访问控制

2. Setup

2.设置

We’ll use the same setup as our introduction to Shiro — that is, we’ll only add the shiro-core module to our dependencies:

我们将使用与介绍Shiro相同的设置–也就是说,我们只将shiro-core 模块添加到我们的依赖项中。

<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-core</artifactId>
    <version>1.4.1</version>
</dependency>

Furthermore, for testing purposes, we’ll use a simple INI Realm by placing the following shiro.ini file at the root of the classpath:

此外,为了测试目的,我们将使用一个简单的INI境界,在classpath的根部放置以下shiro.ini文件。

[users]
jane.admin = password, admin
john.editor = password2, editor
zoe.author = password3, author
 
[roles]
admin = *
editor = articles:*
author = articles:create, articles:edit

Then, we’ll initialize Shiro with the above realm:

然后,我们将用上述境界初始化Shiro。

IniRealm iniRealm = new IniRealm("classpath:shiro.ini");
SecurityManager securityManager = new DefaultSecurityManager(iniRealm);
SecurityUtils.setSecurityManager(securityManager);

3. Roles and Permissions

3.角色和权限

Usually, when we talk about authentication and authorization, we center on the concepts of users and roles.

通常情况下,当我们谈论认证和授权时,我们会以用户和角色的概念为中心。

In particular, roles are cross-cutting classes of users of an application or service. So, all the users that have a specific role will have access to some resources and operations and might have restricted access to other parts of the application or service.

特别是,角色是应用程序或服务的用户的交叉类别。因此,所有拥有特定角色的用户将可以访问一些资源和操作,并可能对应用程序或服务的其他部分有限制性的访问。

The set of roles is usually designed up-front and rarely changes to accommodate new business requirements. However, roles could also be defined dynamically — for example, by an administrator.

角色集通常是预先设计好的,而且很少改变以适应新的业务需求。然而,角色也可以动态地定义–例如,由管理员定义。

With Shiro, we have several ways of testing if a user has a particular role. The most straightforward way is to use the hasRole method:

通过Shiro,我们有几种方法来测试一个用户是否拥有一个特定的角色。最直接的方法是使用hasRole方法。

Subject subject = SecurityUtils.getSubject();
if (subject.hasRole("admin")) {       
    logger.info("Welcome Admin");              
}

3.1. Permissions

3.1.许可

However, there’s a problem if we check for authorization by testing if the user has a specific role. In fact, we’re hardcoding the relationship between roles and permissions. In other words, when we want to grant or revoke access to a resource, we’ll have to change the source code. Of course, this also means rebuilding and redeploying.

然而,如果我们通过测试用户是否拥有特定的角色来检查授权,就会出现问题。事实上,我们正在硬编码角色和权限之间的关系。换句话说,当我们想要授予或撤销对资源的访问时,我们必须改变源代码。当然,这也意味着重建和重新部署。

We can do better; that’s why we’ll now introduce the concept of permissions. Permissions represent what the software can do that we can authorize or deny, and not who can do it. For example, “edit the current user’s profile”, “approve a document”, or “create a new article”.

我们可以做得更好;这就是为什么我们现在要引入权限的概念。权限代表软件可以做什么,我们可以授权或拒绝,而不是谁可以做。例如,”编辑当前用户的个人资料”、”批准一份文件 “或 “创建一篇新文章”。

Shiro makes very few assumptions about permissions. In the simplest case, permissions are plain strings:

Shiro对权限的假设非常少。在最简单的情况下,权限是纯字符串。

Subject subject = SecurityUtils.getSubject();
if (subject.isPermitted("articles:create")) {
    //Create a new article
}

Note that the use of permissions is entirely optional in Shiro.

请注意,在Shiro中,权限的使用是完全可选的。

3.2. Associating Permissions to Users

3.2.将权限与用户联系起来

Shiro has a flexible model of associating permissions with roles or individual users. However, typical realms, including the simple INI realm we’re using in this tutorial, only associate permissions to roles.

Shiro有一个灵活的模型,将权限与角色或个人用户相关联。然而,典型的境界,包括我们在本教程中使用的简单的INI境界,只将权限与角色相关联。

So, a user, identified by a Principal, has several roles, and each role has several Permissions.

因此,一个由Principal标识的用户,有几个角色,每个角色有几个Permission

For example, we can see that in our INI file, user zoe.author has the author role, and that gives them the articles:create and articles:edit permissions:

例如,我们可以看到,在我们的INI文件中,用户zoe.author拥有author角色,这让他们拥有articles:createarticles:edit权限。

[users]
zoe.author = password3, author
#Other users...

[roles]
author = articles:create, articles:edit
#Other roles...

Similarly, other realm types (such as the built-in JDBC realm) can be configured to associate permissions to roles.

同样,其他境界类型(比如内置的JDBC境界)也可以被配置为与角色相关的权限。

4. Wildcard Permissions

4.通配符权限

The default implementation of permissions in Shiro is wildcard permissions, a flexible representation for a variety of permission schemes.

Shiro中默认的权限实现是通配符权限,这是一种灵活的表示,适用于各种权限方案。

We represent wildcard permissions in Shiro with strings. A permission string is made of one or more components separated by a colon, such as:

我们在Shiro中用字符串表示通配符权限。一个权限字符串是由一个或多个组件组成的,用冒号隔开,例如::

articles:edit:1

The meaning of each part of the string depends on the application, as Shiro doesn’t enforce any rule. However, in the above example, we can quite clearly interpret the string as a hierarchy:

字符串的每一部分的含义取决于应用,因为Shiro并不强制执行任何规则。然而,在上面的例子中,我们可以很清楚地把这个字符串解释为一个层次结构。

  1. The class of resources we’re exposing (articles)
  2. An action on such a resource (edit)
  3. The id of a specific resource on which we want to allow or deny the action

This three-tiered structure of resource:action:id is a common pattern in Shiro applications, as it’s both simple and effective at representing many different scenarios.

这种资源:行动:ID的三层结构是Shiro应用程序中的一种常见模式,因为它既简单又能有效地代表许多不同的场景。

So, we could revisit our previous example to follow this scheme:

因此,我们可以重新审视我们之前的例子,遵循这个方案。

Subject subject = SecurityUtils.getSubject();
if (subject.isPermitted("articles:edit:123")) {
    //Edit article with id 123
}

Note that the number of components in a wildcard permissions string does not have to be three, even though three components is the usual case.

请注意,通配符权限字符串中的组件数量不一定是三个,尽管三个组件是通常的情况。

4.1. Permission Implication and Instance-Level Granularity

4.1.权限含义和实例级颗粒度

Wildcard permissions shine when we combine them with another feature of Shiro permissions — implication.

当我们把通配符权限与Shiro权限的另一个特点–暗示结合起来时,通配符权限就会大放异彩。

When we test for roles, we test for exact membership: either a Subject has a particular role, or it doesn’t. In other words, Shiro tests roles for equality.

当我们测试角色时,我们测试的是精确的成员资格:要么一个主体有一个特定的角色,要么没有。换句话说,Shiro测试角色的平等性。

On the other hand, when we test for permissions, we test for implication: do the Subject‘s permissions imply the one we’re testing it against?

另一方面,当我们测试权限时,我们测试的是暗示:主体的权限是否暗示了我们要测试的那个权限?

What implication means concretely depends on the implementation of the permission. In fact, for wildcard permissions, the implication is a partial string match, with the possibility of wild components, as the name suggests.

暗示的具体含义取决于权限的实现。事实上,对于通配符权限来说,暗示是一个部分字符串的匹配,有可能是通配符,正如其名称所暗示的那样。

So, let’s say we assign the following permissions to the author role:

因此,假设我们为作者角色分配了以下权限。

[roles]
author = articles:*

Then, everyone with the author role will be allowed every possible operation on articles:

然后,每个拥有作者角色的人将被允许对文章进行各种可能的操作。

Subject subject = SecurityUtils.getSubject();
if (subject.isPermitted("articles:create")) {
    //Create a new article
}

That is, the string articles:* will match against any wildcard permission whose first component is articles.

也就是说,字符串articles:*将与任何第一个成分为articles.的通配符权限匹配。

With this scheme, we can both assign very specific permissions – a certain action on a certain resource with a given id – or broad permissions, such as edit any article or perform any operation on any article.

有了这个方案,我们既可以分配非常具体的权限–在某个资源上对某个给定的id进行某种操作,也可以分配广泛的权限,如编辑任何文章或对任何文章进行任何操作。

Of course, for performance reasons, since the implication is not a simple equality comparison, we should always test against the most specific permission:

当然,出于性能方面的考虑,由于牵涉到的不是简单的平等比较,我们应该总是针对最具体的权限进行测试

if (subject.isPermitted("articles:edit:1")) { //Better than "articles:*"
    //Edit article
}

5. Custom Permissions Implementations

5.自定义权限的实施

Let’s touch briefly on permissions customizations. Even though wildcard permissions cover an extensive range of scenarios, we might want to replace them with a solution custom-made for our application.

让我们简单地谈谈权限的定制。尽管通配符权限涵盖了广泛的场景,但我们可能想用一个为我们的应用程序定制的解决方案来取代它们。

Suppose that we need to model permissions on paths so that permission on a path implies permissions on all subpaths. In reality, we could use wildcard permissions just fine for the task, but let’s ignore that.

假设我们需要对路径上的权限进行建模,使一个路径上的权限意味着所有子路径上的权限。实际上,我们可以使用通配符权限来完成这个任务,但让我们忽略这一点。

So, what do we need?

那么,我们需要什么?

  1. a Permission implementation
  2. to tell Shiro about it

Let’s see how to achieve both points.

让我们看看如何实现这两点。

5.1. Writing a Permission Implementation

5.1.编写权限实现

A Permission implementation is a class with a single method — implies:

一个权限实现是一个具有单一方法的类–暗示

public class PathPermission implements Permission {

    private final Path path;

    public PathPermission(Path path) {
        this.path = path;
    }

    @Override
    public boolean implies(Permission p) {
        if(p instanceof PathPermission) {
            return ((PathPermission) p).path.startsWith(path);
        }
        return false;
    }
}

The method returns true if this implies the other permission object, and returns false otherwise.

如果this意味着其他权限对象,该方法返回true,否则返回false

5.2. Telling Shiro About Our Implementation

5.2.向志郎介绍我们的实施情况

Then, there are various ways of integrating a Permission implementation into Shiro, but the most straightforward way is to inject a custom PermissionResolver into our Realm:

然后,有各种方法将Permission实现整合到Shiro中,但最直接的方法是将自定义的PermissionResolver注入我们的Realm中:

IniRealm realm = new IniRealm();
Ini ini = Ini.fromResourcePath(Main.class.getResource("/com/.../shiro.ini").getPath());
realm.setIni(ini);
realm.setPermissionResolver(new PathPermissionResolver());
realm.init();

SecurityManager securityManager = new DefaultSecurityManager(realm);

The PermissionResolver is responsible for converting the string representation of our permissions to actual Permission objects:

PermissionResolver负责将我们的权限的字符串表示转换为实际的Permission对象:

public class PathPermissionResolver implements PermissionResolver {
    @Override
    public Permission resolvePermission(String permissionString) {
        return new PathPermission(Paths.get(permissionString));
    }
}

We’ll have to modify our previous shiro.ini with path-based permissions:

我们将不得不修改我们之前的shiro.ini与基于路径的权限。

[roles]
admin = /
editor = /articles
author = /articles/drafts

Then, we’ll be able to check for permissions on paths:

然后,我们就可以检查路径上的权限了。

if(currentUser.isPermitted("/articles/drafts/new-article")) {
    log.info("You can access articles");
}

Note that here we’re configuring a simple realm programmatically. In a typical application, we’ll use a shiro.ini file or other means such as Spring to configure Shiro and the realm. A real-world shiro.ini file might contain:

请注意,这里我们是以编程方式配置一个简单的境界。在一个典型的应用程序中,我们将使用shiro.ini文件或其他方式(如Spring)来配置Shiro和境界。一个真实世界的shiro.ini文件可能包含。

[main]
permissionResolver = com.baeldung.shiro.permissions.custom.PathPermissionResolver
dataSource = org.apache.shiro.jndi.JndiObjectFactory
dataSource.resourceName = java://app/jdbc/myDataSource

jdbcRealm = org.apache.shiro.realm.jdbc.JdbcRealm
jdbcRealm.dataSource = $dataSource 
jdbcRealm.permissionResolver = $permissionResolver

6. Conclusion

6.结论

In this article, we’ve reviewed how Apache Shiro implements Permissions-Based Access Control.

在这篇文章中,我们已经回顾了Apache Shiro是如何实现基于权限的访问控制的。

As always, the implementations of all these examples and code snippets are available over on GitHub.

像往常一样,所有这些例子和代码片段的实现都可以在GitHub上找到