Shiro
- Apache Shiro是Java的一个安全框架.
- 用于身份认证,,授权,加密和会话管理与WEB集成,缓存.
Shiro架构
组件 | 作用 |
---|---|
Subject | 当前访问系统的用户,进行认证的主体.getSubject()获取当前用户,委托Shiro实现功能. |
SecurityManager | 安全管理器,负责与shiro的其他组件进行交互,实现Subject委托的各种功能. |
Realms | 数据源,Realms是shiro与数据库之间的连接器,shiro从自定义的Realms查找相关的数据进行比对,检验. |
Authenticator | 认证器,用于协调协调一个或者多个Realm,从Realm指定的数据源取得数据之后,进行认证. |
Authorizer | 授权器,决定用户是否拥有执行指定操作的权限. |
SessionManager | 会话管理 |
CacheManager | 缓存组件,用于缓存认证信息等 |
Cryptography | Shiro提供了一个加解密的命令行工具jar包 |
shiro构架核心
- Subject
- SecurityManager
- Realms
用户登录认证—>Authenticator
访问授权—>Authorizer
Shiro认证
依赖包
包 | 作用 |
---|---|
shiro-core | shiro核心 |
shiro-web | shiro的Web模块 |
shiro-spring | shiro和Spring集成 |
shiro-ehcache | shiro底层使用的ehcache缓存 |
认证流程图:
认证操作
配置shiro.ini文件,用于被shiro读取ini里配置的用户信息
使用shiro相关Api进行认证
1 |
|
- SecurityUtils.getSubject();
- 获取用户主体对象,调用login方法委托shiro进行登录的认证操作.
- factory.getInstance();
- 创建安全管理器对象
- 添加到运行环境,告诉shiro使用哪个安全管理器调用Authenticator进行认证操作
异常信息
UnknownAccountException** —> 账号错误
IncorrectCredentialsException—> 密码错误
shiro认证流程分析
- 在DelegatingSubject,调用login()方法,传入当前的subject对象和封装好的用户令牌.
- 在DefaultSecurityManager方法中,如果认证方法(令牌)失败.则抛出异常,调用onFailedLogin方法.成功则调用onSuccessfulLogin方法,传入token
- 在AbstractAuthenticator中,调用doAuthenticate方法,传入token
- 在ModularRealmAuthenticator中,getRealms拿到数据源,判断数据源个数调用不同的方法
- AuthenticatingRealm,根据账户信息doGetAuthenticationInfo(重写),拿到用户的认证信息.然后做账户的认证和密码的认证.
- 在SimpleAccountRealm根据token.username获取账户对象.被锁,凭证过期抛异常,最终返回账户信息给调用者.然后继续验证密码.
- 在SimpleCredentialsMatcher拿到凭证匹配器,拿到token和账户对象比对
- 有异常回到onFailedLogin,没有异常onSuccessfulLogin
自定义Realm
- 需要的账户信息通常来自程序或数据库中,需要自定义Realm.
- 继承AuthorizingRealm(需要缓存,认证,授权所有的功能)
- 重写doGetAuthenticationInfo()方法
- 返回AuthenticationInfo对象
获取数据源
1 | //获取认证信息 |
通过配置修改SecurityManager中的默认Realm的使用
1 | #自定义Realm信息 |
预处理操作
- 在认证之前,需要进行预处理.使用过滤器进行过滤操作.
- 在web.xml上配置shiroFilter过滤器
1 | <filter> |
shiro过虑器,DelegatingFilterProx会从spring容器中找shiroFilter,所以过滤器的生命周期
还是交给Spring进行管理的.
配置操作
为了方便对shiro的相关配置进行管理,分离出一个shiro.xml配置文件,在mvc.xml中引用
使用安全管理器DefaultWebSecurityManager,并且在该安全管理器中指定自定义的Realm
1 | <!--配置安全管理器--> |
在shiro.xml中指定系统资源所需要的具体过滤器
1 | <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> |
过滤器
过滤器名称 | 作用 |
---|---|
anon | 匿名拦截器,即不需要登录即可访问.一般用于静态资源过滤 |
authc | 表示需要认证(登录)才能使用 |
authcBasic | Basic HTTP身份验证拦截器 |
roles | 角色授权拦截器,验证用户是否拥有资源角色 |
perms | 权限授权拦截器,验证用户是否拥有资源权限 |
rest | rest风格拦截器,自动根据请求方法构建权限字符串 |
ssl | SSL拦截器,只有请求协议是https才能通过.否则自动跳转会https端口 |
在自定义的Realm中使用注解,让安全管理器能够找到指定的Realm.
注解的目的是把自定义的Realm交给Spring管理
1 | "CrmRealm") ( |
前端请求
在前端使用ajax发生异步请求的方式,需要后端返回一个json格式的数据
1 | <script type="text/javascript"> |
后端的处理操作
- Shiro默认会在FormAuthenticationFilter调用onLoginFailure()和onLoginSuccess()方法对认证的结果进行处理.
- 因此需要继承FormAuthenticationFilter重写这两个方法
1 | //登录成功处理 |
将继承自FormAuthenticationFilter的过滤器设置为当前使用的过滤器
1 | <property name="filters"> |
- Shiro封装了登录业务逻辑,根据请求判断访问登录页面,还是在登录页面中发送登录请求.
- get->访问登录页面
- post->登录请求
Shiro授权
获取授权信息
- 在自定义的Realm中重写doGetAuthorizationInfo()方法,获取数据库中的权限信息,根据对应的权限为当前用户授权
- 判断是否管理员,获得全部权限
- 根据当前用户的id查询所有对应的角色编号
- 查询当前用户的所有权限表达式
- 封装到AuthorizationInfo对象中
1 | //获取授权信息 |
使用Shiro提供的权限注解
Controller的方法上贴上Shiro提供的权限注解(@RequiresPermissions)
- logical属性:
- Logical.AND:必须存在多个权限表达式
- Logical.OR:只拥有一个权限表达式即可
- value属:
- 同时设置权限表达式和权限的名称
1 | "部门列表","department:list"}, logical = Logical.OR) (value = { |
在shiro.xml配置权限注解扫描器
1 | <!--权限注解扫描器--> |
当扫描到Controller中有使用@RequiresPermissions注解时,会使用动态代理为当前Controller生成代理对象,增强对应方法的权限校验功能.
生成权限信息到数据库
- 在PermissionServiceImpl实现类中
- 扫描Controller类中的方法
- 生成权限信息到数据库中
1 | public void reload() { |
权限异常处理
访问了没有权限的页面会抛出UnauthorizedException异常
使用Spring MVC的统一异常处理,在mvc.xml中配置异常页面
1 | <!--配置异常页面--> |
异步请求没有权限异常处理
1 |
|
Shiro标签
在前端页面使用shiro标签,从而更加细致的显示用户对应的权限,没有权限的相关操作隐藏.
引入jar包
1 | <dependency> |
新建CRMFreeMarkerConfigurer类,在FreeMark中注册shiro标签
重写afterPropertiesSet()方法
1 |
|
在mvc.xml中将MyFreeMarkerCong设置成当前环境中使用的配置对象
1 | <!--配置freeMarker的模板路径 --> |
shiro的freemarker常用标签
标签 | 作用 |
---|---|
<@shiro.authenticated>… | 已认证通过的用户 |
<@shiro.notAuthenticated>… | 未认证通过的用户 |
<@shiro.principal property=”name” /> | 输出当前用户信息,通常为登录帐号信息 |
<@shiro.hasRole name=”admin”>… | 验证当前用户是否属于该角色 |
<@shiro.hasAnyRoles name=”admin,user,operator”>… | 验证当前用户是否属于这些角色中的任何一个 |
<@shiro.hasPermission name=”/order:*”>… | 当前用户是否拥有该权限 |
MD5加密
在添加用户的时候,需要对添加的用户密码进行加密
在EmployeeServiceImpl中
1 |
|
在CRMRealm中,认证的时候,密码匹配需要用到的密码应该和添加用户时的加密规则一致
1 | if (currentEmp != null) { |
在shiro.xml配置需要的加密算法
1 | <!--指定当前需要使用的凭证适配器--> |
在Realm中,将容器中的配置的凭证匹配器注入给当前的Realm对象
在该Realm中使用指定的凭证匹配器来完成密码匹配的操作
1 | //注入给当前的Realm对象(@Autowired还可以注入set方法) |
EhCache缓存
用户登陆后,授权信息一般很少变动,所有我们可以在第一次授权后就把这些授权信息存到缓存中,下一次就直接从缓存中获取,避免频繁访问数据库.
使用EhCache实现缓存
添加依赖
1 | <dependency> |
在shiro.xml中
1 | <!--配置缓存管理器并引用缓存管理器--> |
在上面的安全管理器中注入
1 | <bean id="securityManager" |
添加ehcache配置文件:shiro-ehcache.xml
1 |
|
属性 | 作用 |
---|---|
maxElementsInMemory | 缓存最大个数 |
eternal | 对象是否永久有效 |
timeToIdleSeconds | 设置对象在失效前的允许闲置时间(单位:秒) |
timeToLiveSeconds | 设置对象在失效前允许存活时间(单位:秒) |
overowToDisk | 当内存中对象数量达到maxElementsInMemory时,Ehcache将会对象写到磁盘中.LRU(最近最少使用) |
- 清空缓存
- 如果用户正常退出,缓存自动清空.
- 如果用户非正常退出,缓存自动清空.
- 如果修改了用户的权限,而用户不退出系统,修改的权限无法立即生效.
- 当用户权限修改后,用户再次登陆shiro会自动调用realm从数据库获取权限数据,如果在修改权限后想立即清除缓存则可以调用realm的clearCache方法清除缓存
在realm中定义该方法
1 | public void clearCached() { |
最后在角色或权限service中,delete或者update方法去调用realm的清除缓存方法.