抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

SpringBoot整合Shiro(三)

整合shiro-ehcache缓存

我们最后有两个问题,其中关于无权限页面的问题已经解决了,还有一个问题就是每次访问服务,都会去查询数据库,真实的情况是权限并不会经常出现变化,所以最好的办法就是做缓存处理,如果只是公司后台运营管理系统,可能只启动一个单节点就够了,这个时候我们就可以用到ehcache缓存。

前置工作

修改RoleService

增加delPermission,addPermission 方法

1
2
3
4
5
6
7
8
public interface RoleService {

Set<Role> findRolesByUserId(Integer uid);

void delPermission(int roleId, int permissionId);

void addPermission(int i, int i1);
}
RoleServiceImpl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Service
public class RoleServiceImpl implements RoleService {

@Autowired
private RoleMapper roleMapper;

@Override
public Set<Role> findRolesByUserId(Integer uid) {
return roleMapper.findRolesByUserId(uid);
}

@Override
public void delPermission(int roleId, int permissionId) {
roleMapper.delPermission(roleId,permissionId);
}

@Override
public void addPermission(int roleId, int permissionId) {
roleMapper.addPermission(roleId,permissionId);
}
}
修改RoleMapper
1
2
3
4
5
6
7
8
9
@Mapper
public interface RoleMapper {
Set<Role> findRolesByUserId(@Param("uid") Integer uid);

void delPermission(@Param("roleId") Integer roleId, @Param("permissionId") Integer permissionId);

void addPermission(@Param("roleId") Integer roleId, @Param("permissionId") Integer permissionId);
}

RoleMapper.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.demo.mapper.RoleMapper">

<!-- 查询用户信息 -->
<select id="findRolesByUserId" resultType="com.demo.entity.Role">
SELECT r.* from sys_role r LEFT JOIN sys_user_role ur on r.id = ur.role_id where ur.uid = #{uid}
</select>

<insert id="addPermission">
INSERT INTO sys_role_permission (role_id, permission_id)
VALUES
(#{roleId},#{permissionId})
</insert>

<delete id="delPermission">
DELETE FROM sys_role_permission WHERE role_id=#{roleId} and permission_id=#{permissionId}
</delete>

</mapper>

pom添加依赖

1
2
3
4
5
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.5.2</version>
</dependency>

修改ShiroConfig

在ShiroConfig中添加缓存管理器

添加shiro缓存管理器
1
2
3
4
5
6
7
8
9
10
11
12
/**
* shiro缓存管理器;
* 需要添加到securityManager中
*
* @return
*/
@Bean
public EhCacheManager ehCacheManager() {
EhCacheManager cacheManager = new EhCacheManager();
cacheManager.setCacheManagerConfigFile("classpath:config/ehcache-shiro.xml");
return cacheManager;
}
添加Spring静态注入
1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 让某个实例的某个方法的返回值注入为Bean的实例
* Spring静态注入
*
* @return
*/
@Bean
public MethodInvokingFactoryBean getMethodInvokingFactoryBean() {
MethodInvokingFactoryBean factoryBean = new MethodInvokingFactoryBean();
factoryBean.setStaticMethod("org.apache.shiro.SecurityUtils.setSecurityManager");
factoryBean.setArguments(new Object[]{securityManager()});
return factoryBean;
}
修改安全事务管理器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 配置核心安全事务管理器
*
* @return
*/
@Bean(name = "securityManager")
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//设置自定义realm.
securityManager.setRealm(shiroRealm());
//配置记住我
securityManager.setRememberMeManager(rememberMeManager());

//配置 ehcache缓存管理器
securityManager.setCacheManager(ehCacheManager());

//配置自定义session管理,使用redis
//securityManager.setSessionManager(sessionManager());

return securityManager;
}
重写shiroRealm方法

重写shiroRealm开启Ehcache缓存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* 身份认证realm; (这个需要自己写,账号密码校验;权限等)
*
* @return
*/
@Bean
public ShiroRealm shiroRealm() {
ShiroRealm shiroRealm = new ShiroRealm();
shiroRealm.setCachingEnabled(true);
//启用身份验证缓存,即缓存AuthenticationInfo信息,默认false
shiroRealm.setAuthenticationCachingEnabled(true);
//缓存AuthenticationInfo信息的缓存名称 在ehcache-shiro.xml中有对应缓存的配置
shiroRealm.setAuthenticationCacheName("authenticationCache");
//启用授权缓存,即缓存AuthorizationInfo信息,默认false
shiroRealm.setAuthorizationCachingEnabled(true);
//缓存AuthorizationInfo信息的缓存名称 在ehcache-shiro.xml中有对应缓存的配置
shiroRealm.setAuthorizationCacheName("authorizationCache");

return shiroRealm;
}

配置ehcache配置文件

在config下添加ehcache-shiro.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
<?xml version="1.0" encoding="UTF-8"?>
<ehcache name="es">

<!--
缓存对象存放路径
java.io.tmpdir:默认的临时文件存放路径。
user.home:用户的主目录。
user.dir:用户的当前工作目录,即当前程序所对应的工作路径。
其它通过命令行指定的系统属性,如“java –DdiskStore.path=D:\\abc ……”。
-->
<diskStore path="java.io.tmpdir"/>

<!--

name:缓存名称。
maxElementsOnDisk:硬盘最大缓存个数。0表示不限制
maxEntriesLocalHeap:指定允许在内存中存放元素的最大数量,0表示不限制。
maxBytesLocalDisk:指定当前缓存能够使用的硬盘的最大字节数,其值可以是数字加单位,单位可以是K、M或者G,不区分大小写,
如:30G。当在CacheManager级别指定了该属性后,Cache级别也可以用百分比来表示,
如:60%,表示最多使用CacheManager级别指定硬盘容量的60%。该属性也可以在运行期指定。当指定了该属性后会隐式的使当前Cache的overflowToDisk为true。
maxEntriesInCache:指定缓存中允许存放元素的最大数量。这个属性也可以在运行期动态修改。但是这个属性只对Terracotta分布式缓存有用。
maxBytesLocalHeap:指定当前缓存能够使用的堆内存的最大字节数,其值的设置规则跟maxBytesLocalDisk是一样的。
maxBytesLocalOffHeap:指定当前Cache允许使用的非堆内存的最大字节数。当指定了该属性后,会使当前Cache的overflowToOffHeap的值变为true,
如果我们需要关闭overflowToOffHeap,那么我们需要显示的指定overflowToOffHeap的值为false。
overflowToDisk:boolean类型,默认为false。当内存里面的缓存已经达到预设的上限时是否允许将按驱除策略驱除的元素保存在硬盘上,默认是LRU(最近最少使用)。
当指定为false的时候表示缓存信息不会保存到磁盘上,只会保存在内存中。
该属性现在已经废弃,推荐使用cache元素的子元素persistence来代替,如:<persistence strategy=”localTempSwap”/>。
diskSpoolBufferSizeMB:当往磁盘上写入缓存信息时缓冲区的大小,单位是MB,默认是30。
overflowToOffHeap:boolean类型,默认为false。表示是否允许Cache使用非堆内存进行存储,非堆内存是不受Java GC影响的。该属性只对企业版Ehcache有用。
copyOnRead:当指定该属性为true时,我们在从Cache中读数据时取到的是Cache中对应元素的一个copy副本,而不是对应的一个引用。默认为false。
copyOnWrite:当指定该属性为true时,我们在往Cache中写入数据时用的是原对象的一个copy副本,而不是对应的一个引用。默认为false。
timeToIdleSeconds:单位是秒,表示一个元素所允许闲置的最大时间,也就是说一个元素在不被请求的情况下允许在缓存中待的最大时间。默认是0,表示不限制。
timeToLiveSeconds:单位是秒,表示无论一个元素闲置与否,其允许在Cache中存在的最大时间。默认是0,表示不限制。
eternal:boolean类型,表示是否永恒,默认为false。如果设为true,将忽略timeToIdleSeconds和timeToLiveSeconds,Cache内的元素永远都不会过期,也就不会因为元素的过期而被清除了。
diskExpiryThreadIntervalSeconds :单位是秒,表示多久检查元素是否过期的线程多久运行一次,默认是120秒。
clearOnFlush:boolean类型。表示在调用Cache的flush方法时是否要清空MemoryStore。默认为true。
diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
maxElementsInMemory:缓存最大数目
memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。
memoryStoreEvictionPolicy:
Ehcache的三种清空策略;
FIFO,first in first out,这个是大家最熟的,先进先出。
LFU, Less Frequently Used,就是上面例子中使用的策略,直白一点就是讲一直以来最少被使用的。如上面所讲,缓存的元素有一个hit属性,hit值最小的将会被清出缓存。
LRU,Least Recently Used,最近最少使用的,缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。
-->
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="0"
timeToLiveSeconds="0"
overflowToDisk="false"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"
/>

<!-- 授权缓存 -->
<cache name="authorizationCache"
maxEntriesLocalHeap="2000"
eternal="false"
timeToIdleSeconds="0"
timeToLiveSeconds="0"
overflowToDisk="false"
statistics="true">
</cache>

<!-- 认证缓存 -->
<cache name="authenticationCache"
maxEntriesLocalHeap="2000"
eternal="false"
timeToIdleSeconds="0"
timeToLiveSeconds="0"
overflowToDisk="false"
statistics="true">
</cache>

</ehcache>

在ShiroRealm的doGetAuthorizationInfo方法中添加日志,或者直接看控制台sql打印也行。
启动项目,使用admin登录,访问http://localhost:8080/userInfo/view 第一次访问,查看控制台,有sql查询,再次访问将不再打印查询数据库的日志,证明缓存生效了。上面的缓存配置时间配置为永久,请根据需求自己更改值来进行测试。
关于在ShiroRealm中对 用户信息 角色信息 权限信息的查询,如果需要添加缓存 请自行处理。

缓存更新

如果用户的权限发生改变怎么办

​ 上面已经启用了缓存,第一次请求走数据库查询,后续请求将直接查询ehcache缓存,假如这个时候在权限控制台分配了某个权限给某个角色,那么拥有这个角色的所有用户在下次请求之前都需要从数据库查询最新的权限信息。下面开始进行在权限发生改变时,该如何做:

清理缓存

在ShiroRealm类中添加以下方法(清理缓存)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
public class ShiroRealm extends AuthorizingRealm {

@Autowired
private UserMapper userMapper;

@Autowired
private RoleMapper roleMapper;

@Autowired
private PermissionMapper permissionMapper;

...

/**
* 重写方法,清除当前用户的的 授权缓存
* @param principals
*/
@Override
public void clearCachedAuthorizationInfo(PrincipalCollection principals) {
super.clearCachedAuthorizationInfo(principals);
}

/**
* 重写方法,清除当前用户的 认证缓存
* @param principals
*/
@Override
public void clearCachedAuthenticationInfo(PrincipalCollection principals) {
super.clearCachedAuthenticationInfo(principals);
}

@Override
public void clearCache(PrincipalCollection principals) {
super.clearCache(principals);
}

/**
* 自定义方法:清除所有 授权缓存
*/
public void clearAllCachedAuthorizationInfo() {
getAuthorizationCache().clear();
}

/**
* 自定义方法:清除所有 认证缓存
*/
public void clearAllCachedAuthenticationInfo() {
getAuthenticationCache().clear();
}

/**
* 自定义方法:清除所有的 认证缓存 和 授权缓存
*/
public void clearAllCache() {
clearAllCachedAuthenticationInfo();
clearAllCachedAuthorizationInfo();
}
}

修改UserController

在UserController中添加下面两个方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
@RestController
@RequestMapping("userInfo")
public class UserController {

@Autowired
private UserService userService;

@Autowired
private RoleService roleService;

.....

/**
* 给admin用户添加 userInfo:del 权限
* @param model
* @return
*/
@RequestMapping(value = "/addPermission",method = RequestMethod.GET)
@ResponseBody
public String addPermission(Model model) {

//在sys_role_permission 表中 将 删除的权限 关联到admin用户所在的角色
roleService.addPermission(1,3);

//添加成功之后 清除缓存
DefaultWebSecurityManager securityManager = (DefaultWebSecurityManager)SecurityUtils.getSecurityManager();
ShiroRealm shiroRealm = (ShiroRealm) securityManager.getRealms().iterator().next();
//清除权限 相关的缓存
shiroRealm.clearAllCache();
return "给admin用户添加 userInfo:del 权限成功";

}

/**
* 删除admin用户 userInfo:del 权限
* @param model
* @return
*/
@RequestMapping(value = "/delPermission",method = RequestMethod.GET)
@ResponseBody
public String delPermission(Model model) {

//在sys_role_permission 表中 将 删除的权限 关联到admin用户所在的角色
roleService.delPermission(1,3);
//添加成功之后 清除缓存
DefaultWebSecurityManager securityManager = (DefaultWebSecurityManager) SecurityUtils.getSecurityManager();
ShiroRealm shiroRealm = (ShiroRealm) securityManager.getRealms().iterator().next();
//清除权限 相关的缓存
shiroRealm.clearAllCache();
return "删除admin用户userInfo:del 权限成功";
}

}

注意:在添加权限 或者 删除权限之后 都有调用shiroRealm.clearAllCache();来清除所有的缓存。

测试步骤:

  1. 两个浏览器: 一个谷歌浏览器登录 admin账户,另一个360浏览器登录 test账户,每个账户登录跳转到idnex页面之后 ,刷新两下,发现之后无论怎么刷新都不再打印查询数据库的sql

  2. 在谷歌浏览器地址调用添加权限的方法http://localhost:8080/userInfo/addPermission 显示添加完成,然后再次访问http://localhost:8080/index 页面上已经显示刚添加的权限,查看日志发现走数据库查询了最新的权限信息

  3. 这个时候在360浏览器刷新http://localhost:8080/index 查看控制台日志发现test的用户也有走数据库查询权限信息

shiro会话管理

​ Shiro提供了完整的企业级会话管理功能,不依赖于底层容器(如Tomcat),不管是J2SE还是J2EE环境都可以使用,提供了会话管理,会话事件监听,会话存储/持久化,容器无关的集群,失效/过期支持,对Web的透明支持,SSO单点登录的支持等特性。即直接使用 Shiro 的会话管理可以直接替换如 Web 容器的会话管理。

shiro中的session特性

  • 基于POJO/J2SE:shiro中session相关的类都是基于接口实现的简单的java对象(POJO),兼容所有java对象的配置方式,扩展也更方便,完全可以定制自己的会话管理功能 。
  • 简单灵活的会话存储/持久化:因为shiro中的session对象是基于简单的java对象的,所以你可以将session存储在任何地方,例如,文件,各种数据库,内存中等。
  • 容器无关的集群功能:shiro中的session可以很容易的集成第三方的缓存产品完成集群的功能。例如,Ehcache + Terracotta, Coherence, GigaSpaces等。你可以很容易的实现会话集群而无需关注底层的容器实现。
  • 异构客户端的访问:可以实现web中的session和非web项目中的session共享。
  • 会话事件监听:提供对对session整个生命周期的监听。
  • 保存主机地址:在会话开始session会存用户的ip地址和主机名,以此可以判断用户的位置。
  • 会话失效/过期的支持:用户长时间处于不活跃状态可以使会话过期,调用touch()方法,可以主动更新最后访问时间,让会话处于活跃状态。
  • 透明的Web支持:shiro全面支持Servlet 2.5中的session规范。这意味着你可以将你现有的web程序改为shiro会话,而无需修改代码。
  • 单点登录的支持:shiro session基于普通java对象,使得它更容易存储和共享,可以实现跨应用程序共享。可以根据共享的会话,来保证认证状态到另一个程序。从而实现单点登录。

会话相关API

获取Session的方式
1
2
Subject subject = SecurityUtils.getSubject();
Session session = subject.getSession();

与web中的 HttpServletRequest.getSession(boolean create) 类似!
Subject.getSession(true)。即如果当前没有创建session对象会创建一个;
Subject.getSession(false),如果当前没有创建session对象则返回null。

Session相关API
返回值 方法名 描述
Object getAttribute(Object key) 根据key标识返回绑定到session的对象
Collection getAttributeKeys() 获取在session中存储的所有的key
String getHost() 获取当前主机ip地址,如果未知,返回null
Serializable getId() 获取session的唯一id
Date getLastAccessTime() 获取最后的访问时间
Date getStartTimestamp() 获取session的启动时间
long getTimeout() 获取session失效时间,单位毫秒
void setTimeout(long maxIdleTimeInMillis) 设置session的失效时间
Object removeAttribute(Object key) 通过key移除session中绑定的对象
void setAttribute(Object key, Object value) 设置session会话属性
void stop() 销毁会话
void touch() 更新会话最后访问时间

会话管理器

会话管理器管理着应用中所有Subject的会话的创建、维护、删除、失效、验证等工作。是Shiro的核心组件,顶层组件SecurityManager直接继承了SessionManager,且提供了SessionsSecurityManager实现直接把会话管理委托给相应的SessionManager,DefaultSecurityManager及DefaultWebSecurityManager默认SecurityManager都继承了SessionsSecurityManager。

SecurityManager提供了如下接口:

  • Session start(SessionContext context); //启动会话

  • Session getSession(SessionKey key) throws SessionException;//根据会话Key获取会话

另外用于Web环境的WebSessionManager又提供了如下接口:

  • boolean isServletContainerSessions();//是否使用Servlet容器的会话

Shiro还提供了ValidatingSessionManager用于验资并过期会话:

  • void validateSessions();//验证所有会话是否过期
Shiro提供了三个默认实现

DefaultSessionManager

​ DefaultSecurityManager使用的默认实现,用于JavaSE环境;

ServletContainerSessionManager

​ DefaultWebSecurityManager使用的默认实现,用于Web环境,其直接使用Servlet容器的会话;

DefaultWebSessionManager

​ 用于Web环境的实现,可以替代ServletContainerSessionManager,自己维护着会话,直接废弃了Servlet容器的会话管理。

shiro配置会话管理配置

SessionListener

创建session监听类,实现SessionListener接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
public class ShiroSessionListener implements SessionListener {

/**
* 统计在线人数
* juc包下线程安全自增
*/
private final AtomicInteger sessionCount = new AtomicInteger(0);

/**
* 会话创建时触发
*
* @param session
*/
@Override
public void onStart(Session session) {
//会话创建,在线人数加一
sessionCount.incrementAndGet();
}

/**
* 退出会话时触发
*
* @param session
*/
@Override
public void onStop(Session session) {
//会话退出,在线人数减一
sessionCount.decrementAndGet();
}

/**
* 会话过期时触发
*
* @param session
*/
@Override
public void onExpiration(Session session) {
//会话过期,在线人数减一
sessionCount.decrementAndGet();
}

/**
* 获取在线人数使用
*
* @return
*/
public AtomicInteger getSessionCount() {
return sessionCount;
}
}

配置ShiroConfig

配置session监听
1
2
3
4
5
6
7
8
9
10
/**
* 配置session监听
*
* @return
*/
@Bean("sessionListener")
public ShiroSessionListener sessionListener() {
ShiroSessionListener sessionListener = new ShiroSessionListener();
return sessionListener;
}
配置会话ID生成器
1
2
3
4
5
6
7
8
9
/**
* 配置会话ID生成器
*
* @return
*/
@Bean
public SessionIdGenerator sessionIdGenerator() {
return new JavaUuidSessionIdGenerator();
}
配置sessionDAO

SessionDAO的作用是为Session提供CRUD并进行持久化的一个shiro组件

  • MemorySessionDAO 直接在内存中进行会话维护
  • EnterpriseCacheSessionDAO 提供了缓存功能的会话维护,默认情况下使用MapCache实现,内部使用ConcurrentHashMap保存缓存的会话。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* SessionDAO的作用是为Session提供CRUD并进行持久化的一个shiro组件
* MemorySessionDAO 直接在内存中进行会话维护
* EnterpriseCacheSessionDAO 提供了缓存功能的会话维护,默认情况下使用MapCache实现,内部使用ConcurrentHashMap保存缓存的会话。
*
* @return
*/
@Bean
public SessionDAO sessionDAO() {
EnterpriseCacheSessionDAO enterpriseCacheSessionDAO = new EnterpriseCacheSessionDAO();
//使用ehCacheManager
enterpriseCacheSessionDAO.setCacheManager(ehCacheManager());
//设置session缓存的名字 默认为 shiro-activeSessionCache
enterpriseCacheSessionDAO.setActiveSessionsCacheName("shiro-activeSessionCache");
//sessionId生成器
enterpriseCacheSessionDAO.setSessionIdGenerator(sessionIdGenerator());
return enterpriseCacheSessionDAO;
}
配置sessionId的Cookie
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 配置保存sessionId的cookie
* 注意:这里的cookie 不是上面的记住我 cookie 记住我需要一个cookie session管理 也需要自己的cookie
*
* @return
*/
@Bean("sessionIdCookie")
public SimpleCookie sessionIdCookie() {
//这个参数是cookie的名称
SimpleCookie simpleCookie = new SimpleCookie("session_id");
//setcookie的httponly属性如果设为true的话,会增加对xss防护的安全系数。它有以下特点:

//setcookie()的第七个参数
//设为true后,只能通过http访问,javascript无法访问
//防止xss读取cookie
simpleCookie.setHttpOnly(true);
simpleCookie.setPath("/");
//maxAge=-1表示浏览器关闭时失效此Cookie
simpleCookie.setMaxAge(-1);
return simpleCookie;
}
配置session管理器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/**
* 配置会话管理器,设定会话超时及保存
* @return
*/
@Bean("sessionManager")
public SessionManager sessionManager() {

DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
Collection<SessionListener> listeners = new ArrayList<SessionListener>();
//配置监听
listeners.add(sessionListener());
sessionManager.setSessionListeners(listeners);
sessionManager.setSessionIdCookie(sessionIdCookie());
sessionManager.setSessionDAO(sessionDAO());
sessionManager.setCacheManager(ehCacheManager());

//全局会话超时时间(单位毫秒),默认30分钟 暂时设置为10秒钟 用来测试
sessionManager.setGlobalSessionTimeout(1800000);
//是否开启删除无效的session对象 默认为true
sessionManager.setDeleteInvalidSessions(true);
//是否开启定时调度器进行检测过期session 默认为true
sessionManager.setSessionValidationSchedulerEnabled(true);
//设置session失效的扫描时间, 清理用户直接关闭浏览器造成的孤立会话 默认为 1个小时
//设置该属性 就不需要设置 ExecutorServiceSessionValidationScheduler 底层也是默认自动调用ExecutorServiceSessionValidationScheduler
//暂时设置为 5秒 用来测试
sessionManager.setSessionValidationInterval(3600000);

//取消url 后面的 JSESSIONID
sessionManager.setSessionIdUrlRewritingEnabled(false);

return sessionManager;
}

注意:这里的SessionIdCookie 是新建的一个SimpleCookie对象,不是之前整合记住我的那个rememberMeCookie 如果配错了,就会出现session经典问题:每次请求都是一个新的session 并且后台报以下异常,解析的时候报错.因为记住我cookie是加密的用户信息,所以报解密错误

1
org.apache.shiro.crypto.CryptoException: Unable to execute 'doFinal' with cipher instance [javax.crypto.Cipher@461df537].
配置清理孤立session

以上整合会话管理,还有一个问题: 如果用户如果不点注销,直接关闭浏览器,不能够进行session的清空处理,所以为了防止这样的问题,还需要增加有一个会话的验证调度。

1
2
3
4
5
6
7
8
9
10
//全局会话超时时间(单位毫秒),默认30分钟  暂时设置为10秒钟 用来测试
sessionManager.setGlobalSessionTimeout(1800000);
//是否开启删除无效的session对象 默认为true
sessionManager.setDeleteInvalidSessions(true);
//是否开启定时调度器进行检测过期session 默认为true
sessionManager.setSessionValidationSchedulerEnabled(true);
//设置session失效的扫描时间, 清理用户直接关闭浏览器造成的孤立会话 默认为 1个小时
//设置该属性 就不需要设置 ExecutorServiceSessionValidationScheduler 底层也是默认自动调用ExecutorServiceSessionValidationScheduler
//暂时设置为 5秒 用来测试
sessionManager.setSessionValidationInterval(3600000);
取消URL的JSESSIONID

shiro取消url上面的JSESSIONID

1
2
//取消url 后面的 JSESSIONID
sessionManager.setSessionIdUrlRewritingEnabled(false);
修改SecurityManager

将session管理器交给SecurityManager

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 配置核心安全事务管理器
*
* @return
*/
@Bean(name = "securityManager")
public SecurityManager securityManager() {
...

//配置自定义session管理
securityManager.setSessionManager(sessionManager());

return securityManager;
}

配置Ehcache缓存

ehcache-shiro.xml添加session缓存属性

1
2
3
4
5
6
7
8
9
<!-- session缓存 -->
<cache name="shiro-activeSessionCache"
maxEntriesLocalHeap="2000"
eternal="false"
timeToIdleSeconds="0"
timeToLiveSeconds="0"
overflowToDisk="false"
statistics="true">
</cache>

注意: 这里一定要注意缓存的设置过期时间,还有setGlobalSessionTimeout 的值,任一个时间设置的比较短,session就会从ehcache中清除,到时候就会报There is no session with id [2e9e317f-7575-4bb0-98c4-3e6e5d2578f5]

启动测试

配置完成之后启动测试,登陆的时候点击 rememberMe 查看cookie 可以看到一个sessionId 和 一个记住我cookie

评论