Shiro User Manual-Authorization

1. Authorization
[img]http://dl.iteye.com/upload/attachment/0082/8977/8f0925d6-b045-38fc-8f88-c39c1d495396.png[/img]
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?
}

基于角色检查的方法:
[table]
|Subject Method | Description
|hasRoles(List<String> roleNames) | 查看Subject具有哪些指定角色并返回。
|hasAllRoles(Collection<String> roleNames) | 如果Subject具有所有指定的角色,返回true,否则返回false。
|hasRole(String roleName) | 如果Subject具有指定的角色,返回true,否则返回false
[/table]
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异常。以下是几个和断言角色相关的方法:
[table]
|Subject Method |Description
|checkRoles(String... roleNames) | 和下面方法功能一样,只是参数类型不同。
|checkRoles(Collection<String> roleNames) | 如果Subject具有所有指定角色,流程继续,否则抛出AuthorizationException异常。
|checkRole(String roleName) | 如果Subject具有指定角色,流程继续,否则抛出AuthorizationException异常。
[/table]

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. 可反映出系统资源被正确的利用。
以下是几个和对象权限检查相关的方法:
[table]
|Subject Method | Description
|isPermitted(Permission p) | 如果Subject具有指定权限,返回true,否则返回false。
|isPermitted(List<Permission> perms) | 查看Subject具有哪些指定权限并返回。
|isPermittedAll(Collection<Permission> perms) | 如果Subject具有所有指定的权限,返回true,否则返回false。
[/table]
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?
}

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

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

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内部关于授权的细节。我们用之前的架构图来说明,左边高亮部分即是和授权相关的。数字表示授权的次序。
[img]http://dl.iteye.com/upload/attachment/0082/9235/9e97f371-6341-34e8-95a7-a7123ea6af29.png[/img]
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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值