Shiro——权限验证框架基本认识

1. 简介

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

2. Shiro内置过滤器

anon

org.apache.shiro.web.filter.authc.AnonymousFilter

authc

org.apache.shiro.web.filter.authc.FormAuthenticationFilter

authcBasic

org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter

perms

org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter

port

org.apache.shiro.web.filter.authz.PortFilter

rest

org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter

roles

org.apache.shiro.web.filter.authz.RolesAuthorizationFilter

ssl

org.apache.shiro.web.filter.authz.SslFilter

user

org.apache.shiro.web.filter.authc.UserFilter

这些过滤器分为两组:
1)认证过滤器:anon(不认证也可以访问),authcBasic, authc(必须认证后才可访问)
2)授权过滤器:perms(指定资源需要哪些权限才可以访问),Roles, ssl, rest, port

3. 使用shiro的加密工具加密——Md5Hash

3.1 添加依赖

<dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-core</artifactId>
        <version>1.2.3</version>
    </dependency>

    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-web</artifactId>
        <version>1.2.3</version>
    </dependency>

    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-spring</artifactId>
        <version>1.2.3</version>
    </dependency>

    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-aspectj</artifactId>
        <version>1.2.3</version>
    </dependency>
</dependencies>

3.2 用法

shiro的加密工具使用起来很简单,直接使用Md5Hash的构造函数,例如:

private String getMd5Pwd(String pwd,String username) {
	if(!StringUtils.isEmpty(pwd)) {
		/* 使用shiro的Md5Hash对密码进行加密处理 
		 * Md5Hash提供了三个构造函数,这里使用复杂的一种
		 * 		参数一:源
		 *  	参数二:盐,也就是搅乱码,这里使用用户名。
		 *  	参数三:散列次数,也就是加密次数
		 * */
		System.out.println("原密码:" + pwd);
		Md5Hash md5 = new Md5Hash(pwd, username, hashIterations);
		System.out.println("加密之后的密码:" + md5.toString());
		return md5.toString();
	}
	return pwd;
}

hashIterations就是散列次数,一般设为2,数字越大,加密越复杂。

4. shiro的核心配置

4.1 在web.xml中添加过滤器代理DelegatingFilterProxy

<!-- 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>*.do</url-pattern>
	<url-pattern>*.html</url-pattern>
	<url-pattern>*</url-pattern> <!-- * 表示没有后缀名的请求 -->
</filter-mapping>

        Spring提供的一个简便的过滤器处理方案,它将具体的操作交给内部的Filter对象delegate去处理,而这个delegate对象通过Spring的IOC容器获取,这里采用的是Spring的FactorBean的方式获取这个对象。

        虽然中配置了这一个filter,但是它并没做任何实际的工作,而是把这个工作交由Spring容器中一个bean的id为shiroFilter的类,即ShiroFilterFactoryBean

4.2 添加shiro核心控制器的spring配置文件

<?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工厂,创建了该工厂,shiro的9个过滤器就默认的加载了  -->
	<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
		<!-- 安全管理器,shiro的核心组件 Facade模式 -->
		<property name="securityManager" ref="securityManager" />
		<!-- 如果没有登录,访问资源就会自动跳转到login.html -->
		<property name="loginUrl" value="/login.html" />
		<!-- 当用户没有访问某个资源权限的时候,跳转到该页面 -->
		<property name="unauthorizedUrl" value="/error.html" />
		<!-- 定义过滤链:定义URL访问的时候,对应的认证或授权时处理的过滤器 -->
		<property name="filterChainDefinitions">
			<value>
				/error.html = anon	<!-- 注意:匿名的要放在前面 -->
				/*.html = authc
				/*.do = authc
				/* = authc
			</value>
		</property>
	</bean>

	<!-- 安全管理器,shiro的核心组件 -->
	<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">

	</bean>


</beans>

5. 匿名认证的使用

我们可以先做一个登录的认证

/** 根据用户名和密码查询员
 * */
@RequestMapping(value="/checkUser")
public ResultEntity checkUser(HttpSession session,Emp emp) {
	try {
		if(null != emp) {
			return new ResultEntity(false,500,"请输入用户名和密码!");
		} 
		// 1. 创建令牌
		UsernamePasswordToken upt = new UsernamePasswordToken(emp.getName(),emp.getPwd());
		// 2. 获取主题:封装了当前用户的一些操作
		Subject subject = SecurityUtils.getSubject();
		// 3. 执行登录
		subject.login(upt);
		// 执行到这里,证明登录成功,直接返回
		return new ResultEntity().ok();
	} catch (Exception e) {
		System.out.println(e);
		return new ResultEntity(false,500,"登录失败!");
	}
}

然后在xml文件中配置匿名访问连接

登录系统,发现还是登录不上,这是因为shiro还没这么智能,我们没告诉它去哪里找正确的用户名和密码,它怎么能知道我们是登录成功还是失败了,这时候,我们就要了解shiro的realm了

5.1 自定义Realm实现认证功能

        Realm充当了Shiro与应用安全数据间的“桥梁”或者“连接器”。也就是说,当对用户执行认证(登录)和授权(访问控制)验证时,Shiro会从应用配置的Realm中查找用户及其权限信息。 
         从这个意义上讲,Realm实质上是一个安全相关的DAO:它封装了数据源的连接细节,并在需要时将相关数据提供给Shiro。当配置Shiro时,你必须至少指定一个Realm,用于认证和(或)授权。配置多个Realm是可以的,但是至少需要一个。
        回到上面的登录问题上,我们改用subject.login方法后,并不会调用登陆的业务层进行登陆的验证查询,即不会从数据库查找登陆的用户名和密码是否正确,而是将这项工作交给shiro去完成。那shiro是怎么知道登陆的用户名和密码是否正确的呢?其实它也需要用到我的登陆验证业务,这时它就得向“别人”打听一下,那就是Realm了。
        真正实现登陆验证的是Realm,而shiro只是去调Realm

5.1.1 新建类,并继承AuthorizingRealm类

package erp.web.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) {
		// TODO Auto-generated method stub
		return null;
	}

	// 认证
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
		// TODO Auto-generated method stub
		return null;
	}

}

注意:这里有两个类都可以解决我们的问题一个是AuthenticatingRealm,一个就是AuthorizingRealm,这两个有什么关系了?

AuthenticatingRealm是AuthorizingRealm父类,包含了认证的相关操作,AuthorizingRealm既包含认证也包含授权的相关操作。

5.1.2 在xml文件中配置realm

<!-- 安全管理器,shiro的核心组件 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
	<property name="realm" ref="erpRealm"></property>
</bean>

<!-- 自定义realm -->
<bean id="erpRealm" class="erp.web.realm.ErpRealm"></bean>

5.1.3 实现认证逻辑

package erp.web.realm;

import javax.annotation.Resource;

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.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;

import cn.bjc.biz.IEmpBiz;
import cn.bjc.entity.Emp;

public class ErpRealm extends AuthorizingRealm{

	@Resource
	private IEmpBiz empBiz;
	
	// 授权
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
		// TODO Auto-generated method stub
		return null;
	}

	/**认证
	 * @return  返回null 表示认证失败  返回AuthenticationInfo的实现类,表示认证通过
	 */
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
		// 1. 获取用户名和密码 AuthenticationToken 是 UsernamePasswordToken的父类,所以可以强转
		UsernamePasswordToken upt = (UsernamePasswordToken)token;
		String username = upt.getUsername();
		String password = new String(upt.getPassword());
		
		// 构建查询条件,并查询数据库
		Emp emp = empBiz.getEmpByNameAndPwd(username,password);
		if(null != emp) {
			/*
			 * 构造参数:
			 * 		1.  主角:登录用户
			 * 		2.授权码:写进去一个值就行,一般就用密码或者用户名
			 * 		3. realm名称
			 * */
			SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(emp,password,getName());
			return info;
		}
		return null;
	}

}

然后,运行,登录,发现闪了一下,又回到了登录页面,并提示请登录(这里是因为我做了处理的),出现这个的原因是,前面的开发中,loginUser对象放在session中,现在使用了shiro之后,登录对象放在shiro的subject中了,所以session中没有loginUser对象,所以登不进去了。解决办法也很简单,将获取loginUser的showname与logout与getLoginuser方法都改成从subject中获取即可,例如:

@RequestMapping(value="/showName")
public ResultEntity showName(HttpSession session) {
	try {
		// 1. 获取主题(再realm中有设置new SimpleAuthenticationInfo(emp,password,getName());)
		Subject subject = SecurityUtils.getSubject();
		// 2. 提取猪脚对象
		Emp loginUser = (Emp) subject.getPrincipal();
		if(null != loginUser) {
			return new ResultEntity(true, loginUser.getUuid(), loginUser.getUsername());
		} else {
			return new ResultEntity(false, 500, "请先登录!");
		}
	} catch (Exception e) {
		e.printStackTrace();
		return new ResultEntity(false, 500, "服务器异常!");
	}
	
}

5.2 认证的过程

        shiro的工作是通过它的三大核心组件subject(登录用户)、shiro SecurityManager(管理登录用户)、Realm(认证数据源)来完成认证过程的。

        当我们的系统被访问的时候,会调用shiro的主题subject,然后subject的方法会去调用SecurityManager,去进行realm的调用。当我们是subject调用login方法的时候,实际上是调用安全管理SecurityManager的的login方法

6. 授权

        授权就是通过设置规则,指定哪些URL需要哪些权限才可以访问

6.1 授权的配置——perms

在applicationContext_shiro.xml中配置一个权限,如图:

表示,当前访问的用户需要有dep权限。

注意:支持中文

访问,带dep的url,发现,无法访问了,如图:

然后,在realm中的授权方法中添加权限,如图;

然后再次访问,可以访问了,如图:

从这个案例中,可以知道,两点

1)授权方法的作用:告诉shiro当前用户有什么权限

2)配置信息的作用:告诉shiro什么资源有什么权限才可以访问

6.2 登录用户授权的实现

首先在配置文件中配置url访问规则,如图:

然后,在realm中,通过用户的权限菜单授权,例如:

@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
	System.out.println("执行了授权的方法。。。");
	// 创建授权对象
	SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
	
	// 获取主题对象并强转层emp对象
	Emp emp = (Emp)principals.getPrimaryPrincipal();
	// 根据用户的id,查询用户下的权限菜单名称
	List<String> menuNames = empBiz.getAllMenuNameByEmpuuid(emp.getUuid());
	// 添加权限并返回
	info.addStringPermissions(menuNames);
	return info;
}

注意:在perms中定义权限码的时候,可以定义多个,但是这时候,每个授权码之间是and的关系,即,当在一个perms中配置多个授权码的时候,登录用户必须同时满足这些条件才能登录成功。

例如:/store.html=perms["仓库1号","仓库2号"]

6.3 自定义授权过滤器

前面我们了解到shiro中,当一个连接有多个权限的时候,默认的过滤器不能满足我们的or的诉求,所以,我们强烈的需要可以自定义一个过滤器,以满足我们只要有一个权限码满足就可以访问的诉求,我们先看一下perms过滤器的源码定义:

package org.apache.shiro.web.filter.authz;

import java.io.IOException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

import org.apache.shiro.subject.Subject;

public class PermissionsAuthorizationFilter extends AuthorizationFilter {

    public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws IOException {

        Subject subject = getSubject(request, response);
        String[] perms = (String[]) mappedValue;

        boolean isPermitted = true;
        if (perms != null && perms.length > 0) {
            if (perms.length == 1) {
                if (!subject.isPermitted(perms[0])) {
                    isPermitted = false;
                }
            } else {
                if (!subject.isPermittedAll(perms)) {
                    isPermitted = false;
                }
            }
        }

        return isPermitted;
    }
}

可看出,该过滤器有三个参数,request、response、和mappedValue,其中request和response并未用到,这两个参数可以作为扩展参数来看,而mapperValue就是我们配置的perms的参数值,这里首先将Object转成String[],通过其length来判断,当length>1的时候,如图:

可以知道,只要有一个不满足,就直接返回false了,所以,我们只需要重写该过滤器,将这段代码改造一下即可。

6.3.1 过滤器定义

package erp.web.filter;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authz.AuthorizationFilter;

/**自定义授权过滤器
 * @author Administrator
 *
 */
public class ErpAuthorizationFilter extends AuthorizationFilter{

	@Override
	protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue)
			throws Exception {
		
			// 获取主题
		 	Subject subject = getSubject(request, response);
		 	// 权限码
	        String[] perms = (String[]) mappedValue;

	        if (perms != null && perms.length > 0) {
	            for(String str : perms) {
	            	if(subject.isPermitted(str)) {  // 只要有一个满足,就返回true
	            		return true;
	            	}
	            }
	        } else {  // 没有配置权限,放行
	        	return true;
	        }

	        return false;
	}

}

6.3.2 过滤器配置

1)注入过滤器

<!-- 自定义过滤器配置 -->
<bean id="erpAuthorizationFilter" class="erp.web.filter.ErpAuthorizationFilter"></bean>

2)在shiroFilter中注入过滤器

<!-- 注入过滤器 -->
<property name="filters">
	<map>
		<entry key="myPerms" value-ref="erpAuthorizationFilter"></entry>
	</map>
</property>

3)定义权限码数组

注意:自定义过滤器的key的属性值是什么,这里的权限组就用什么名字,如果自定义的过滤器的key的值为perms,那么咱们的自定义的过滤器就覆盖了shiro原有的perms过滤器了。

总结:

Shiro的三大核心组件

Subject:正与系统进行交互的人,或某一个第三方服务。所有Subject实例都被绑定到(且这是必须的)一个SecurityManager上。
SecurityManager:Shiro架构的心脏,典型的Facade模式。用来协调内部各安全组件,管理内部组件实例,并通过它来提供安全                                    管理的各种服务。当Shiro与一个Subject进行交互时,实质上是幕后的SecurityManager处理所有繁重的                                               Subject安全操作。
Realms:本质上是一个特定安全的DAO。当配置Shiro时,必须指定至少一个Realm用来进行身份验证和/或授权。Shiro提供了多                   种可用的Realms来获取安全相关的数据。如关系数据库(JDBC),INI及属性文件等。可以定义自己Realm实现来代表自                      定义的数据源
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值