shiro + ssm的开发步骤
前言
ssm的基本搭建就不多说了,主要讲shiro的基本配置。
pom配置
这次项目采用的是maven管理,所以首先在pom.xml中引入。
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-all</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<artifactId>ehcache-core</artifactId>
<groupId>net.sf.ehcache</groupId>
<version>2.5.0</version>
</dependency>
web.xml配置
web.xml中配置filter,有多个filter就放在最前面。
shiro中定义的filtername bean是要被wen.xml引用的,所以这里的filtername要和shiro中的filtername相同 。
<!-- Shiro配置 -->
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<init-param>
<param-name>targetFilterLifecycle</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
ehcache配置
创建个xml,在里面复制上
<?xml version="1.0" encoding="UTF-8"?>
<ehcache updateCheck="false" name="shirocache">
<!--
name:缓存名称。
maxElementsInMemory:缓存最大个数。
eternal:对象是否永久有效,一但设置了,timeout将不起作用。
timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。
timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。
overflowToDisk:当内存中对象数量达到maxElementsInMemory时,Ehcache将会对象写到磁盘中。
diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。
maxElementsOnDisk:硬盘最大缓存个数。
diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。
memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。
clearOnFlush:内存数量最大时是否清除。
-->
<diskStore path="F:\fileUpload\cache"/>
<defaultCache
maxElementsInMemory="10000"
maxElementsOnDisk="0"
eternal="true"
overflowToDisk="true"
diskPersistent="false"
timeToIdleSeconds="0"
timeToLiveSeconds="0"
diskSpoolBufferSizeMB="50"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LFU"
/>
<!-- 登录记录缓存 锁定10分钟 -->
<cache name="passwordRetryCache"
maxEntriesLocalHeap="2000"
eternal="false"
timeToIdleSeconds="3600"
timeToLiveSeconds="0"
overflowToDisk="false"
statistics="true">
</cache>
<cache name="authorizationCache"
maxEntriesLocalHeap="2000"
eternal="false"
timeToIdleSeconds="3600"
timeToLiveSeconds="0"
overflowToDisk="false"
statistics="true">
</cache>
<cache name="authenticationCache"
maxEntriesLocalHeap="2000"
eternal="false"
timeToIdleSeconds="3600"
timeToLiveSeconds="0"
overflowToDisk="false"
statistics="true">
</cache>
<cache name="shiro-activeSessionCache"
maxEntriesLocalHeap="2000"
eternal="false"
timeToIdleSeconds="3600"
timeToLiveSeconds="0"
overflowToDisk="false"
statistics="true">
</cache>
<cache name="shiro_cache"
maxElementsInMemory="2000"
maxEntriesLocalHeap="2000"
eternal="false"
timeToIdleSeconds="0"
timeToLiveSeconds="0"
maxElementsOnDisk="0"
overflowToDisk="true"
memoryStoreEvictionPolicy="FIFO"
statistics="true">
</cache>
</ehcache>
关于ehcache的属性介绍,可以看这篇博客Shiro + EHCache 缓存的使用
自定义的加密算法
public class CustomCredentialsMatcher extends SimpleCredentialsMatcher {
//密码比较的方法 token代表用户在界面输入的用户名和密码 info代表从数据库中得到加密数据
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
//1.向下转型
UsernamePasswordToken upToken = (UsernamePasswordToken) token;
//2.将用户在界面输入的原始密码加密
Object pwd = Encrypt.md5(new String(upToken.getPassword()), upToken.getUsername());
//3.取出数据库中加密的密码
Object dbPwd = info.getCredentials();
return this.equals(pwd, dbPwd);
}
UsernamePasswordToken是shiro中自己的类,是在执行登陆方法是调用的
Subject subject = SecurityUtils.getSubject();
//2.调用登录方法
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
subject.login(token);//当这一代码执行时,就会自动跳入到AuthRealm中认证方法
或者可以自己继承UsernamePasswordToken类
public class ShiroToken extends UsernamePasswordToken implements Serializable{
private static final long serialVersionUID = 1L;
private String username;
private String pwd;
private String corpUid; //业务的需求,
....
Encrypt为工具类,对密码加密
public class Encrypt {
/*
* 散列算法一般用于生成数据的摘要信息,是一种不可逆的算法,一般适合存储密码之类的数据,
* 常见的散列算法如MD5、SHA等。一般进行散列时最好提供一个salt(盐),比如加密密码“admin”,
* 产生的散列值是“21232f297a57a5a743894a0e4a801fc3”,
* 可以到一些md5解密网站很容易的通过散列值得到密码“admin”,
* 即如果直接对密码进行散列相对来说破解更容易,此时我们可以加一些只有系统知道的干扰数据,
* 如用户名和ID(即盐);这样散列的对象是“密码+用户名+ID”,这样生成的散列值相对来说更难破解。
*/
//高强度加密算法,不可逆
public static String md5(String password, String salt){
return new Md5Hash(password,salt,2).toString();
}
}
自定义的Realm类
需要继承AuthorizingRealm实现接口
这里登陆认证,只根据用户名查询,因为我们自己写了一个加密算法,这里从token取到的密码还不是加密后,当它
返回时会跳转到我们写的CustomCredentialsMatcher 方法进行验证
在自定义Realm配置一定要开启缓存否会无效
public class SampleRealm extends AuthorizingRealm {
@Autowired
UUserService userService;
@Autowired
PermissionService permissionService;
@Autowired
RoleService roleService;
public SampleRealm() {
super();
}
/**
* 认证信息,主要针对用户登录,
*/
protected AuthenticationInfo doGetAuthenticationInfo(
AuthenticationToken authcToken) throws AuthenticationException {
UsernamePasswordToken token = (UsernamePasswordToken) authcToken;
//这里根据用户名查询用户是否存在
User user = userService.queryForOne(token.getUsername());
if(null == user){
throw new AccountException("帐号或密码不正确!");
/**
* 如果用户的status为禁用。那么就抛出<code>DisabledAccountException</code>
*/
}else if(UUser._0.equals(user.getStatus())){
throw new DisabledAccountException("帐号已经禁止登录!");
}else{
//更新登录时间 last login time
user.setLastLoginTime(new Date());
userService.updateByPrimaryKeySelective(user);
}
return new SimpleAuthenticationInfo(user,user.getPswd(), getName());
}
/**
* 授权
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
User user = (User)SecurityUtils.getSubject().getPrincipal();
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//根据用户ID查询角色(role),放入到Authorization里。
Set<String> roles = roleService.findRoleByUserId(user.getUserId());
info.setRoles(roles);
//根据用户ID查询权限(permission),放入到Authorization里。
Set<String> permissions = permissionService.findPermissionByUserId(user.getUserId());
info.setStringPermissions(permissions);
return info;
}
spring-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"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<description>Shiro的配置</description>
<!-- SecurityManager配置 -->
<!-- 配置Realm域 -->
<!-- 密码比较器 -->
<!-- 代理如何生成? 用工厂来生成Shiro的相关过滤器-->
<!-- 配置缓存:ehcache缓存 -->
<!-- 安全管理 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!-- Single realm app. If you have multiple realms, use the 'realms' property instead. -->
<property name="realm" ref="authRealm"/><!-- 引用自定义的realm -->
<!-- 缓存 -->
<!--<property name="cacheManager" ref="shiroEhcacheManager"/>-->
</bean>
<!-- 自定义权限认证 -->
<bean id="authRealm" class="club.thunderstorm.common.shiro.AuthRealm">
<!-- 自定义密码加密算法 -->
<property name="credentialsMatcher" ref="passwordMatcher"/>
<!--缓存开启-->
<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>
<!-- 设置密码加密策略 md5hash -->
<bean id="passwordMatcher" class="club.thunderstorm.common.shiro.CustomCredentialsMatcher"/>
<!-- filter-name这个名字的值来自于web.xml中filter的名字 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"/>
<!--登录页面 -->
<property name="loginUrl" value="/login"></property>
<!-- 登录成功后 -->
<!--<property name="successUrl" value="/home.action"></property>-->
<property name="filterChainDefinitions">
<!-- /**代表下面的多级目录也过滤 -->
<value>
/static/**=anon
/login* = anon
/** = authc
/*.* = authc
</value>
</property>
</bean>
<!-- 用户授权/认证信息Cache, 采用EhCache 缓存 -->
<bean id="shiroEhcacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
<property name="cacheManagerConfigFile" value="classpath:ehcache-shiro.xml"/>
</bean>
<!-- 保证实现了Shiro内部lifecycle函数的bean执行 -->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
<!-- 生成代理,通过代理进行控制 -->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
depends-on="lifecycleBeanPostProcessor">
<property name="proxyTargetClass" value="true"/>
</bean>
<!-- 安全管理器 -->
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager"/>
</bean>
</beans>
controller登陆方法
//帐号密码封装到这个两个属性中
public Map login(@RequestParam String email,@RequestParam String pwd){
//调用shiro判断当前用户是否是系统用户
Subject subject = SecurityUtils.getSubject(); //得到当前用户
//shiro是将用户录入的登录名和密码(未加密)封装到token对象中
UsernamePasswordToken token = new UsernamePasswordToken(email, pwd);
try {
//自动调用AuthRealm.doGetAuthenticationInfo
subject.login(token);
//登陆成功把用户放到session中,否则报异常
User user =(User)subject.getPrincipal();
subject.getSession().setAttribute("user",user);
} catch (AuthenticationException e) {
....
}
....
结束
最后还有一个很详细的shiro教程推荐Shiro 教程系列