权限验证框架Shiro
一、Shiro简介
Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。
三个核心组件:Subject, SecurityManager 和 Realms.
Subject:即“当前操作用户”。
但是,在Shiro中,Subject这一概念并不仅仅指人,也可以是第三方进程、后台帐户(Daemon Account)或其他类似事物。它仅仅意味着“当前跟软件交互的东西”。
Subject代表了当前用户的安全操作,SecurityManager则管理所有用户的安全操作。
SecurityManager:它是Shiro框架的核心,典型的Facade模式,Shiro通过SecurityManager来管理内部组件实例,并通过它来提供安全管理的各种服务。
Realm: Realm充当了Shiro与应用安全数据间的“桥梁”或者“连接器”。也就是说,当对用户执行认证(登录)和授权(访问控制)验证时,Shiro会从应用配置的Realm中查找用户及其权限信息。
从这个意义上讲,Realm实质上是一个安全相关的DAO:它封装了数据源的连接细节,并在需要时将相关数据提供给Shiro。当配置Shiro时,你必须至少指定一个Realm,用于认证和(或)授权。配置多个Realm是可以的,但是至少需要一个。
Shiro内置了可以连接大量安全数据源(又名目录)的Realm,如LDAP、关系数据库(JDBC)、类似INI的文本配置资源以及属性文件等。
如果系统默认的Realm不能满足需求,你还可以插入代表自定义数据源的自己的Realm实现。
二、Shiro内置的过滤器
三、ERP整合Shiro
1、添加依赖
(1)给erp_parent父工程添加依赖
定义版本号:
<shiro.ver>1.2.3</shiro.ver>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>${shiro.ver}</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>${shiro.ver}</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>${shiro.ver}</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-aspectj</artifactId>
<version>${shiro.ver}</version>
</dependency>
(2)配置web.xml,添加过滤器代理DelegatingFilterProxy,要放在struts2的核心过滤器之间
<!-- shiro过滤器 -->
<filter>
<!-- 代理过滤器,指向spring容器当中的id=shiroFilter的Bean来处理 -->
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>*.action</url-pattern>
<url-pattern>*.html</url-pattern>
<!-- 拦截没有后缀名的 -->
<url-pattern>*</url-pattern>
</filter-mapping>
(3)添加shiro核心控制器的spring配置文件
1)创建applicationContext_shiro.xml文件到erp_web的资源目录下
<?xml version="1.0" encoding="UTF-8"?>
<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">
<!-- shiro的过滤工厂,相当默认的加载了9个过滤器 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<!-- 安全管理器,shiro核心组件(大脑) Facade模式 -->
<property name="securityManager" ref="securityManager" />
<property name="filters">
<map>
<entry key="perms" value-ref="erpAuthorizationFilter"></entry>
</map>
</property>
<!-- 用户如果没有登陆,当他在访问资源的时候,就会自动跳转到登陆的页面 -->
<property name="loginUrl" value="/login.html" />
<!-- 当用户没有访问某项资源权限的时候,跳转到该页面 -->
<property name="unauthorizedUrl" value="/error.html" />
<!-- 过滤链的定义:定义URL访问的时候对应的认证或授权时处理的过滤器 -->
<property name="filterChainDefinitions">
<value>
/error.html = anon
/login_*.action = anon
/login_*=anon
/*_list=perms[]
/dep_*=perms["部门"]
/supplier.html=perms["供应商管理","客户管理"]
/supplier_*=perms["供应商管理","客户管理"]
/role.html=perms["角色设置"]
/role_*=perms["角色设置","角色菜单设置"]
/emp.html=perms["员工"]
/emp_*=perms["用户角色设置","重置密码"]
/goodstype.html=perms["商品类型"]
/goodstype_*=perms["商品类型"]
/goods.html=perms["商品"]
/goods_*=perms["商品"]
/store.html=perms["仓库"]
/store_*=perms["仓库"]
/dep.html=perms["部门管理"]
/dep_*=perms["部门管理"]
/orders.html=perms["我的采购订单","采购订单申请","采购订单查询","采购订单审核","采购订单确认","采购订单入库","销售订单查询","销售订单录入","销售订单出库"]
/orders_*=perms["我的采购订单","采购订单申请","采购订单查询","采购订单审核","采购订单确认","采购订单入库","销售订单查询","销售订单录入","销售订单出库"]
/report_order.html=perms["销售统计报表"]
/report_*=perms["销售统计报表","销售趋势报表"]
/report_trend.html=perms["销售趋势报表"]
/*.html = authc
/*.action=authc
/*=authc
</value>
</property>
</bean>
<!-- 安全管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
</bean>
</beans>
2)创建error.html
2、认证
(1)需求分析
判断当前用户是否登录,如果没有登录侧跳转到登录页面
(2)认证现实(重点)
1)Subject的login方法
修改LoginAction的checkUser方法
步骤:
创建令牌:UsernamePasswordToken
获取subject
执行subject.login()方法
public void checkUser() {
// 在登录的时候可能会发送异常
try {
/*
* // 查询是否存在 Emp loginUser = empBiz.findByUsernameAndPwd(username, pwd);
* System.out.println(loginUser+"emp"); if (loginUser != null) { // 记录当前登录的用户
* ActionContext.getContext().getSession().put("loginUser", loginUser);//
* 先把用户的信息放到session当中 ajaxReturn(true, "用户名或密码正确"); } else { ajaxReturn(false,
* "用户名或密码不正确"); }
*/
// 1、创建令牌(通行证)身份认证,身份证明
UsernamePasswordToken upt = new UsernamePasswordToken(username, pwd);
// 2、获取主机subject:封装当前用户的操作
Subject subject = SecurityUtils.getSubject();
// 3、指向login
subject.login(upt);
ajaxReturn(true, "用户名或密码正确");
} catch (Exception e) {
e.printStackTrace();
ajaxReturn(false, "登录失败");
}
}
3、自定义Realm
我们改用subject.login方法后,并不会调用登陆的业务层进行登陆的验证查询,即不会从数据库查找登陆的用户名和密码是否正确,而是将这项工作交给 shiro去完成。
那 shiro,是怎么知道登陆的用户名和密码是否正确的呢?其实它也需要用到我的登陆验证业务,这时它就得向“别人”打听一下,那就是Realm了。
真正实现登陆验证的是Realm,而shiro只是去调Realm
Realm: Realm 充当了 Shiro与应用安全数据间的“桥梁"或者"连接器"。也就是说,当对用户执行认证(登录)和授权(访问控制)验证时,Shiro会从应用配置的 Realm中查找用户及其权限信息。
从这个意义上讲,Realm实质上是一个安全相关的DAO:它封装了数据源的逐接细节,并在需要时将相关数据提供给 Shiro。当配置 Shiro,时,你必须至少指定一个 Realm,用于认证和或授权。
配置多个Realm 是可以的,但是至少需要一个。
1) 在erp_web子工程下创建包 com.itzheng.erp.realm:
2)创建ErpRealm类继承自AuthorizingRealm
package com.itzheng.erp.realm;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
public class ErpRealm extends AuthorizingRealm {
/*
* 授权
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
System.out.println("执行的授权的方法。。。");
return null;
}
/*
* 认证
* return null;认证失败,返回AuthenticationInfo实现类,认证成功
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("执行的授权的方法。。。");
return null;
}
}
3)修改applicationContext_shiro.xml
<!-- 安全管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="erpRealm"></property>
</bean>
<!-- 自定义的realm -->
<bean id="erpRealm" class="com.itzheng.erp.realm.ErpRealm">
<property name="empBiz" ref="empBiz"></property>
</bean>
4)修改ErpRealm的doGetAuthenticationInfo方法
public class ErpRealm extends AuthorizingRealm {
private IEmpBiz empBiz;
/*
* 授权
*/
public void setEmpBiz(IEmpBiz empBiz) {
this.empBiz = empBiz;
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
System.out.println("执行的授权的方法。。。");
return null;
}
/*
* 认证 return null;认证失败,返回AuthenticationInfo实现类,认证成功
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("执行的授权的方法。。。");
//通过令牌 用户名和密码?
UsernamePasswordToken upt = (UsernamePasswordToken) token;
//得到密码
String pwd = new String(upt.getPassword());
//调用登录查询
Emp emp = empBiz.findByUsernameAndPwd(upt.getUsername(), pwd);
if(null != emp) {
//构造参数1,主角=登录用户
//参数2:授权码
//参数3,realm的名称
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(emp,pwd,getName());
return info;
}
return null;
}
}
5)修改applicationContext_shiro.xml
6)修改LoginAction,以及对应的配置文件
/*
* 显示登录用户
*/
public void showName() {
// 获取当前登录的用户
//Emp emp = (Emp) ActionContext.getContext().getSession().get("loginUser");// 先把用户的信息放到session当中
// session是否会超时,用户是否登录过
//获取主题
Subject subject = SecurityUtils.getSubject();
//提取主角,拿到emp
Emp emp =(Emp)subject.getPrincipal();
System.out.println(emp);
if (null != emp) {
System.out.println(emp + "yyyy");
ajaxReturn(true, emp.getName());
} else {
ajaxReturn(false, "");
}
}
/*
* 退出登录
*
*/
public void loginOut() {
// ActionContext.getContext().getSession().remove("loginUser");
SecurityUtils.getSubject().logout();
}
applicationContext_shiro.xml
7)修改BaseAction的getLoginUser方法
/*
* 获取登录的用户信息
*/
public Emp getLoginUser() {
//Emp emp = (Emp) ActionContext.getContext().getSession().get("loginUser");
return (Emp) SecurityUtils.getSubject().getPrincipal();
}
4、授权(指定电脑的那些资源可以被访问)
(1)需求分析
授权就是通过设置规则,指定URL需要哪些授权才可以访问
(2)授权的实现
1)授权方法与配置(重点)
a、修改ErpRealm的doGetAuthorizationInfo方法
System.out.println("执行了授权的方法...");
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
// uuid=?怎么来
Emp emp = (Emp) principals.getPrimaryPrincipal();
// 获取当前登陆用户的菜单权限
List<Menu> menuList = empBiz.getMenusByEmpuuid(emp.getUuid());
b、配置权限名称:修改applicationContext_shiro.xml
<value>
/error.html = anon
/login_*.action = anon
/login_*=anon
/*_list=perms[]
/dep_*=perms["部门"]
/supplier.html=perms["供应商管理","客户管理"]
/supplier_*=perms["供应商管理","客户管理"]
/role.html=perms["角色设置"]
/role_*=perms["角色设置","角色菜单设置"]
/emp.html=perms["员工"]
/emp_*=perms["用户角色设置","重置密码"]
/goodstype.html=perms["商品类型"]
/goodstype_*=perms["商品类型"]
/goods.html=perms["商品"]
/goods_*=perms["商品"]
/orders.html=perms["我的采购订单","采购订单申请","采购订单查询","采购订单审核","采购订单确认","采购订单入库","销售订单查询","销售订单录入","销售订单出库"]
/orders_*=perms["我的采购订单","采购订单申请","采购订单查询","采购订单审核","采购订单确认","采购订单入库","销售订单查询","销售订单录入","销售订单出库"]
/report_order.html=perms["销售统计报表"]
/report_*=perms["销售统计报表","销售趋势报表"]
/report_trend.html=perms["销售趋势报表"]
/*.html = authc
/*.action=authc
/*=authc
</value>
2)
3)小结:
授权方法的作用:告诉shiro当前用户有什么权限
配置信息的作用:告诉shiro什么资源有什么权限才可以被访问
5、自定义授权过滤器
(1)创建filter包以及ErpAuthorizationFilter类继承AuthorizationFilter
package com.itzheng.erp.filter;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authz.AuthorizationFilter;
/**
* 自定义授权过滤器
*
*/
public class ErpAuthorizationFilter extends AuthorizationFilter {
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue)
throws Exception {
// 获取主题
Subject subject = getSubject(request, response);
/// orders.html=perms["采购订单的查询","采购订单的审核","采购订单的确认","采购订单的入库"]
// mappedValue="采购订单的查询","采购订单的审核","采购订单的确认","采购订单的入库"
String[] perms = (String[]) mappedValue;
boolean isPermitted = true;
if (null == perms || perms.length == 0) {
return isPermitted;
}
if (perms != null && perms.length > 0) {
/*
* 原有的代码 if (perms.length == 1) { if (!subject.isPermitted(perms[0])) {
* isPermitted = false; } } else { if (!subject.isPermittedAll(perms)) {
* isPermitted = false; } }
*/
for (String perm : perms) {
// 只要有一个权限,就返回true
if (subject.isPermitted(perm)) {
return true;
}
}
}
return false;
}
}
(2)过滤器的配置和使用
<property name="securityManager" ref="securityManager" />
<property name="filters">
<map>
<entry key="perms" value-ref="erpAuthorizationFilter"></entry>
</map>
</property>