本文核心:RBAC权限模型,Shiro与Spring的整合,Shiro认证与授权【集成到项目中】。
这系列博客是记录自己做过项目学到知识。one、two代表项目编号。
前言:
后台管理系统
技术选型:ssm + easyui + ajax + js/jq + jsp + mysql + redis + webservice
工具版本:jdk1.8 + tomcat7/8 + mysql8.0.11 + idea2017 + spring4.1.13
项目规范:maven3.3.9 + git
shiro认证与授权可以通过配置shiro.ini文件。这里采用Shrio与Spring项目集成的方式,将Shiro的配置文件配置到Spring配置文件中。
一:RBAC权限模型【管理系统权限】
首先介绍一下,什么是认证,什么是授权。
认证:
Subject:主体
访问系统的用户,主体可以是用户、程序等,进行认证的都称为主体。
Principal:身份信息
是主体(subject)进行身份认证的标识,标识必须具有唯一性,如用户名、手机号、邮箱地址等,一个主体可以有多个身份,但是必须有一个主身份(Primary Principal)。
credential:凭证信息
是只有主体自己知道的安全信息,如密码、证书等。
token(令牌):身份信息+凭证信息的组合=一个令牌
授权:
Who,即主体(Subject),主体需要访问系统中的资源。
What,即资源(Resource),如系统菜单、页面、按钮、类方法、系统商品信息等。资源包括资源类型和资源实例,比如商品信息为资源类型,类型为t01的商品为资源实例,编号为001的商品信息也属于资源实例。
How,权限/许可(Permission),规定了主体对资源的操作许可,权限离开资源没有意义,如用户查询权限、用户添加权限、某个类方法的调用权限、编号为001用户的修改权限等,通过权限可知主体对哪些资源都有哪些操作许可
RBAC认为权限的过程可以抽象概括为:判断【Who是否可以对What进行How的访问操作(Operator)】这个逻辑表达式的值是否为True的求解过程。
即将权限问题转换为Who、What、How的问题。who、what、how构成了访问权限三元组。
权限模型:
我们的数据库权限设计,需要五张表。这张图我已经详细解释了如何查询用户的角色和权限。【不补充数据库设计和Mapper文件的写法。】
二:Shiro与Spring整合
首先导入Shiro依赖和Shiro与Spring整合依赖 【默认已完成SSM项目框架搭建】
<!-- shiro core与Web集成的依赖 -->
<!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-core -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.3.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-web -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.3.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-spring -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.3.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-ehcache -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.2.6</version>
</dependency>
Web.xml配置 shiro的过滤器【Filter过滤器配置顺序即调用链传递的顺序,个人建议:如果配置SpringMVC自带的字符过滤器应放在Shiro过滤器前面。】
<!--配置shir的过滤器-->
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<init-param>
<param-name>targetBeanName</param-name>
<param-value>shiroFilter</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
其中的<init-param></init-param> 配置指向 application-context-shiro.xml的配置文件的一个bean
application-context-shiro.xml的配置文件
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"><!--配置安全管理工厂-->
<!--配置安全工厂-->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!--配置登陆验证与授权的myRealm-->
<property name="realms">
<list>
<ref bean="myRealm"></ref>
</list>
</property>
</bean>
<!--匹配器对象 并配置复杂匹配器-->
<bean id="myRealm" class="com.baizhi.util.MyRealm">
<property name="credentialsMatcher" ref="credentialsMatcher"></property>
</bean>
<!--声明复杂的凭证匹配器对象-->
<bean id="credentialsMatcher" class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<!--加密算法-->
<property name="hashAlgorithmName" value="md5"></property>
<!--迭代加密次数-->
<property name="hashIterations" value="1024"></property>
</bean>
<!--shiro过滤对象工厂对象 负责校验 处理请求的bean
【区分】限制资源的访问【主要判断用户是否认证,重点:认证】
-->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"/>
<!--登录页的Url 验证失败 重定向的Url-->
<property name="loginUrl" value="/Login_Jsp/login.jsp"/>
<!-- 产生异常 重定向的Url-->
<property name="unauthorizedUrl" value="/error.jsp"/>
<property name="filterChainDefinitions" >
<!--
定义过滤器链的拦截规则
从上到下,满足则执行。
url=过滤器简称
过滤器:anon |不认证就可以访问资源
authc |必须认证才可以访问资源【不支持记住我功能】
user |认证成功或记住我【放行】 可访问资源
logout|登出过滤器
roles |角色授权过滤器
perms|权限授权过滤器
-->
<value>
/Login_Jsp/login.jsp=anon
/Main_Jsp/main/main.jsp=authc
/**=anon
</value>
</property>
</bean>
</beans>
重点理解掌握 【过滤器的取值的含义】
过滤器:anon |不认证就可以访问资源
authc |必须认证才可以访问资源【不支持记住我功能】
user |认证成功或记住我【放行】 可访问资源
logout|登出过滤器
roles |角色授权过滤器
perms|权限授权过滤器
提示:我们将
Shiro的配置单独为一个文件,因此需要将Shiro的配置文件导入Spring配置文件中。【代码省略】
三:自定义复杂的凭证匹配器
MyRealm 继承AuthorizingRealm 类,需要实现两个抽象方法。
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken)//认证方法
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection)//授权方法
认证的过程:
①Controller接收参数用户名密码,web环境下获取主体,调用主体的Login方法。
@RequestMapping("/login")
public @ResponseBody String managerLogin(String managerName, String managerPwd, String enCode, HttpSession session){
//先判断验证码是否正确
String code=(String)session.getAttribute("code");
if(code.equalsIgnoreCase(enCode)) {
// 在web环境中安全管理器会自动进行初始化
// 获取主体
Subject subject = SecurityUtils.getSubject();
try {
subject.login(new UsernamePasswordToken(managerName,managerPwd));
session.setAttribute("managerName",managerName);
return "success";
} catch (Exception e) {
e.printStackTrace();
return "loser";
}
}else{
return "loser";
}
}
② 中间经过一系列的包装,方法走到doGetAuthenticationInfo【认证】,验证账号密码是否正确
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//首先强转为UsernamePasswordToken 可以获取到用户名->查询数据库->验证
UsernamePasswordToken token=(UsernamePasswordToken)authenticationToken;
String managerName=token.getUsername();
Manager manager=managerService.queryManagerByName(managerName);
if (manager!=null){
return new SimpleAuthenticationInfo(managerName,
manager.getManagerPwd(),
ByteSource.Util.bytes(manager.getManagerSalt()),
UUID.randomUUID().toString());
}
return null;
}
验证方法返回的SimpleAuthenticationInfo对象的四个参数: 用户名,加密后的密码,加密的盐,无重复的数。【采用哪种加密算法和散列运算的次数在配置文件中配置】 参考application-context-shiro.xml。
验证失败会以异常的形式抛出 【罗列常见的两种异常,我们可以捕获异常给予用户友好的提示。】
UnknownAccountException
账号不存在异常如下:
org.apache.shiro.authc.UnknownAccountException: No account found for user。。。。
IncorrectCredentialsException
当输入密码错误会抛此异常,如下:
org.apache.shiro.authc.IncorrectCredentialsException: Submitted credentials for token [org.apache.shiro.authc.UsernamePasswordToken - zhangsan, rememberMe=false] did not match the expected credentials.
授权的过程 【获取用户名->查询数据库->绑定角色、权限】
提示,只有先认证成功,才会走到授权这一步。
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//获取用户名 【登陆账号】
String username = (String) principalCollection.getPrimaryPrincipal();
List<Role> list=managerService.queryRole(username);
//这里只查询了角色
if(list!=null){
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
for (Role role : list) {
info.addRole(role.getRoleName());
}
return info;
}
return null;
}
在JSP页面我们一通过Shiro定义的标签轻松判断用户拥有哪些权限和角色,通过标签来控制页面的展示信息。
标签的使用和相关功能【18年7月14日 补充】
//JSP页面引入shiro标签库
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
<shiro:guest></shiro:guest>//判断用户是否为游客
<shiro:user></shiro:user>//判断用户是否已经登录(包括已认证和已记住)
<shiro:hasRole name="root"></shiro:hasRole>//判断用户是否具有某个角色
<shiro:lacksRole name="root"></shiro:lacksRole>//判断用户是否缺少某个角色
<shiro:hasAnyRoles name="root,admin"></shiro:hasAnyRoles>>//判断用户是否具有任意某个角色
<shiro:hasPermission name="user:add"></shiro:hasPermission>//判断用户是否具有某个权限
<shiro:lacksPermission name="user:add"></shiro:lacksPermission>//判断用户是否缺少某个权限