本文是对CSDN开涛大神的shiro教程第十二章——shiro整合spring的一个整理。
Shiro是最近非常流行的安全框架,比spring security要简单一些,轻量一些。但其功能相当强大,不仅提供权限管理、还集成了单点登录、分布式session等等。
下面是shiro和spring的整合流程。
一般来说权限管理系统可以设计5张表,user表,role表,permission表,他们两两之间分别是一对多的关系,即一个用户可以拥有多个角色,一个角色可以拥有多个权限。因此还需建立user_role_relation表和role_permission
表。
SSM整合shiro首先把这五张表的dao层写好。用户量较小时,可以使用spring JDBCTemplate写dao层。
shiro提供了SimpleHash,他是一个加密工具,使用起来非常方便。
private RandomNumberGenerator randomNumberGenerator = new SecureRandomNumberGenerator();
private String algorithmName = "md5";
private int hashIterations = 2;
public void encryptPassword(User user) {
user.setSalt(randomNumberGenerator.nextBytes().toHex());
/*
shiro提供的simpleHash,分别赋值加密算法、密码、盐、加密循环次数。并转为16进制返回。
*/
String newPassword = new SimpleHash(
algorithmName,
user.getPassword(),
//CredentialsSalt在实体类中由username+salt构成
ByteSource.Util.bytes(user.getCredentialsSalt()),
hashIterations).toHex();
user.setPassword(newPassword);
}
SimpleHash构造函数里有五个参数,分别是算法名称(这里使用的MD5),加密数据,盐,加密循环次数。这样我们在service层插入用户时,就可以使用这个encryptPassword方法将用户密码加密后插入DB了。至于service层无非也是调用dao做一些CURD的操作,不做一一阐述了。重点是下面的Realm
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
/**
*@Author hfismyangel@163.com
*@Description:
* 保存登陆成功后的信息
*@Date: 8:46 2017/8/18
* @param principals
*/
String username = (String)principals.getPrimaryPrincipal();
//简单授权信息,保存用户登录状态
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
authorizationInfo.setRoles(userService.findRoles(username));
authorizationInfo.setStringPermissions(userService.findPermissions(username));
return authorizationInfo;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
/**
*@Author hfismyangel@163.com
*@Description:
* 验证是否能登录
*@Date: 8:47 2017/8/18
* @param token
*/
//直接从token里拿用户名
String username = (String)token.getPrincipal();
User user = userService.findByUsername(username);
if(user == null) {
throw new UnknownAccountException();//没找到帐号
}
if(Boolean.TRUE.equals(user.getLocked())) {
throw new LockedAccountException(); //帐号锁定
}
//交给AuthenticatingRealm使用CredentialsMatcher进行密码匹配,如果觉得人家的不好可以自定义实现
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
user.getUsername(), //用户名
user.getPassword(), //密码
ByteSource.Util.bytes(user.getCredentialsSalt()),//salt=username+salt
getName() //realm name
);
return authenticationInfo;
}
这个Realm可以理解为对于是否登录/拥有权限做逻辑判断的一层。首先自定义一个类,继承Shiro的AuthorizingRealm父类。重写doGetAuthorizationInfo和doGetAuthenticationInfo方法即可,注意两个方法互为重载方法。一个是用于保存登陆成功后的信息,一个是用于判断是否登录。
注意第二个doGetAuthorizationInfo方法里调用的token.getPrincipal()方法,是直接从token中拿到用户名,而第一个doGetAuthorizationInfo方法里token.getPrincipal()是从PrincipalCollection通过遍历器拿到第一个用户名,PrincipalCollection 是个身份集合,保存各种身份信息,具体实现可以看源码。
最简单的代码配置就到此为止,再配置一下spring-shiro.xml的配置文件,在web.xml里配置一个shiro的过滤器就可以进行测试了。
下面是shiro与srpring整合的配置文件,原文注释写的比较清晰,不作解释了。另外最好根据这个shiro流程图来理解。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:util="http://www.springframework.org/schema/util"
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
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
<!--dao层、passwordHelper类、与shiro-web无关-->
<!-- 缓存管理器 使用Ehcache实现 -->
<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
<property name="cacheManagerConfigFile" value="classpath:ehcache.xml"/>
</bean>
<!-- 凭证匹配器 ,用于管理密码重试次数-->
<bean id="credentialsMatcher"
class="com.github.zhangkaitao.shiro.chapter12.credentials.RetryLimitHashedCredentialsMatcher">
<constructor-arg ref="cacheManager"/>
<property name="hashAlgorithmName" value="md5"/>
<property name="hashIterations" value="2"/>
<property name="storedCredentialsHexEncoded" value="true"/>
</bean>
<!-- Realm实现,包含两部分AuthenticationToken和AuthenticationInfo-->
<bean id="userRealm" class="com.github.zhangkaitao.shiro.chapter12.realm.UserRealm">
<property name="userService" ref="userService"/>
<property name="credentialsMatcher" ref="credentialsMatcher"/>
<property name="cachingEnabled" value="true"/>
<property name="authenticationCachingEnabled" value="true"/>
<property name="authenticationCacheName" value="authenticationCache"/>
<property name="authorizationCachingEnabled" value="true"/>
<property name="authorizationCacheName" value="authorizationCache"/>
</bean>
<!-- 会话ID生成器,指定使用uuid生成-->
<bean id="sessionIdGenerator" class="org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator"/>
<!-- 会话Cookie模板 -->
<bean id="sessionIdCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
<constructor-arg value="sid"/>
<property name="httpOnly" value="true"/>
<property name="maxAge" value="180000"/>
</bean>
<!-- 会话DAO,管理session。
shiro的session都是存在缓存中的,所有会有一个sessionDAO的类EnterpriseCacheSessionDAO来做CRUD操作-->
<bean id="sessionDAO" class="org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO">
<property name="activeSessionsCacheName" value="shiro-activeSessionCache"/>
<property name="sessionIdGenerator" ref="sessionIdGenerator"/>
</bean>
<!-- 会话验证调度器 -->
<!--shiro提供了会话验证调度器,用于定期的验证会话是否已过期,如果过期将停止会话;出于性能考虑,
一般情况下都是获取会话时来验证会话是否过期并停止会话的;但是如在web环境中,如果用户不主动退出是不知道会话是否过期的,
因此需要定期的检测会话是否过期,Shiro提供了会话验证调度器SessionValidationScheduler。-->
<bean id="sessionValidationScheduler" class="org.apache.shiro.session.mgt.quartz.QuartzSessionValidationScheduler">
<!--设置调度时间间隔,单位毫秒,默认是1小时-->
<property name="sessionValidationInterval" value="1800000"/>
<property name="sessionManager" ref="sessionManager"/>
</bean>
<!--==============================================================================================================-->
<!-- 会话管理器 -->
<bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
<property name="globalSessionTimeout" value="1800000"/>
<property name="deleteInvalidSessions" value="true"/>
<property name="sessionValidationSchedulerEnabled" value="true"/>
<property name="sessionValidationScheduler" ref="sessionValidationScheduler"/>
<property name="sessionDAO" ref="sessionDAO"/>
<property name="sessionIdCookieEnabled" value="true"/>
<!--存储sessionID的jsessionId-->
<property name="sessionIdCookie" ref="sessionIdCookie"/>
</bean>
<!-- 安全管理器,安全框架的核心组件,统筹realm/sessionManager/cache-->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="userRealm"/>
<property name="sessionManager" ref="sessionManager"/>
<property name="cacheManager" ref="cacheManager"/>
</bean>
<!-- 通过MethodInvokingFactoryBean工厂Bean,可以将指定方法返回值注入成为目标Bean的属性值。
将SecurityUtils的SecurityManager赋值-->
<bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="staticMethod" value="org.apache.shiro.SecurityUtils.setSecurityManager"/>
<property name="arguments" ref="securityManager"/>
</bean>
<!-- Shiro生命周期处理器-->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
<!--==============================================================================================================-->
<!-- 基于Form表单的身份验证过滤器 -->
<bean id="formAuthenticationFilter" class="org.apache.shiro.web.filter.authc.FormAuthenticationFilter">
<property name="usernameParam" value="username"/>
<property name="passwordParam" value="password"/>
<property name="loginUrl" value="/login.jsp"/>
</bean>
<!-- Shiro的Web过滤器 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"/>
<property name="loginUrl" value="/login.jsp"/>
<property name="unauthorizedUrl" value="/unauthorized.jsp"/>
<property name="filters">
<util:map>
<!--拦截表单,通过认证才能登录-->
<entry key="authc" value-ref="formAuthenticationFilter"/>
</util:map>
</property>
<property name="filterChainDefinitions">
<!--
①[urls]部分的配置,其格式为:url=拦截器[参数],拦截器[参数]。
②如果当前请求的url匹配[urls]部分的某个url模式,将会执行器配置的拦截器。
③anon(anonymous)拦截器表示可以匿名访问。
④authc(authencation)拦截器标识需要身份认证通过后才能访问。
⑤logout(logout)拦截器表示登出/退出登录,它会清空shiro缓存信息。
⑥url模式使用 Ant 风格模式:
[1]?匹配一个字符;
[2]*匹配零个或多个字符;
[3]**匹配路径中的零个或多个路径;-->
<value>
/index.jsp = anon
/unauthorized.jsp = anon
/login.jsp = authc
/logout = logout
/** = user
</value>
</property>
</bean>
</beans>