权限控制方案之——集成shiro

Posted 任长江

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了权限控制方案之——集成shiro相关的知识,希望对你有一定的参考价值。

概述:

         上一篇文章中我们介绍的权限控制方案的实现方式是通过URL拦截实现的,这里介绍通过shiro实现权限控制。shiro是apache下的开源的权限管理框架。

Shiro架构:

        

         Subject:主体,是用户和程序的统一抽象。

         SecurityManager:安全管理器,认证和授权的核心处理类。

         Authenticator:认证器,主体认证的处理类。

         Authorizer:授权器,主体授权的处理类。

         SessionManager:shiro提供的一套session管理机制。

         SessionDao:对于session的存储需要用到sessionDao。

         CacheManager;缓存管理器,主要对session和相关数据进行缓存管理。

         Realm:通过realm间接连接数据源,认证和授权需要通过realm存取认证和授权数据。

         Cryptography:密码管理器,shiro提供的一套加密解密组建。

 

整合shiro

         实现方式:

         1、通过shiro默认的IniRealm实现(开发中一般不用该方式),该方式需要将认证和授权信息配置在一个ini文件中,shiro会默认通过IniRealm读取该文件信息,实现认证。

         2、通过自定义Realm实现(一般都需要自定义Realm),自定义的Realm可以自定义认证和授权信息获取的方式(例如从数据库中查询),自定义的Realm继承AuthorizingRealm。

         这里主要介绍通过自定义Realm实现认证和授权的方式。下面通过将shiro整合到项目中的过程来介绍shiro的基本用法和shiro的工作原理。

         整合过程:

         1、添加jar包

         主要包括:shiro和spring整合的jar包,shiro和web应用整合的jar包和shiro一些基本的包。

         2、配置shiro的filter

         上篇文章我们介绍通过URL拦截中用到了拦截器,shiro内部也是用到了filter拦截进行实现。

         配置信息:

        

<!-- 将shiroFilter作为spring的Bean,由Spring来管理该Filter,实现和其他Filter的统一 -->
	<filter>
		<filter-name>shiroFilter</filter-name>
		<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
		<!-- 设置true表示由servlet容器控制filter的生命周期 -->
		<init-param>
			<param-name>targetFilterLifecycle</param-name>
			<param-value>true</param-value>
		</init-param>
		<!-- 设置spring容器filter指定的Bean的id,如果不设置则找与filter-name一致的bean-->
		<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>


         3、applicationContext-shiro.xml

         在applicationContext-shiro.xml中配置shiroFilter需要的Bean及其他相关配置。

        

<!-- web.xml中shiro的filter对应的bean -->
<!-- Shiro 的Web过滤器 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
	<property name="securityManager" ref="securityManager" />
	<!-- loginUrl认证提交地址,如果没有认证将会请求此地址进行认证,请求此地址将由formAuthenticationFilter进行表单认证 -->
	<property name="loginUrl" value="/login.action" />
	<!-- 认证成功跳转到first.action-->
	<property name="successUrl" value="/first.action"/>
	<!-- 指定没有权限操作时跳转页面-->
	<property name="unauthorizedUrl" value="/refuse.jsp" />
	<!-- 自定义filter配置 -->
	<property name="filters">
		<map>
			<!-- 将自定义 的FormAuthenticationFilter注入shiroFilter中-->
			<entry key="authc" value-ref="formAuthenticationFilter" />
		</map>
	</property>
	
	<!-- 过虑器链定义,从上向下顺序执行,一般将/**放在最下边 -->
	<property name="filterChainDefinitions">
		<value>
			<!-- 对静态资源设置匿名访问 -->
			/images/** = anon
			/js/** = anon
			/styles/** = anon
			/validatecode.jsp = anon
			
			<!-- 只需请求logout.action地址即可清除session-->
			/logout.action = logout
			<!--授权配置,例queryItems.action请求需要item:query权限-->
			/items/queryItems.action = perms[item:query]
			/items/editItems.action = perms[item:edit]
			<!-- /** = authc 所有url都必须认证通过才可以访问-->
			/** = authc
		</value>
	</property>
</bean>

<!-- securityManager属性配置 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
	<property name="realm" ref="customRealm" />
	<!-- 注入缓存管理器 -->
	<property name="cacheManager" ref="cacheManager"/>
	<!-- 注入session管理器 -->
	<property name="sessionManager" ref="sessionManager" />
</bean>

<!-- 自定义的realm -->
<bean id="customRealm" class="cn.itcast.ssm.shiro.CustomRealm">
	<!-- 将凭证匹配器设置到realm中,realm按照凭证匹配器的要求进行散列 -->
	<property name="credentialsMatcher" ref="credentialsMatcher"/>
</bean>

<!-- 凭证匹配器,后边有介绍 -->
<bean id="credentialsMatcher"
	class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
	<property name="hashAlgorithmName" value="md5" />
	<property name="hashIterations" value="1" />
</bean>

<!-- 缓存管理器,后边将会介绍关于引入ehcache缓存 -->
<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
    	<property name="cacheManagerConfigFile" value="classpath:shiro-ehcache.xml"/>
</bean>

<!--session管理器,让shiro对session管理-->
<bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
    <!-- session的失效时长,单位毫秒 -->
    <property name="globalSessionTimeout" value="600000"/>
    <!-- 删除失效的session -->
    <property name="deleteInvalidSessions" value="true"/>
</bean>

<!-- 自定义form认证过虑器 -->
<!-- 基于Form表单的身份验证过滤器,不配置将采用默认值,默认值为username和password -->
<bean id="formAuthenticationFilter" 
class="cn.itcast.ssm.shiro.CustomFormAuthenticationFilter ">
	<!-- 表单中账号的input名称 -->
	<property name="usernameParam" value="username" />
	<!-- 表单中密码的input名称 -->
	<property name="passwordParam" value="password" />
</bean>


         4、认证:

         实现原理:由上边的配置可知,当用户进行认证时,会去请求loginurl进行认证,中间FormAuthenticationFilter进行拦截,取出request中携带的username和password参数(注:这里默认为username和password,也可以自行配置),FormAuthenticationFilter会调用realm(自定义的吗?)进行认证,同时传给realm一个token参数,username和password封装在token中,realm会根据username查询用户信息,如果查询到则返回,如果查询不到则会返回null,这时FormAuthenticationFilter会向request中设置异常参数和信息。

        

	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(
			AuthenticationToken token) throws AuthenticationException 
		
		//token中封装了用户输入的用户名和密码,从token中取出用户名
		String userCode = (String) token.getPrincipal();

		//根据用户输入的userCode从数据库查询
		SysUser sysUser = null;
		try 
			sysUser = sysService.findSysUserByUserCode(userCode);
		 catch (Exception e1) 
			e1.printStackTrace();
		
		// 如果查询不到返回null
		if(sysUser==null)
			//此处返回null,ModularRealmAuthenticator抛出UnknownAccountException(用户不存在)异常
			return null; 
		
		//获取用户散列密码
		String password = sysUser.getPassword();
		//获取盐
		String salt = sysUser.getSalt();
		
		ActiveUser activeUser = new ActiveUser();
		activeUser.setUserid(sysUser.getId());
		activeUser.setUsercode(sysUser.getUsercode());
		activeUser.setUsername(sysUser.getUsername());
		
		List<SysPermission> menus  = null;
		try 
			//根据用户id取出菜单
			menus = sysService.findMenuListByUserId(sysUser.getId());
		 catch (Exception e) 
			e.printStackTrace();
		
		//用户菜单设置到activeUser
		activeUser.setMenus(menus);
		//将activeUser设置到simpleAuthenticationInfo返回
		SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(
				activeUser, password,ByteSource.Util.bytes(salt), this.getName());
		return simpleAuthenticationInfo;
	


         5、登出

         对于登出可以不用去实现具体的退出操作,只需要访问一个指定的登出的url,因为中间会由LogoutFilter拦截,同时清除session数据。配置方式见applicationContext-shiro.xml中的logout配置

         6、授权配置

         说明:对于shiro的授权配置有三种方法:

                  1、通过配置文件进行配置。

                  2、通过注解进行配置。

                  3、通过jsp标签进行配置。

         对于配置文件的配置方式见applicationContext-shiro.xml中的授权配置部分。

         实现原理为:在用户请求到/users/deleteUser.action时会被PermissionsAuthorizationFilter拦截,filter根据配置发现需要“item:query”权限,这时PermissionAuthorizationFilter会调用realm从数据库中获取权限信息,如果发现用户拥有该权限会放行,如果发现没有会转到前边配置中指定的refuse.jsp页面。

         7、权限判断

        

	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(
			PrincipalCollection principals) 
		
		//获取身份信息
		ActiveUser activeUser =  (ActiveUser) principals.getPrimaryPrincipal();
		
		List<SysPermission> permissionList = null;
		try 
			//获取用户拥有的权限信息
			permissionList = sysService.findPermissionListByUserId(activeUser.getUserid());
		 catch (Exception e) 
			e.printStackTrace();
		
		List<String> permissions = new ArrayList<String>();
		if(permissionList!=null)
			for(SysPermission sysPermission:permissionList)
				permissions.add(sysPermission.getPercode());
			
		
		
		//返回授权信息,将查询到的权限信息设置到simpleAuthorizationInfo中
		SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
		simpleAuthorizationInfo.addStringPermissions(permissions);
		return simpleAuthorizationInfo;
	


         8、设置凭证匹配器

         由于在数据库中存放的密码是经过加密算法的散列值,所以在shiro也需要对取到的密码进行相同的加密算法才能进行密码比对,这里通过在配置文件中配置的凭证匹配器对加密算法进行配置,对于凭证匹配器的配置见applicationContext-shiro.xml。

         上边对于授权的方式我们使用的是通过配置文件进行实现的,但是这样的缺点在,URL和对应的权限成对配置,这样将造成很大的配置量,相对来说比较麻烦;下边我们说明一下如何通过注解的方式进行配置。

 

整合改进

         注解方式实现授权:

         上边介绍了对于shiro的授权配置有三种方式,上边的方式是通过配置文件的方式实现的,下边介绍一下如何通过注解方式和jsp标签方式进行授权配置。

         开启注解:

         由于注解方式内部使用了spring的AOP方式,所以需要首先开启spring的AOP,同时开启shiro的注解支持。配置如下:

        

	<!-- 开启aop,对类代理 -->
	<aop:config proxy-target-class="true"></aop:config>
	<!-- 开启shiro注解支持 -->
	<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
		<property name="securityManager" ref="securityManager" />
	</bean>


         注解使用:

         注解的使用方式为,在需要进行权限控制的方法上添加相应注解,注解的写法为:

         @RequiresPermissions(“users:delete”),表示执行此方法需要“users:delete”权限。


         Jsp标签实现授权:

         对于通过jsp标签实现授权需要引入shiro的自定义标签

         <%@ taglib uri="http://shiro.apache.org/tags" prefix="shiro" %>

         标签使用:shiro提供的标签很多,这里介绍其中几个:

         1、认证成功后可以显示的内容放在<shiro:authenticated>内。

         2、当用户有指定角色时才显示的内容放在<shiro:hasRole name="角色名"> 内

         3、当用户有指定的权限时才显示的内容放在<shiro:hasPermission name="item:query">内

         在实际应用当中,通过注解和jsp标签的方式相对多些。


         引入缓存:

         对于上边的使用,无论是通过何种方式配置的权限,存在一个较大的问题是,对于权限的判断,每次都需要realm从数据库中查询权限信息进行比对,这样对系统的性能消耗是很大的,为了解决该问题,我们为shiro引入缓存处理,这样就只需要在用户第一次访问时去查询数据库,再后边就可以直接从缓存中取,这里使用的是ehcache。

         1、添加ehcache的jar包。

         2、配置cacheManager,需要将cacheManager注入到securityManager中,对于相关配置见applicationContext-shiro.xml。

         3、创建shiro-ehcache.xml,定义ehcache的使用方案,具体配置如下:

        

<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
	<!--diskStore:缓存数据持久化的目录地址  -->
	<diskStore path="F:\\develop\\ehcache" />
	<defaultCache 
		maxElementsInMemory="1000" 
		maxElementsOnDisk="10000000"
		eternal="false" 
		overflowToDisk="false" 
		diskPersistent="false"
		timeToIdleSeconds="120"
		timeToLiveSeconds="120" 
		diskExpiryThreadIntervalSeconds="120"
		memoryStoreEvictionPolicy="LRU">
	</defaultCache>
</ehcache>

         其他

         shiro提供了在用户退出后缓存的自动清空处理,然而,在用户登录期间对用户的缓存进行更新处理,需要调用realm的clearCached方法对缓存进行清空。

 

总结:

         对于利用shiro对权限的控制和前边基于URL拦截的控制进行对比来说,shiro的控制功能更加强大一些,但是对于框架的依赖性大些,虽然shiro本身是不依赖与其他框架的,但是这里和项目整合还是依赖了spring框架,在使用shiro时如果使用注解的方式进行权限控制会更加方便和直观。这里主要介绍了shiro和项目整合中的基本用法,其中对于认证和授权的流程在后边进行介绍,这些只是shiro的一些基本用法,对于shiro的其他应用特点还有待继续了解和学习,也希望这篇文章可以对你有所帮助吧。

 

以上是关于权限控制方案之——集成shiro的主要内容,如果未能解决你的问题,请参考以下文章

shiro之授权

springboot shiro和freemarkervuejs/element-ui集成之控制按钮权限完全参考手册

前后端分离项目中 springboot 集成 shiro 实现权限控制

权限框架之Shiro详解

shiro授权

springboot mybatisPlus集成shiro实现权限控制