这是我参与8月更文挑战的第6天,活动详情查看:8月更文挑战
四、授权
4.1、授权概述
授权,即访问控制,控制谁能访问哪些资源。主体进行身份认证后需要分配权限方可访问系统的资源,对于某些资源没有权限是无法访问的。
系统中的授权功能就是为用户分配相关的权限,只有当用户拥有相应的权限后,才能访问对应的资源。
如果系统中无法管理用户的权限,那么将会出现客户信息泄露,数据被恶意篡改等问题,所以在绝大多数的应用中,我们都会有权限管理模块。一般基于角色的权限控制管理有以下三个子模块:
- 用户管理
- 角色管理
- 权限管理
4.2、 关键对象
**授权可简单理解为who对what(which)进行How操作:**
`Who,即主体(Subject)`,主体需要访问系统中的资源。
`What,即资源(Resource)`,如系统菜单、页面、按钮、类方法、系统商品信息等。资源包括`资源类型`和`资源实例`,比如`商品信息为资源类型`,类型为t01的商品为`资源实例`,编号为001的商品信息也属于资源实例。
`How,权限/许可(Permission)`,规定了主体对资源的操作许可,权限离开资源没有意义,如用户查询权限、用户添加权限、某个类方法的调用权限、编号为001用户的修改权限等,通过权限可知主体对哪些资源都有哪些操作许可。
4.3、授权流程
4.4、授权方式
4.4.1、基于角色的访问控制
RBAC基于角色的访问控制(Role-Based Access Control)是以角色为中心进行访问控制
1 | java复制代码if(subject.hasRole("admin")){//主体具有admin角色 |
4.4.2、基于资源的访问控制
RBAC基于资源的访问控制(Resource-Based Access Control)是以资源为中心进行访问控制
1 | java复制代码if(subject.isPermission("user:find:*")){ //对用户模块的所有用户有查询权限 |
4.5、权限字符串
权限字符串的规则是:资源标识符:操作:资源实例标识符,意思是对哪个资源的哪个实例具有什么操作,“:”是资源/操作/实例的分割符,权限字符串也可以使用*通配符。
例子:
- 用户创建权限:user:create,或user:create:*
- 用户修改实例001的权限:user:update:001
- 用户实例001的所有权限:user:*:001
4.6、Shiro授权的实现方式
4.6.1、编程式
1 | java复制代码Subject subject = SecurityUtils.getSubject(); |
4.6.2、注解式
1 | java复制代码@RequiresRoles("admin") |
4.6.3、标签式
1 | jsp复制代码JSP标签:在JSP 页面通过相应的标签完成: |
4.7、基于ini的授权
4.7.1、编写ini文件
1 | ini复制代码#用户的身份、凭据、角色 |
4.7.2、编写测试类
1 | java复制代码@Test |
4.7.3、自定义Realm
1 | java复制代码public class EmployeeRealm extends AuthorizingRealm { |
4.8、SSM整合Shiro认证
在开发中,我们一般使用注解来完成授权操作, 我们需要使用 Shiro 自身提供的一套注解来完成。
4.8.1、贴注解
在 Controller 的方法上贴上 Shiro 提供的权限注解(@RequiresPermissions,@RequiresRoles)
1 | java复制代码 // 说明需要有这个权限才可以访问这个方法 |
4.8.2、配置注解扫描
当扫描到 Controller 中有使用 @RequiresPermissions 注解时,会使用cglib动态代理为当前 Controller 生成代理对象,增强对应方法,进行权限校验
1 | xml复制代码<!-- <aop:config/> 会扫描配置文件中的所有advisor,并为其创建代理 --> |
4.8.3、修改自定义Realm
1 | java复制代码// 授权 |
4.8.4、配置自定义异常
1 | java复制代码 @ExceptionHandler(AuthorizationException.class) |
4.8.5、Shiro标签集成FreeMarker
在前端页面上,我们通常可以根据用户拥有的权限来显示具体的页面,如:用户拥有删除员工的权限,页面上就把删除按钮显示出来,否则就不显示删除按钮,通过这种方式来细化权限控制。我们需要使用Shiro标签来进行控制。要能够实现上面的控制,需要使用 Shiro 中提供的相关标签。
4.8.5.1、拓展FreeMarker标签
前端页面我们选择的是freemarker,**而默认 freemarker 是不支持 shiro 标签的**,所以需要对其功能做拓展,可以理解为注册 shiro 的标签,达到在freemarker 页面中使用的目的。
1 | java复制代码public class ShiroFreeMarkerConfig extends FreeMarkerConfigurer { |
4.8.5.2、修改mvc.xml中的配置
在mvc.xml 中把以前的FreeMarkerConfigurer修改成我们自定义的MyFreeMarkerConfig类
1 | xml复制代码 <!-- 注册 FreeMarker 配置类 --> |
4.8.5.3、常用标签
4.8.5.3.1、authenticated标签
authenticated标签里面囊括的表示的是已经通过了认证了的用户才会显示的前端界面
1 | html复制代码<@shiro.authenticated> </@shiro.authenticated> |
4.8.5.3.2、notAuthenticated标签
与authenticated标签相对立,表示为认证通过的用户。
1 | html复制代码<@shiro.notAuthenticated></@shiro.notAuthenticated> |
4.8.5.3.3、principal 标签
principal 标签表示的是输出当前用户的信息。,通常可以用来输出登录用户的用户名。
1 | html复制代码<@shiro.principal property="name" /> |
4.8.5.3.4、hasRole 标签
hasRole表示验证当前用户是否拥有某些角色。
1 | html复制代码<@shiro.hasRole name="admin">Hello admin!</@shiro.hasRole> |
4.8.5.3.5、hasAnyRoles 标签
hasAnyRoles标签表示验证当前用户是否拥有这些角色中的任何一个,角色之间逗号分隔。
1 | html复制代码<@shiro.hasAnyRoles name="admin,user">Hello admin</@shiro.hasAnyRoles> |
4.8.5.3.6、hasPermission 标签
hasPermission 标签表示验证当前用户是否拥有该权限。
1 | html复制代码<@shiro.hasPermission name="department:delete">删除</@shiro.hasPermission> |
4.8.6、重构权限加载方法
这里要说的是一种思想,我们在项目中可能会遇到需要加载项目中加了@RequiresPermissions注解的权限,就会有一个类似加载权限的按钮。
但是我们发现好像Shiro的@RequiresPermissions注解并没有提供name属性给我们,仅仅只有value属性,那么我们需要另辟蹊径来完成这个需求。
Shiro的@RequiresPermissions注解有两个属性:
- value属性:这个属性是一个数组,也就是说一个请求映射方法可以运行配置多个权限。多个权限之间用逗号隔开
1 | java复制代码value={"employee:list","employee:delete", } |
- logical 属性:该属性根据配置属性值对当前用户是否有权限访问请求映射方法进行限制,他有两个值:
Logical.AND: 必须同时拥有value配置所有权限才允许访问。
Logical.OR:只需要拥有value配置所有权限中一个即可允许访问。
我们可以约定@RequiresPermissions 注解中的value属性值(数组)中第一位为权限表达式, 第二位为权限名称。
1 | java复制代码 // shiro注解无法使用name属性,所以约定,value中第一个位置的值是权限表达式,第二个位置的值是权限名称. |
同时修改reload方法
1 | java复制代码 public void reload() { |
五、Shiro密码加密
加密的目的是从系统数据的安全考虑,如,用户的密码,如果我们不对其加密,那么所有用户的密码在数据库中都是明文,只要有权限查看数据库的都能够得知用户的密码,这是非常不安全的。所以,只要密码被写入磁盘,任何时候都不允许是明文, 以及对用户来说非常机密的数据,我们都应该想到使用加密技术,这里我们采用的是 MD5+盐+散列次数来进行加密。
如何实现项目中密码加密的功能:
- 添加用户的时候,对用户的密码进行加密
- 登录时,按照相同的算法对表单提交的密码进行加密然后再和数据库中的加密过的数据进行匹配
5.1、MD5+盐加密
MD5 加密的数据如果一样,那么无论在什么时候加密的结果都是一样的,所以,相对来说还是不够安全,但是我们可以对数据加“盐”。同样的数据加不同的“盐”之后就是千变万化的,因为我们不同的人加的“盐”都不一样。这样得到的结果相同率也就变低了。
**盐一般要求是固定长度的字符串,且每个用户的盐不同。**
可以选择用户的唯一的数据来作为盐(账号名,身份证等等),注意使用这些数据作为盐要求是不能改变的,假如登录账号名改变了,则再次加密时结果就对应不上了。
5.2、Md5Hash()
Md5Hash()这个方法有三个参数,第一个参数表示需要加密的密码的明文,第二个参数表示加密时所需要的盐,第三个参数表示散列次数(加密几次),这样可以保证加密后的密文很难恢复和破解。
5.3、注册用户(密码加密)
在添加用户的时候,需要对用户的密码进行加密。
1 | java复制代码@RequestMapping("checkUsername") |
5.4、登录
在登录时, 先对前端传过来的密码进行与注册相同的相同算法的加密,再传给shiro进行认证处理即可。
1 | java复制代码try { |
六、Shiro集成EhCache
6.1、Cache是什么
Cache是缓存,他是**计算机内存中一段数据** ,他的作用是 **用来减轻DB的访问压力,从而提高系统的查询效率。**
6.2、使用缓存的原因
我们在进行Debug的时候,我们会发现,一旦请求到需要权限控制的方法的时候,每请求一次他都会去调用自定义Realm中的 doGetAuthorizationInfo 方法获取用户的权限信息,这个时候对数据库造成的访问压力是十分大的,而且用户登陆后,授权信息一般很少变动,所以我们可以在第一次授权后就把这些授权信息存到缓存中,下一次就直接从缓存中获取,避免频繁访问数据库。
6.3、集成EhCache
6.3.1、引入依赖
1 | xml复制代码<!--引入shiro和ehcache--> |
6.3.2、添加缓存配置文件
1 | xml复制代码<ehcache> |
配置文件属性详解:
maxElementsInMemory: 缓存对象最大个数。
**eternal **:对象是否永久有效,一但设置了,timeout 将不起作用。
timeToIdleSeconds: 对象空闲时间,指对象在多长时间没有被访问就会失效(单位:秒)。仅当 eternal=false 对象不是永久有效时使用,可选属性,默认值是 0,也就是可闲置时间无穷大。
timeToLiveSeconds:对象存活时间,指对象从创建到失效所需要的时间(单位:秒)。仅当 eternal=false 对象不是永久有效时使用,默认是 0,也就是对象存活时间无穷大。
memoryStoreEvictionPolicy:当达到 maxElementsInMemory 限制时,Ehcache 将会根据指定的策略去清理内存。
缓存策略一般有3种:
- 默认LRU(最近最少使用,距离现在最久没有使用的元素将被清出缓存)。
- FIFO(先进先出, 如果一个数据最先进入缓存中,则应该最早淘汰掉)。
- LFU(较少使用,意思是一直以来最少被使用的,缓存的元素有一个hit 属性(命中率),hit 值最小的将会被清出缓存)。
6.3.2、配置缓存管理器
1 | java复制代码<!--安全管理器--> |
本文转载自: 掘金