Shiro User Manual-Authorization

1. Authorization

Authorization, 授权,或访问控制,是对资源访问的管理。换句话说,控制用户在应用中访问资源的权限。
授权的例子:当前用户是否可以查看当前网页,编辑数据,查看这个按钮,或者使用打印机。这些操作都取决用户是否具有相应的权限。

2. Elements of Authorization
在Shiro中,对于授权,有三个核心元素:permissions,roles,users。

2.1 Permissions
权限在Shiro中代表着最原子级的安全策略,他们明确的描述了用户可以在应用中执行的操作。

2.2 Permission Granularity
Roles:
  角色是一系列行为,动作或职责的命名实体。Shiro支持两种类型的角色:
  1. Implicit Roles(隐式)
   只使用角色名隐晦的定义一系列操作。
  2. Excplict Roles(显示)
  通常是一些命名权限语句的集合。由于明确的指定用户具有哪些操作,就不需要指定特殊的角色。

2.3 Users
user在应用中,就表示他是谁。正如我们之前所说,Subject就是Shiro的user。如果Users关联了权限和角色,就允许其执行相应的操作。

3. Authorizing Subjects
在Shiro中可以通过3种方式执行授权。
a. 编程式: 可以在java代码中利用if,else语句块进行授权检查。
b. JDK注解: 可以在java方法上使用授权注解
c. JSP/GSP tag库: 可以使用标签在JSP或GSP进行权限和角色的控制。

3.1 Programmatic Authorization
最简单的授权方式就是通过编码和Subject进行交互。

3.2 Role-Based Authorization
如果想通过角色名进行权限控制,就需要对角色进行检查:
a. Role Checks
如果想检查当前的Subject是否具有某一角色,可以使用subject的hasRole*方法。
Subject currentUser = SecurityUtils.getSubject();

if (currentUser.hasRole("administrator")) {
    //show the admin button
} else {
    //don't show the button?  Grey it out?
}

基于角色检查的方法:
Subject Method Description
hasRoles(List<String> roleNames)  查看Subject具有哪些指定角色并返回。
hasAllRoles(Collection<String> roleNames) 如果Subject具有所有指定的角色,返回true,否则返回false。
hasRole(String roleName) 如果Subject具有指定的角色,返回true,否则返回false

b. Role Assertions
还有一种方式就是,在执行逻辑前,使用断言查看subject是否具有某一角色。如果Subject没有指定的角色,会抛出AuthorizationException异常, 如果有,流程继续,不会有任何异常。
Subject currentUser = SecurityUtils.getSubject();

//guarantee that the current user is a bank teller and
//therefore allowed to open the account:
currentUser.checkRole("bankTeller");
openBankAccount();

相比于hasRole*方法,断言更简洁,且在角色不满足时,不需要你构建AuthorizationExceptions异常。以下是几个和断言角色相关的方法:
Subject Method Description
checkRoles(String... roleNames) 和下面方法功能一样,只是参数类型不同。
checkRoles(Collection<String> roleNames) 如果Subject具有所有指定角色,流程继续,否则抛出AuthorizationException异常。
checkRole(String roleName) 如果Subject具有指定角色,流程继续,否则抛出AuthorizationException异常。


3.3 Permission-Based Authorization
如我们在角色那节所说,控制权限访问最好的方式就是基于权限。由于基于权限的授权和你的应用功能相关,当功能改变时,权限授权的代码就需要改变,这相比于角色授权,代码的更改不是那么频繁了。
a. Permission Checks
如果想检查Subject是否具有执行某项操作的权限,可以使用subject的isPermitted方法。它有两种方式-基于对象的权限检查和基于字符串的权限检查。
  1.Object-based Permission Checks
一种执行权限检查的方式就是实现org.apache.shiro.authz.Permission接口并将其传入isPermitted方法以执行检查。假设有如下情形,办公室有一台打印机,且只有唯一一个账户
laserjet4400n。我们需要检查当前的用户是否有权限进行打印,代码如下:
Permission printPermission = new PrinterPermission("laserjet4400n", "print");

Subject currentUser = SecurityUtils.getSubject();

if (currentUser.isPermitted(printPermission)) {
    //show the Print button
} else {
    //don't show the button?  Grey it out?
}

基于对象的权限非常有用:
  1. 保证了编译时类型安全。
  2. 保证了权限被正确使用。
  3. 显示的控制权限的执行。
  4. 可反映出系统资源被正确的利用。
以下是几个和对象权限检查相关的方法:
Subject Method Description
isPermitted(Permission p)  如果Subject具有指定权限,返回true,否则返回false。
isPermitted(List<Permission> perms) 查看Subject具有哪些指定权限并返回。
isPermittedAll(Collection<Permission> perms) 如果Subject具有所有指定的权限,返回true,否则返回false。

  2. String-based permission checks
  虽然基于对象权限的检查很有用,但有时,却很笨拙。还有另一种方式,即基于字符串形式的权限。还是打印机的情形,我们利用字符串形式进行权限检查:
Subject currentUser = SecurityUtils.getSubject();

if (currentUser.isPermitted("printer:print:laserjet4400n")) {
    //show the Print button
} else {
    //don't show the button?  Grey it out?
}

代码依旧是实例级别的权限检查,但是现在权限的那部分已经由字符串来替代了--printer (资源类型), print (动作),和laserjet4400n (实例id) 。org.apache.shiro.authz.permission.WildcardPermission负责解析用分号":"配置的字符串权限。
以下为上面代码的原型:
Subject currentUser = SecurityUtils.getSubject();

Permission p = new WildcardPermission("printer:print:laserjet4400n");

if (currentUser.isPermitted(p) {
    //show the Print button
} else {
    //don't show the button?  Grey it out?
}

基于字符串形式的权限配置不强制你实现特殊的接口,且易读。但是没有了类型安全检查。如果需要更复杂的权限且超出了字符串的配置形式,那么你还得实现权限接口。
实践中,很多用户使用字符串形式的配置。以下是字符串式配置的权限检查方法:
Subject Method Description
isPermitted(String... perms) 查看Subject具有哪些指定的权限,并返回
isPermittedAll(String... perms)  如果Subject具有所有指定权限,返回true,否则返回false。
isPermitted(String perm)  如果Subject具有指定权限,返回true,否则返回false。

b. Permission Assertions
和角色断言一样,权限也有断言。
Subject currentUser = SecurityUtils.getSubject();

//guarantee that the current user is permitted
//to open a bank account:
Permission p = new AccountPermission("open");
currentUser.checkPermission(p);
openBankAccount();

字符串形式的代码:
Subject currentUser = SecurityUtils.getSubject();

//guarantee that the current user is permitted
//to open a bank account:
currentUser.checkPermission("account:open");
openBankAccount();

以下是几个关于面向对象的权限断言方法:
Subject Method Description
checkPermission(String perm) 如果Subject具有指定权限,流程继续,否则抛出AuthorizationException异常。
checkPermissions(Collection<Permission> perms)  如果Subject具有所有指定权限,流程继续,否则抛出AuthorizationException异常。
checkPermission(Permission p)   如果Subject具有指定权限,返回true,否则抛出AuthorizationException异常。
checkPermissions(String... perms)  和checkPermissions功能一样,只是参数类型不同。


4. Annotation-based Authorization
除了API的调用,Shiro提供了注解式的授权。

4.1 Configuration
注解式的授权,需要AOP的支持。

4.2 The RequiresAuthentication annotation
RequiresAuthentication注解要求当前Subject被认证过才可以对注解的类、实例或方法进行调用。
@RequiresAuthentication
public void updateAccount(Account userAccount) {
    //this method will only be invoked by a 
    //Subject that is guaranteed authenticated
    ...
}

这段代码等同于:
public void updateAccount(Account userAccount) {
    if (!SecurityUtils.getSubject().isAuthenticated()) {
        throw new AuthorizationException(...);
    }
    
    //Subject is guaranteed authenticated here
    ...
}


4.3 The RequiresGuest annotation
RequiresGuest注解要求当前的Subject是一个游客身份,即它没有被认证或没有被上次会话记忆过。
@RequiresGuest
public void signUp(User newUser) {
    //this method will only be invoked by a 
    //Subject that is unknown/anonymous
    ...
}

这段代码等同于:
public void signUp(User newUser) {
    Subject currentUser = SecurityUtils.getSubject();
    PrincipalCollection principals = currentUser.getPrincipals();
    if (principals != null && !principals.isEmpty()) {
        //known identity - not a guest:
        throw new AuthorizationException(...);
    }
    
    //Subject is guaranteed to be a 'guest' here
    ...
}


4.4 The RequiresPermissions annotation
RequiresPermissions注解要求当前的Subject具有一个或多个指定权限。
@RequiresPermissions("account:create")
public void createAccount(Account account) {
    //this method will only be invoked by a Subject
    //that is permitted to create an account
    ...
}

这段代码等同于:
public void createAccount(Account account) {
    Subject currentUser = SecurityUtils.getSubject();
    if (!subject.isPermitted("account:create")) {
        throw new AuthorizationException(...);
    }
    
    //Subject is guaranteed to be permitted here
    ...
}


4.5 The RequiresRoles permission
RequiresRoles注解要求当前的Subject具有所有指定的角色。如果不具有,方法不会被执行且会抛出AuthorizationException异常。
@RequiresRoles("administrator")
public void deleteUser(User user) {
    //this method will only be invoked by an administrator
    ...
}

这段代码等同于:
public void deleteUser(User user) {
    Subject currentUser = SecurityUtils.getSubject();
    if (!subject.hasRole("administrator")) {
        throw new AuthorizationException(...);
    }
    
    //Subject is guaranteed to be an 'administrator' here
    ...
}


4.6 The RequiresUser annotation
RequiresUser注解要求当前的Subject为一个应用的用户。所谓“应用的用户”,指被当前会话认证过,或被上次会话记忆过(即来自Remember Me服务)。
@RequiresUser
public void updateAccount(Account account) {
    //this method will only be invoked by a 'user'
    //i.e. a Subject with a known identity
    ...
}

这段代码等同于:
public void updateAccount(Account account) {
    Subject currentUser = SecurityUtils.getSubject();
    PrincipalCollection principals = currentUser.getPrincipals();
    if (principals == null || principals.isEmpty()) {
        //no identity - they're anonymous, not allowed:
        throw new AuthorizationException(...);
    }
    
    //Subject is guaranteed to have a known identity here
    ...
}


5. JSP TagLib Authorization
基于Subject的状态,Shiro提供了标签库供在JSP/GSP中使用。

6. Authorization Sequence
我们了解了对于当前Subject的授权,让我们再来了解一下Shiro内部关于授权的细节。我们用之前的架构图来说明,左边高亮部分即是和授权相关的。数字表示授权的次序。

step1:系统调用Subject的hasRole*, checkRole*, isPermitted*,checkPermission*任何一个方法,传入需要的权限或角色。
step2:DelegatingSubject(Subject实现类)会委托给SecurityManager,并调用SecurityManager的hasRole*, checkRole*, isPermitted*,checkPermission*方法。
step3:SecurityManager会委托给其内部的org.apache.shiro.authz.Authorizer,调用authorizer的hasRole*, checkRole*, isPermitted*, checkPermission*方法。authorizer接口默认实现为ModularRealmAuthorizer,ModularRealmAuthorizer同时支持单一Realm的授权操作和协调多个Realm的授权调用。
step4:配置的每个Realm都要检查其是否实现了Authorizer接口。如果实现了,就调用hasRole*, checkRole*, isPermitted*,checkPermission*方法。

6.1 ModularRealmAuthorizer
SecurityManager默认实现中使用ModularRealmAuthorizer。对于任何的授权操作,ModularRealmAuthorizer都要迭代其内部的Realm集合,和他们进行交互:
A. 如果当前交互的Realm实现了Authorizer接口,会调用Authorizer的hasRole*, checkRole*, isPermitted*, checkPermission*方法。
  1. 如果Realm返回异常,异常被封装为AuthorizationException并传给Subject。并不再和其他Reaml交互。
  2. 如果Realm的hasRole*或isPermitted*方法返回true,程序立刻返回,并短路其他Reealm。这是为了增加性能才如此设计的。
B. 如果当前交互的Realm没有实现Authorizer接口,将忽略这个Realm。

6.2 Realm Authorization Order
和认证一样,ModularRealmAuthorizer与Realm也是迭代顺序。

6.3 Configuring a global PermissionResolver
当使用字符串形式的权限检查,在执行逻辑前,Realm将字符串转换为对应的Permission接口。这是因为,字符串式权限验证并非简单的相等比较。
Shiro提供了PermissionResolver概念来支持转换操作,默认使用WildcardPermissionResolver解析基于字符串式权限配置。
如果想自定义全局的PermissionResolver,需要实现PermissionResolverAware接口,然后进行如下配置:
globalPermissionResolver = com.foo.bar.authz.MyPermissionResolver
...
securityManager.authorizer.permissionResolver = $globalPermissionResolver
...

如果不想配置全局的PermissionResolver,可以显式的为每个Realm配置PermissionResolver。
permissionResolver = com.foo.bar.authz.MyPermissionResolver

realm = com.foo.bar.realm.MyCustomRealm
realm.permissionResolver = $permissionResolver
...


6.4 Configuring a global RolePermissionResolver
和PermissionResolver相似,RolePermissionResolver执行权限检查。区别是,配置的字符串是角色名,而非权限名。
如果想自定义全局RolePermissionResolver,自定义类需要实现RolePermissionResolverAware接口, 然后进行如下配置:
globalRolePermissionResolver = com.foo.bar.authz.MyPermissionResolver
...
securityManager.authorizer.rolePermissionResolver = $globalRolePermissionResolver
...

如果不想配置全局的RolePermissionResolver, 可以为每个Realm显示配置一个RolePermissionResolver:
rolePermissionResolver = com.foo.bar.authz.MyRolePermissionResolver

realm = com.foo.bar.realm.MyCustomRealm
realm.rolePermissionResolver = $rolePermissionResolver
...


7. Custom Authorizer
也可以自定义Authorizer:
authorizer = com.foo.bar.authz.CustomAuthorizer

securityManager.authorizer = $authorizer

猜你喜欢

转载自technoboy.iteye.com/blog/1844774