shiro入门(整合ssh)(授权实现中用到hql连接查询)

  1. 什么是shiro
  2. 先要做一些配置
  3. 认证实现
  4. 主角对象的提取
  5. 授权实现
  6. 自定义授权过滤器
  7. Shiro细颗粒授权控制

什么是shiro

Apache Shiro是一个强大易用的Java安全框架,提供了认证、授权、加密和会话管理等功能:
认证(Authentication):用户身份识别,常被称为用户“登录”,判断用户是否登陆,如果未登陆,则拦截其请求
授权(Authorization):访问控制。当用户登陆后,判断其身份是否有权限访问相应的资源,如果没有权限则拦截
密码加密(Cryptography):保护或隐藏数据防止被偷窃。将MD5进行二次封装,让其更加容易使用。注意MD5不可逆运算
会话管理(Session Management)
shiro内置过滤器:
anon
authc
authcBasic
perms
port
rest
roles
ssl
user

先要做一些配置

添加pom依赖

	  	<shiro.ver>1.2.3</shiro.ver>
	  </properties>

	     <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>

配置web.xml
添加过滤器代理DelegatingFilterProxy,要放在struts2的核心过滤器之前

		<!-- shiro过滤器  要放在struts2的核心过滤器之前-->
	<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>	

Spring提供的一个简便的过滤器处理方案,它将具体的操作交给内部的Filter对象delegate去处理,而这个delegate对象通过Spring的IOC容器获取,这里采用的是Spring的FactorBean的方式获取这个对象。
虽然中配置了这一个Struts2 的 filter,但是它并没做任何实际的工作,而是把这个工作交由Spring容器中一个bean的id为shiroFilter的类,即ShiroFilterFactoryBean。

添加shiro核心控制器的spring配置文件
添加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">
	    <!--加载核心组件  -->
		<property name="securityManager" ref="securityManager" />
		<!-- 用户如果没有登陆,当他在访问资源的时候,就会自动跳转到登陆的页面 -->
		<property name="loginUrl" value="/login.html" />
		<!-- 当用户没有访问某项资源权限的时候,跳转到该页面 -->
		<property name="unauthorizedUrl" value="/error.html" />
		<!-- 过滤链的定义:定义URL访问的时候对应的认证或授权时处理的过滤器 -->
		<property name="filterChainDefinitions">
			<value>
           <!--前两行anon(认证过滤器)表示,所指定的资源不需要任何权限就可以访问 -->
				/error.html = anon
				/login_*=anon 
			<!--后三行authc(认证过滤器)表示,所指定的资源必须认证后才可以访问访问 -->					
				/*.html = authc
				/*.action=authc
				/*=authc
			</value>
		</property>
	</bean>
	
	<!-- 安全管理器,shiro核心组件(大脑) Facade模式 -->
	<!-- 安全管理器 -->
	<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
	</bean>
</beans>

如果没有error.html 的话创建

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>提示信息</title>
</head>
<body>
尊敬的客户,你没有访问的权限!
</body>
</html>

认证实现

验证登录的方法(页面点击登录后跳转的后端方法)

	/**
	 * 验证登录 两种验证方式,第一种只最原始的方法,第二种是利用shiro进行认证授权
	 */
	public void checkUser(){
		//第一种方式
//		Emp user =  empBiz.checkUserLogin(username,pwd);
//		if(user == null){
//			writeToPage("输入有误");
//		}else{
//			ActionContext.getContext().getSession().put("user", user);
//			writeToPage("登陆成功");
//		}
		//第二种方式
		
		try {
			//1.创建令牌
			UsernamePasswordToken upt = new UsernamePasswordToken(username,pwd);
			//2.获得主题
			Subject subject = SecurityUtils.getSubject();
			//3.执行login
			subject.login(upt);
			writeToPage("登陆成功");
		} catch (AuthenticationException e) {
			writeToPage("输入有误");
			e.printStackTrace();
		}
	}

	public void writeToPage( Object o){
		String jsonString = JSON.toJSONString(o);
		HttpServletResponse response = ServletActionContext.getResponse();
		response.setContentType("text/html;charset=utf-8"); 
		try {
			response.getWriter().print(jsonString);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

自定义Realm
真正实现登陆验证的是Realm,而shiro只是去调Realm
创建ErpRealm类继承自AuthorizingRealm

import java.util.List;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;

/**
 * 自定义realm,实现认证授权
 * @author admin
 *
 */
public class ERPRealm extends AuthorizingRealm{
	private IEmpBiz empBiz;//注入biz

	/**
	 * 授权方法
	 */
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
		System.out.println("授权..");
		return null;
	}

	/**
	 * 认证方法
	 */
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
		System.out.println("认证..");
		UsernamePasswordToken upt = (UsernamePasswordToken)token;
		String pwd = new String(upt.getPassword());
		Emp user =  empBiz.checkUserLogin(upt.getUsername(),pwd);
		if(null != user) {
			//返回认证信息
			//参数一:主角,当前登录的用户
			//参数二:这书或者凭证,这里我们用的是密码
			//参数三:当前realm的名称
			return new SimpleAuthenticationInfo(user,pwd,getName());
		}else {
			return null;
		}
	}
	public IEmpBiz getEmpBiz() {
		return empBiz;
	}
	public void setEmpBiz(IEmpBiz empBiz) {
		this.empBiz = empBiz;
	}
}

配置ErpRealm, 在applicationContext_shiro.xml中配置

<?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">
	    <!--加载核心组件  -->
		<property name="securityManager" ref="securityManager" />
		<!-- 用户如果没有登陆,当他在访问资源的时候,就会自动跳转到登陆的页面 -->
		<property name="loginUrl" value="/login.html" />
		<!-- 当用户没有访问某项资源权限的时候,跳转到该页面 -->
		<property name="unauthorizedUrl" value="/error.html" />
		<!-- 过滤链的定义:定义URL访问的时候对应的认证或授权时处理的过滤器 -->
		<property name="filterChainDefinitions">
			<value>
           <!--前两行anon(认证过滤器)表示,所指定的资源不需要任何权限就可以访问 -->
				/error.html = anon
				/login_*=anon 
			<!--后三行authc(认证过滤器)表示,所指定的资源必须认证后才可以访问访问 -->					
				/*.html = authc
				/*.action=authc
				/*=authc
			</value>
		</property>
	</bean>
	<!-- 安全管理器,shiro核心组件(大脑) Facade模式 -->
	<!-- 安全管理器 -->
	<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
		<property name="realm" ref="erpRealm"></property>
	</bean>

	<!-- 自定义的realm 实现授权认证的逻辑类-->                       
	<bean id="erpRealm" class="realm.ERPRealm">
		<property name="empBiz" ref="empBiz"></property>
	</bean>
</beans>

认证完成,启动项目测试,浏览器地址栏输入localhost:8080/erpa/login.html 之外的的url : localhost:8080/erpa/xxx.html,都会自动跳转到登录页面login.html,登录页面输入正确的用户名密码,可以正常登录。

主角对象的提取

Shiro提供了会话管理机制,实际上我们自定义的realm的认证方法返回值对象中的主角对象就是登陆的用户,它可以代替我们之前存入session中的emp对象。我们可以通过subject的getPrincipal方法将其提取出来。

	/**
	 * 显示用户名
	 * @param o
	 */
	public void showName(){
		//Emp user = (Emp)ActionContext.getContext().getSession().get("user");
		//shiro回话特点实现该功能
		Emp user = (Emp)SecurityUtils.getSubject().getPrincipal();
		if(null != user) {
			writeToPage(user.getName());
		}
	}
	/**
	 * 退出登录
	 * @param o
	 */
	public void loginOut(){
		//ActionContext.getContext().getSession().remove("user");
		SecurityUtils.getSubject().logout();
	}

授权实现

修改ErpRealm中的授权方法:

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;

import entity.Emp;
import entity.Menu;
import service.IEmpBiz;

/**
 * 自定义realm,实现认证授权
 * @author admin
 *
 */
public class ERPRealm extends AuthorizingRealm{
	private IEmpBiz empBiz;//注入biz
	/**
	 * 授权方法
	 */
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
		System.out.println("授权..");
		Emp user =  (Emp)principals.getPrimaryPrincipal();
		SimpleAuthorizationInfo sai = new SimpleAuthorizationInfo();
		List<Menu> menus = empBiz.getMenusByUser(user);
		for (Menu menu : menus) {
			System.out.println("当前用户:"+user.getName()+"     权限:"+menu.getMenuname());
			sai.addStringPermission(menu.getMenuname());
		}
		return sai;
	}

	/**
	 * 认证方法
	 */
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
		System.out.println("认证..");
		UsernamePasswordToken upt = (UsernamePasswordToken)token;
		String pwd = new String(upt.getPassword());
		Emp user =  empBiz.checkUserLogin(upt.getUsername(),pwd);
		if(null != user) {
			//返回认证信息
			//参数一:主角,当前登录的用户
			//参数二:这书或者凭证,这里我们用的是密码
			//参数三:当前realm的名称
			return new SimpleAuthenticationInfo(user,pwd,getName());
		}else {
			return null;
		}
	}
	public IEmpBiz getEmpBiz() {
		return empBiz;
	}
	public void setEmpBiz(IEmpBiz empBiz) {
		this.empBiz = empBiz;
	}
}

这里多说两句,授权方法中根据用户查询器拥有的权限时,emp和role多对多关联,role和menu多对多关联,查询时设计hql的连接查询,这里粘上代码:

	/**
	 * 获取用户权限列表
	 */
	@Override
	public List<Menu> getMenusByUser(Emp user) {
		List<Menu> list = new ArrayList<>();
		String hql = "select m from Emp e join e.roles r join r.menus m where e.id=?";
		return (List<Menu>) this.getHibernateTemplate().find(hql, user.getId());
	}

配置授权控制规则
在applicationContext_shiro.xml中配置

<?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">
	    <!--加载核心组件  -->
		<property name="securityManager" ref="securityManager" />
		<!-- 用户如果没有登陆,当他在访问资源的时候,就会自动跳转到登陆的页面 -->
		<property name="loginUrl" value="/login.html" />
		<!-- 当用户没有访问某项资源权限的时候,跳转到该页面 -->
		<property name="unauthorizedUrl" value="/error.html" />
		<!-- 过滤链的定义:定义URL访问的时候对应的认证或授权时处理的过滤器 -->
		<property name="filterChainDefinitions">
			<value>
				/error.html = anon
				/login_*=anon 
				
				/dep.html=perms["部门"]
				/dep_*=perms["部门"]
				/emp.html=perms["员工"]
				/emp_*=perms["员工"]
				/goods.html=perms["商品"]
				/goods_*=perms["商品"]
				
				/*.html = authc
				/*.action=authc
				/*=authc
			</value>
		</property>
	</bean>
	<!-- 安全管理器,shiro核心组件(大脑) Facade模式 -->
	<!-- 安全管理器 -->
	<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
		<property name="realm" ref="erpRealm"></property>
	</bean>

	<!-- 自定义的realm 实现授权认证的逻辑类-->                       
	<bean id="erpRealm" class="realm.ERPRealm">
		<property name="empBiz" ref="empBiz"></property>
	</bean>
</beans>

注意:
/dep.html=perms[“部门”]
/dep_=perms[“部门”]
/emp.html=perms[“员工”]
/emp_
=perms[“员工”]
/goods.html=perms[“商品”]
/goods_*=perms[“商品”]
perms配置要放在anon 和authc之间,中括号中的双引号里的内容就是权限名称(和授权方法中的这行代码对应sai.addStringPermission(menu.getMenuname());),什么权限对应什么资源!

授权方法的作用:告诉shiro当前用户有什么权限
配置信息的作用:告诉shiro什么资源有什么权限才可以访问
至此,授权实现完成。启动项目测试,发现用户只能访问其所拥有权限对应的资源。

自定义授权过滤器

当一个URL有多个权限需要访问的时候,我们应该怎么配置呢?
如果按下面的方法来配置
/orders.html=perms[“采购订单查询”]
/orders.html=perms[“采购订单审核”]
那么只有最后一条生效,前面的会被后面的覆盖掉

如果按下面的方法来配置
/orders.html=perms[“采购订单查询”,“采购订单审核”]
系统默认是同时具备这两个权限才可以访问此URL,而我们的需求是,只要有具备一种就可以访问此URL。系统使用的是and关系,而不是or关系,那我们如何让它实现or关系呢?这就需要我们去自定义授权过滤器啦

创建自定义过滤器,继承自AuthorizationFilter

package realm;

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;
		//如果为空或长度为0,放行
		if(null == perms || perms.length == 0) {
			return true;
		}
		//权限检查
		for (String perm : perms) {
			//只要有一个符合就放行
			if(subject.isPermitted(perm)) {
				return true;
			}
		}
		return false;
	}
}

配置过滤器

<?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" >
	    <!--加载核心组件  -->
		<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_*=anon 
				
				/dep.html=perms["部门"]
				/dep_*=perms["部门"]
				/emp.html=perms["员工"]
				/emp_*=perms["员工"]
				/goods.html=perms["商品","部门","员工"]
				/goods_*=perms["商品","员工"]
								
				/*.html = authc
				/*.action=authc
				/*=authc
			</value>
		</property>
	</bean>
	<!-- 安全管理器,shiro核心组件(大脑) Facade模式 -->
	<!-- 安全管理器 -->
	<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
		<property name="realm" ref="erpRealm"></property>
	</bean>

	<!-- 自定义的realm 实现授权认证的逻辑类-->                       
	<bean id="erpRealm" class="realm.ERPRealm">
		<property name="empBiz" ref="empBiz"></property>
	</bean>

	<!-- 自定义的授权过滤器 --> 
	<bean id="erpAuthorizationFilter" class="realm.ERPAuthorizationFilter"></bean>

</beans>

注意:引入自定义的授权过滤器时

	<property name="filters">
		<map>
			<entry key="perms" value-ref="erpAuthorizationFilter"></entry>
		</map>
	</property>

其中map中的key值为什么是perms呢,由什么决定呢?哈哈,因为我们自定义重写的正是perms,这样做是告诉shiro过滤的时候用我们写的erpAuthorizationFilter 进行过滤!

ok,自定义的授权过滤器完成,运行项目测试,发现 只要拥有 部门,员工,商品,三个权限中的任意一个,就都可以访问goods.html。

Shiro细颗粒授权控制

我们前面做的权限控制都是建立在对URL的访问控制,我们把它称之为粗颗粒的访问控制。
我们还可以使用shiro的细颗粒授权控制。
细颗粒授权控制包括:方法级别 与 代码级别

方法级别控制
对某个方法加访问控制,用户必须拥有某项权限才可以访问该方法,没有权限则抛出异常,无法访问
1、开启注解,在applicationContext_shiro.xml中添加:

	<!-- 启动shiro注解 -->
	<bean
		class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
			depends-on="lifecycleBeanPostProcessor" >
		<!-- 默认使用JDK代理 ,如被代理类没有实现接口,必须使用下列配置开启 cglib代理  -->
		<property name="proxyTargetClass" value="true" />
	</bean>	
	<bean
		class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
		<property name="securityManager" ref="securityManager" />
	</bean>
	
	<!-- 对安全管理器 增强代码 , spring 后处理器 -->
	<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/> 

2、修改OrdersBiz和OrderdetailBiz,在其方法添加注解

	/**
	 * 审核
	 * @param uuid 订单编号
	 * @param empUuid 审核员
	 */
	@RequiresPermissions("采购订单审核")
	public void doCheck(Long uuid, Long empUuid){
		
		//获取订单,进入持久化状态
		Orders orders = ordersDao.get(uuid);
		//订单的状态
		if(!Orders.STATE_CREATE.equals(orders.getState())){
			throw new ErpException("亲!该订单已经审核过了");
		}
		//1. 修改订单的状态
		orders.setState(Orders.STATE_CHECK);
		//2. 审核的时间
		orders.setChecktime(new Date());
		//3. 审核人
		orders.setChecker(empUuid);
	}
	
	/**
	 * 确认
	 * @param uuid 订单编号
	 * @param empUuid 采购员
	 */
	@RequiresPermissions("采购订单确认")
	public void doStart(Long uuid, Long empUuid){
		//获取订单,进入持久化状态
		Orders orders = ordersDao.get(uuid);
		//订单的状态
		if(!Orders.STATE_CHECK.equals(orders.getState())){
			throw new ErpException("亲!该订单已经确认过了");
		}
		//1. 修改订单的状态
		orders.setState(Orders.STATE_START);
		//2. 确认的时间
		orders.setStarttime(new Date());
		//3. 确认人
		orders.setStarter(empUuid);
	}

如果访问了未授权的方法,则会报如下错误:
在这里插入图片描述

代码级别控制
代码级别控制:指的是在代码中加入权限控制
我们的采购订单申请和销售订单录入,都会调用OrdersBiz的add方法。这样只要用户具有其中一个权限,就可以执行另一个功能了。这样是很恐怖的!
那可怎么办呢?我们可以把控制粒度放在更细的层面上,也就是代码级别访问控制
修改OrdersBiz的add方法,方法一开始就加入以下代码
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值