第五章:Shiro的授权(Authorization)——深入浅出学Shiro细粒度权限开发框架
Authorization概述
概述
授权,又称作为访问控制,是对资源的访问管理的过程。换句话说,控制谁有权限在应用程序中做什么。
授权检查的例子是:该用户是否被允许访问这个网页,编辑此数据,查看此按钮,或打印到这台打印机?这些都是决定哪些是用户能够访问的。
n授权的三要素
授权有着三个核心元素:权限、角色和用户 。
我们需要在应用程序中对用户和权限建立关联,通常的做法就是将权限分配给某个角色,然后将这个角色关联一个或多个用户。
权限
是Shiro安全机制最核心的元素。它在应用程序中明确声明了被允许的行为和表现。一个格式良好的权限声明可以清晰表达出用户对该资源拥有的权限。
权限声明和粒度
在shiro中主要通过前面学过的通配符表达式来完成。
角色
角色是一个命名的实体,通常代表一组行为或职责。这些行为演化为你在一个软件应用中能或者不能做的事情。角色通常是分配给用户帐户的,因此,通过分配,用户能够“做”的事情可以归属于各种角色。
Shiro支持的角色类型
1:隐式角色:一个角色代表着一系列的操作,当需要对某一操作进行授权验证时,只需判断是否是该角色即可。这种角色权限相对简单、模糊,不利于扩展。
2:显式角色:一个角色拥有一个权限的集合。授权验证时,需要判断当前角色是否拥有该权限。这种角色权限可以对该角色进行详细的权限描述,适合更复杂的权限设计。 Shiro官方推荐使用这种方式。
Shiro的三种授权方式
1:编写代码——在Java 代码中用像if 和else 块的结构执行授权检查。
2:JDK 的注解——你可以添加授权注解给你的Java 方法。
3:JSP/GSP 标签库——你可以控制基于角色和权限的JSP 或者GSP 页面输出。
编程授权
通过使用subject的方法来实现角色的判断,常见的api:
hasRole(String roleName) :返回true 如果Subject 被分配了指定的角色
hasRoles(List<String> roleNames) :返回一个与方法参数中目录一致的hasRole 结果的数组。
hasAllRoles(Collection<String> roleNames):返回true 如果Subject 被分配了所有的角色
断言支持
Shiro还支持以断言的方式进行授权验证。断言成功,不返回任何值,程序继续执行;断言失败时,将抛出异常信息。方法大致如下:
checkRole(String roleName) 、checkRoles(Collection<String>roleNames)、checkRoles(String… roleNames)
基于权限对象的实现
Permission printPermission = new PrinterPermission("laser400n", "print");
相关方法:isPermitted(Permission p)、isPermitted(List<Permission> perms)、isPermittedAll(Collection<Permission> perms)
基于字符串的实现
if (currentUser.isPermitted("printer:print:laserjet4400n"))
相关方法:isPermitted(String perm)、isPermitted(String... perms)、isPermittedAll(String... perms)
当然上述权限的实现也都可以采用断言的方式
相关方法:
checkPermission(Permission p)
checkPermission(String perm)
checkPermissions(Collection<Permission> perms)
checkPermissions(String... perms)
基于注解的授权
需要有AOP框架的支持,这里选择spring,先看看怎么集成配置,看例子:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
<context:component-scan base-package="cn.javass"></context:component-scan>
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
depends-on="lifecycleBeanPostProcessor" >
<property name="proxyTargetClass" value="true"/>
</bean>
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager" />
</bean>
<bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="staticMethod" value="org.apache.shiro.SecurityUtils.setSecurityManager"/>
<property name="arguments" ref="securityManager"/>
</bean>
<bean id="securityManager" class="org.apache.shiro.mgt.DefaultSecurityManager">
<property name="cacheManager" ref="cacheManager"/>
<property name="realm" ref="myRealm"/>
<property name="sessionManager" ref="sessionManager"/>
</bean>
<bean id="sessionManager" class="org.apache.shiro.session.mgt.DefaultSessionManager">
</bean>
<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager" />
<bean id="myRealm" class="org.apache.shiro.realm.text.IniRealm">
<property name="resourcePath" value="D:/wp/src/TestShiro.ini"></property>
</bean>
</beans>
测试用的HelloAnno
@Service
public class HelloAnno {
@Autowired
private org.apache.shiro.mgt.SecurityManager sm = null;
@RequiresAuthentication
@RequiresPermissions({"p1"})
public void t(){
System.out.println("ok=========");
}
public void login(){
UsernamePasswordToken token = new UsernamePasswordToken("javass","cc");
token.setRememberMe(true);
SecurityUtils.setSecurityManager(sm);
Subject currentUser = SecurityUtils.getSubject();
currentUser.login(token);
}
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
HelloAnno t = (HelloAnno)ctx.getBean("helloAnno");
t.login();
t.t();
}
}
Shiro提供的注解
1:@RequiresAuthentication :要求当前Subject 已经在当前的session 中被验证通过才能被注解的类/实例/方法访问或调用。
2:@RequiresGuest :要求当前的Subject 是一个“guest”,也就是他们必须是在之前的session中没有被验证或记住才能被注解的类/实例/方法访问或调用。
3:@RequiresPermissions:要求当前的Subject 被允许一个或多个权限,以便执行注解的方法,比如:@RequiresPermissions("account:create")
4:@RequiresRoles:要求当前的Subject 拥有所有指定的角色。如果他们没有,则该方法将不会被执行,而且AuthorizationException 异常将会被抛出。比如:@RequiresRoles("administrator")
5:@RequiresUser:需要当前的Subject 是一个应用程序用户才能被注解的类/实例/方法访问或调用。要么是通过验证被确认,或者在之前session 中的'RememberMe'服务被记住。
授权的顺序
Step 1:应用程序或框架代码调用任何Subject的hasRole*, checkRole*, isPermitted*,或者checkPermission*方法的变体,传递任何所需的权限或角色
Step 2:Subject的实例,通常是DelegatingSubject(或子类)代表应用程序的SecurityManager 通过调用securityManager的几乎各自相同的方法。
Step 3:SecurityManager,实现org.apache.shiro.authz.Authorizer 接口,他定义了所有Subject 具体的授权方法 。默认情况下,authorizer 实例是一个ModularRealmAuthorizer 实例,它支持协调任何授权操作过程中的一个或多个Realm 实例。
Step 4:每个配置好的Realm 被检查是否实现了相同的Authorizer接口。如果是,Realm 各自的hasRole*, checkRole*,isPermitted*,或checkPermission*方法将被调用。
理解ModularRealmAuthorizer
ModularRealmAuthorizer 将遍历其内部的Realm 集合,并按迭代顺序与每一个进行交互。每个Realm 的交互功能如下:
1:如果Realm 自己实现了Authorizer 接口,它的各个Authorizer方法将被调用。
(1)如果Realm 的方法导致异常,该异常将会以AuthorizationException 的形式传递给调用者。这将短路授权过程,任何剩余的Realm 将不会被访问
(2)如果该Realm 的方法是一个返回布尔值的hasRole*或者isPermitted*的变体,并且该返回值为true,真值将会立即被返回,同时任何剩余的Realm 都将被短路,这种行为能提高性能。
2:如果Realm 不实现Authorizer 接口,它会被忽略
了解全局的PermissionResolver
当执行基于字符串的权限检查是,大多数Shiro 的默认Realm 实现首先将该字符串转换成一个实际的Permission 实例,用的是内部默认实现的WildcardPermissionResolver
如果你想要支持自己的权限字符串语法,而且你想要所有配置的Realm 实例支持该语法,你可以将你的PermissionResolver 设置为全局的
如果你想配置一个全局的PermissionResolver,每个用来接收配置的PermissionResolver 的Realm 必须实现PermissionResolverAware 接口。这样保证了配置的实例能够被每个支持该配置的Realm 转发。
类似的,还有全局的RolePermissionResolver,但请注意:由于这种转换角色名到权限的概念非常特定于应用程序,Shiro 默认Realm 的实现并不使用它们
- Github 项目推荐 | 微软开源 MMdnn,模型可在多框架间转换
- 半自动化运维之动态添加数据文件(一) (r5笔记第55天)
- 半自动化运维之动态添加数据文件(二) (r5笔记第56天)
- 11g Active DataGuard初探(r5笔记第54天)
- Github 项目推荐 | 用于构建端对端对话系统和训练聊天机器人的开源库 —— DeepPavlov
- 我身边的一些数据库事故 (r5笔记第52天)
- 一个清理脚本的改进思路(r5笔记第51天)
- 【专业技术】Python爬虫:抓取手机APP的传输数据
- 海量数据迁移之传输表空间(一) (r5笔记第71天)
- 一条sql语句的改进探索(r5笔记第70天)
- 【专业技术】Node.js 究竟是什么?
- Github 项目推荐 | 用 Pytorch 实现的 WaveNet-Vocoder
- 重启数据库的一场闹剧(r5笔记第68天)
- 【C语言系列】基础语法案例分析(初级篇)
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 【C语言简单说】十八:二维数组
- 【C语言简单说】十九:二维数组循环嵌套(1)
- 【C语言简单说】十九:二维数组循环嵌套(2)
- 【C语言简单说】二十:指针基础
- 【C语言简单说】二十一:双重指针基础 (完结)
- 有关 php __autoload 自动加载类函数的用法
- sql 子查询(mysql)
- php 使用PDO,防止sql注入 简单说明
- js (javascript) 中获取年月日信息
- js(javascript)取得当前时间小时,分钟,秒 以及毫秒
- js(javascript) onclick与ondblclick 单击与双击事件
- unity5.x C# 获取屏幕宽度 设置不受重力影响
- unity5.x Translate平移移动 以及GetComponent获取组件
- php 计时器microtime 以及去掉数组重复值array_unique
- php 字符串打散为数组,用逗号分隔出数组