1、会话管理SessionDao和SessionManager

1)安装Redis

2)依赖

        <dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.8.0</version>
</dependency>

3)配置redis连接池的bean:

    @Bean
public JedisPoolConfig getJedisPoolConfig() {
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
return jedisPoolConfig;
} @Bean
public JedisPool getJedisPool() {
JedisPool jedisPool = new JedisPool(getJedisPoolConfig(), "129.204.58.30", 6379);
return jedisPool;
}

4)编写redis工具类:

package com.example.demo_mg.util;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool; import java.util.Set; @Component
public class RedisUtil {
@Autowired
private JedisPool jedisPool; private Jedis getResource() {
return jedisPool.getResource();
} public byte[] set(byte[] key, byte[] value) {
Jedis jedis = getResource();
try {
jedis.set(key, value);
return value;
} finally {
jedis.close();
}
} public void expire(byte[] key, int i) {
Jedis jedis = getResource();
try {
jedis.expire(key, i);
} finally {
jedis.close();
}
} public byte[] get(byte[] key) {
Jedis jedis = getResource();
try {
return jedis.get(key);
} finally {
jedis.close();
}
} public void del(byte[] key) {
Jedis jedis = getResource();
try {
jedis.del(key);
} finally {
jedis.close();
}
} //获取指定前缀所有Key
public Set<byte[]> keys(String prefix) {
Jedis jedis = getResource();
try {
return jedis.keys((prefix + "*").getBytes());
} finally {
jedis.close();
}
}
}

5)编写SessionDao继承AbstractSessionDAO:

package com.example.demo_mg.session;

import com.example.demo_mg.util.RedisUtil;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.UnknownSessionException;
import org.apache.shiro.session.mgt.eis.AbstractSessionDAO;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.SerializationUtils; import javax.annotation.Resource;
import java.io.Serializable;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set; @Component
public class RedisSessionDao extends AbstractSessionDAO {
@Resource
private RedisUtil redisUtil; private final String SHIRO_SESSION_PREFIX = "shiro_session:"; private byte[] getKey(String key) {
return (SHIRO_SESSION_PREFIX + key).getBytes();
} private void saveSession(Session session) {
if(session != null && session.getId() !=null) {
byte[] key = getKey(session.getId().toString());
byte[] value = SerializationUtils.serialize(session);
redisUtil.set(key, value);
redisUtil.expire(key, 600);
}
} @Override
protected Serializable doCreate(Session session) {
Serializable sessionId = generateSessionId(session);
assignSessionId(session, sessionId); //需要捆绑,否则登录会抛异常
saveSession(session);
return sessionId;
} // serializable是sessionId
@Override
protected Session doReadSession(Serializable serializable) {
System.out.println("read session");
if(serializable == null) {
return null;
}
byte[] key = getKey(serializable.toString());
byte[] value = redisUtil.get(key);
return (Session) SerializationUtils.deserialize(value);
} @Override
public void update(Session session) throws UnknownSessionException {
saveSession(session);
} @Override
public void delete(Session session) {
if(session == null || session.getId() == null) {
return;
}
byte[] key = getKey(session.getId().toString());
redisUtil.del(key);
} @Override
public Collection<Session> getActiveSessions() {
Set<byte[]> keys = redisUtil.keys(SHIRO_SESSION_PREFIX);
Set<Session> sessions = new HashSet<>();
if(CollectionUtils.isEmpty(keys)) {
return sessions;
}
for (byte[] key : keys) {
Session session = (Session) SerializationUtils.deserialize(redisUtil.get(key));
sessions.add(session);
}
return sessions;
}
}

6)在(二)的基础上修改配置bean:

新配置两个bean
@Bean
public RedisSessionDao getRedisSessionDao() {
RedisSessionDao redisSessionDao = new RedisSessionDao();
return redisSessionDao;
} @Bean
public DefaultWebSessionManager getDefaultWebSessionManager(RedisSessionDao redisSessionDao) {
DefaultWebSessionManager defaultWebSessionManager = new DefaultWebSessionManager();
defaultWebSessionManager.setSessionDAO(redisSessionDao);
return defaultWebSessionManager;
} 修改一个bean(添加会话管理)
@Bean
public DefaultWebSecurityManager getDefaultWebSecurityManager(TestRealm testRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(testRealm); //会话管理
securityManager.setSessionManager(getDefaultWebSessionManager(getRedisSessionDao())); return securityManager;
}

7)登录验证,只是登录,后台打印了5次read session,redis客户端执行keys *,查到"shiro_session:19b704b8-3324-4f88-92c0-c0effb5243a6"。

源码在DefaultSessionManager的retrieveSession方法中,调用Session s = this.retrieveSessionFromDataSource(sessionId);该方法中又调用this.sessionDAO.readSession(sessionId);再调AbstractSessionDao的readSession方法,代码Session s = this.doReadSession(sessionId);调用自己实现的RedisSessionDao的doReadSession方法。所有,要减少去Redis读取session的次数,需要自己重写retrieveSession方法,改造Session s = this.retrieveSessionFromDataSource(sessionId);。

定义一个SessionManager类继承DefaultWebSessionManager类重写retrieveSession方法:

package com.example.demo_mg.session;

import org.apache.shiro.session.Session;
import org.apache.shiro.session.UnknownSessionException;
import org.apache.shiro.session.mgt.SessionKey;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.apache.shiro.web.session.mgt.WebSessionKey; import javax.servlet.ServletRequest;
import java.io.Serializable; /**
* sessionKey对象里有request对象,可以第一次查询以后把session放在request对象里,就不需要频繁查询redis。
*/
public class RedisSessionManager extends DefaultWebSessionManager {
@Override
protected Session retrieveSession(SessionKey sessionKey) throws UnknownSessionException {
Serializable sessionId = getSessionId(sessionKey);
ServletRequest request = null;
if(sessionKey instanceof WebSessionKey) {
request = ((WebSessionKey)sessionKey).getServletRequest();
}
if(request != null && sessionId != null) {
Session session = (Session) request.getAttribute(sessionId.toString());
if(session != null) {
return session;
}
}
Session session = super.retrieveSession(sessionKey);
if(request != null && sessionId != null) {
request.setAttribute(sessionId.toString(), session);
}
return session;
}
}

修改配置bean:

    @Bean
public DefaultWebSessionManager getDefaultWebSessionManager(RedisSessionDao redisSessionDao) {
// DefaultWebSessionManager defaultWebSessionManager = new DefaultWebSessionManager();
DefaultWebSessionManager defaultWebSessionManager = new RedisSessionManager(); //改用自己定义的SessionManager
defaultWebSessionManager.setSessionDAO(redisSessionDao);
return defaultWebSessionManager;
}

2、缓存管理,Cache和CacheManager:

缓存角色、权限信息,授权需要,认证可以不用,可以用redis、echache或map实现缓存CacheManager

1)自定义Cache:

package com.example.demo_mg.cache;

import com.example.demo_mg.util.RedisUtil;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.springframework.stereotype.Component;
import org.springframework.util.SerializationUtils; import javax.annotation.Resource;
import java.util.Collection;
import java.util.Set; @Component
public class RedisCache<K, V> implements Cache<K, V> {
@Resource
private RedisUtil redisUtil; private final String CACHE_PREFIX = "shiro_cache:"; private byte[] getKey(K k) {
if(k instanceof String) {
return (CACHE_PREFIX + k).getBytes();
}
return SerializationUtils.serialize(k);
} //该方法还可以用Map在本地做二级缓存
@Override
public V get(K k) throws CacheException {
System.out.println("缓存读取授权信息");
byte[] value = redisUtil.get(getKey(k));
if(value != null) {
return (V)SerializationUtils.deserialize(value);
}
return null;
} //过期时间最好可配置,单位秒
@Override
public V put(K k, V v) throws CacheException {
byte[] key = getKey(k);
byte[] value = SerializationUtils.serialize(v);
redisUtil.set(key, value);
redisUtil.expire(key, 600);
return v;
} @Override
public V remove(K k) throws CacheException {
byte[] key = getKey(k);
byte[] value = redisUtil.get(key);
redisUtil.del(key);
if(value != null) {
return (V)SerializationUtils.deserialize(value);
}
return null;
} @Override
public void clear() throws CacheException {
//只清空缓存用的,慎重,小心将Redis的所有缓存清空
} @Override
public int size() {
return 0;
} @Override
public Set<K> keys() {
return null;
} @Override
public Collection<V> values() {
return null;
}
}

2)自定义CacheManager:

package com.example.demo_mg.cache;

import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.apache.shiro.cache.CacheManager; import javax.annotation.Resource; public class RedisCacheManager implements CacheManager {
@Resource
private RedisCache redisCache; //这里的String s可以用来创建一个concurrentHashMap缓存cache名称和cache实例,s就是cache名称,这里只有一个redisCache对象就不用map了。
@Override
public <K, V> Cache<K, V> getCache(String s) throws CacheException {
return redisCache;
}
}

3)配置类:

新增bean
@Bean
public RedisCacheManager getRedisCacheManager() {
RedisCacheManager redisCacheManager = new RedisCacheManager();
return redisCacheManager;
} 修改bean(添加缓存管理)
@Bean
public DefaultWebSecurityManager getDefaultWebSecurityManager(TestRealm testRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(testRealm); //会话管理
securityManager.setSessionManager(getDefaultWebSessionManager(getRedisSessionDao())); //缓存管理
securityManager.setCacheManager(getRedisCacheManager()); return securityManager;
}

4)在Realm添加标记(只加上两处System.out打印开始和结束从数据库获取授权信息):

package com.example.demo_mg.realm;

import org.apache.commons.collections.map.HashedMap;
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.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.crypto.hash.Md5Hash;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource; import java.util.*; public class TestRealm extends AuthorizingRealm {
//模拟users、user_roles、roles_permissions三张表的查询,实际应用需要查询数据库或缓存
Map<String, String> users = new HashMap<>();
Map<String, Set<String>> user_roles = new HashedMap();
Map<String, Set<String>> roles_permissions = new HashedMap();
// String salt = UUID.randomUUID().toString().replaceAll("-",""); {
//不加盐(与认证对应)
users.put("wzs", new Md5Hash("123456",null, 2).toString());
//加盐
// users.put("wzs", new Md5Hash("123456",salt, 2).toString());
user_roles.put("wzs", new HashSet<>(Arrays.asList("admin", "test")));
roles_permissions.put("admin", new HashSet<>(Arrays.asList("user:delete", "user:update")));
super.setName("TestRealm"); //设置Realm名称,可选
} @Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//从认证信息获取用户名
String username = (String)principalCollection.getPrimaryPrincipal();
//从数据库或缓存中获取角色、权限数据
System.out.println("数据库获取认证信息start:");
Set<String> roles = user_roles.get(username);
Set<String> permissions = new HashSet<>();
for (String role : roles) {
Set<String> set;
if((set = roles_permissions.get(role)) != null) {
permissions.addAll(set);
}
}
System.out.println("数据库获取认证信息end.");
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
simpleAuthorizationInfo.setRoles(roles);
simpleAuthorizationInfo.setStringPermissions(permissions);
return simpleAuthorizationInfo;
} @Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//从主题传过来的认证信息中,获得用户名
String username = (String)authenticationToken.getPrincipal();
//通过用户名从数据库中获取凭证
String password = users.get(username);
if(password != null) {
//不加盐
// return new SimpleAuthenticationInfo(username, password, super.getName());
//加盐
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(username, password, super.getName());
// simpleAuthenticationInfo.setCredentialsSalt(ByteSource.Util.bytes(salt));
return simpleAuthenticationInfo;
}
return null;
}
}

5)测试结果:

第一次访问带有@RequireRole注解的方法后台打印:

read session

缓存读取授权信息

数据库获取认证信息start:

数据库获取认证信息end.

第二次访问后台打印:

read session

缓存读取授权信息

说明第一次从缓存没取到,去数据库获取并缓存起来,以后直接从缓存获取。

Redis客户端执行keys *,有如下key:

 "\xac\xed\x00\x05sr\x002org.apache.shiro.subject.SimplePrincipalCollection\xa                                     8\x7fX%\xc6\xa3\bJ\x03\x00\x01L\x00\x0frealmPrincipalst\x00\x0fLjava/util/Map;xp                                     sr\x00\x17java.util.LinkedHashMap4\xc0N\\\x10l\xc0\xfb\x02\x00\x01Z\x00\x0bacces                                     sOrderxr\x00\x11java.util.HashMap\x05\a\xda\xc1\xc3\x16`\xd1\x03\x00\x02F\x00\nl                                     oadFactorI\x00\tthresholdxp?@\x00\x00\x00\x00\x00\x0cw\b\x00\x00\x00\x10\x00\x00                                     \x00\x01t\x00\tTestRealmsr\x00\x17java.util.LinkedHashSet\xd8l\xd7Z\x95\xdd*\x1e                                     \x02\x00\x00xr\x00\x11java.util.HashSet\xbaD\x85\x95\x96\xb8\xb74\x03\x00\x00xpw                                     \x0c\x00\x00\x00\x02?@\x00\x00\x00\x00\x00\x01t\x00\x03wzsxx\x00w\x01\x01q\x00~\                                     x00\x05x"

3、RememberMe,记住我,实现自动登录,在User对象中添加boolean类型的remeberMe属性,登录表单添加"记住我"checkbox:

1)改造登录方法

    @RequestMapping(value = "/login",method = RequestMethod.GET)
public String loginUser(String username, String password, boolean rememberMe) {
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(username, password);
Subject subject = SecurityUtils.getSubject();
try {
usernamePasswordToken.setRememberMe(rememberMe); //记住我功能
subject.login(usernamePasswordToken); //完成登录
//更新用户登录时间,也可以在ShiroRealm里面做
return "index";
} catch(Exception e) {
return "login";//返回登录页面
}
}

2)配置bean

添加2个bean
@Bean
public SimpleCookie getSimpleCookie() {
SimpleCookie simpleCookie = new SimpleCookie("rememberMe"); //生成cookie名称
simpleCookie.setMaxAge(600); //生成cookie过期时间,单位秒
return simpleCookie;
} @Bean
public CookieRememberMeManager getCookieRememberMeManager() {
CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
cookieRememberMeManager.setCookie(getSimpleCookie());
return cookieRememberMeManager;
} 修改一个bean(添加RememberMe)
@Bean
public DefaultWebSecurityManager getDefaultWebSecurityManager(TestRealm testRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(testRealm); //会话管理
securityManager.setSessionManager(getDefaultWebSessionManager(getRedisSessionDao())); //缓存管理
securityManager.setCacheManager(getRedisCacheManager()); //RememberMe
securityManager.setRememberMeManager(getCookieRememberMeManager()); return securityManager;
}

3)验证:登录以后浏览器F12的Application下,Cookies下,http://localhost:8080有一条叫做rememberMe的cookie。且后台重启以后页面没调转到登录页,说明记住我生效了。

Apache Shiro 会话+缓存+记住我(三)的更多相关文章

  1. Apache shiro集群实现 (三)shiro身份认证(Shiro Authentication)

    Apache shiro集群实现 (一) shiro入门介绍 Apache shiro集群实现 (二) shiro 的INI配置 Apache shiro集群实现 (三)shiro身份认证(Shiro ...

  2. Apache shiro集群实现 (一) shiro入门介绍

    近期在ITOO项目中研究使用Apache shiro集群中要解决的两个问题,一个是Session的共享问题,一个是授权信息的cache共享问题,官网上给的例子是Ehcache的实现,在配置说明上不算很 ...

  3. Apache shiro集群实现 (六)分布式集群系统下的高可用session解决方案---Session共享

    Apache shiro集群实现 (一) shiro入门介绍 Apache shiro集群实现 (二) shiro 的INI配置 Apache shiro集群实现 (三)shiro身份认证(Shiro ...

  4. Apache shiro学习总结

    Apache shiro集群实现 (一) shiro入门介绍 Apache shiro集群实现 (二) shiro 的INI配置 Apache shiro集群实现 (三)shiro身份认证(Shiro ...

  5. 快速搭建Spring Boot + Apache Shiro 环境

    个人博客网:https://wushaopei.github.io/    (你想要这里多有) 一.Apache Shiro 介绍及概念 概念:Apache Shiro是一个强大且易用的Java安全框 ...

  6. Apache shiro集群实现 (七)分布式集群系统下---cache共享

    Apache shiro集群实现 (一) shiro入门介绍 Apache shiro集群实现 (二) shiro 的INI配置 Apache shiro集群实现 (三)shiro身份认证(Shiro ...

  7. Apache shiro集群实现 (五)分布式集群系统下的高可用session解决方案

    Apache shiro集群实现 (一) shiro入门介绍 Apache shiro集群实现 (二) shiro 的INI配置 Apache shiro集群实现 (三)shiro身份认证(Shiro ...

  8. Apache Shiro 使用手冊 链接文件夹整理

    1.Apache Shiro 使用手冊(一)Shiro架构介绍 2.Apache Shiro 使用手冊(二)Shiro 认证 3.Apache Shiro 使用手冊(三)Shiro 授权 4.Apac ...

  9. Apache shiro集群实现 (八) web集群时session同步的3种方法

    Apache shiro集群实现 (一) shiro入门介绍 Apache shiro集群实现 (二) shiro 的INI配置 Apache shiro集群实现 (三)shiro身份认证(Shiro ...

随机推荐

  1. python学习第二十天文件操作方法

    字符有的存储在内存,有的存储在硬盘,文件也有增删改查的操作方法,open()方法,read()方法,readline()方法,close()文件关闭,write()写的方法,seek() 指针移动方法 ...

  2. P4542 [ZJOI2011]营救皮卡丘(Floyd+网络流)

    P4542 [ZJOI2011]营救皮卡丘 乍一看似乎没啥题相似的 仔细一看,$N<=150$ 边又是双向边,似乎可以用Floyd搞   先跑一遍Floyd处理出$dis[i][j]$ 注意到走 ...

  3. 2018-8-10-win10-uwp-MetroLog-入门

    title author date CreateTime categories win10 uwp MetroLog 入门 lindexi 2018-08-10 19:16:53 +0800 2018 ...

  4. CentOS下安装中文man 手册

    为了方便使用man,安装中文手册,具体如下: 版本:CentOS release 6.6 (Final) 中文包:http://pkgs.fedoraproject.org/repo/pkgs/man ...

  5. centos GIT安装

    [注](yum 源仓库里的 Git 版本更新不及时,最新版本的 Git 是 1.8.3.1,但是官方最新版本已经到了 2.9.2.想要安装最新版本的的 Git,只能下载源码进行安装.) 安装git服务 ...

  6. Codecraft-17 and Codeforces Round #391 - A

    题目链接:http://codeforces.com/contest/757/problem/A 题意:给定一个字符串,问你从这个字符串中选出一些字符然后重新排序后最多能组成多少个 Bulbasaur ...

  7. 【LeetCode】线段树 segment-tree(共9题)+ 树状数组 binary-indexed-tree(共5题)

    第一部分---线段树:https://leetcode.com/tag/segment-tree/ [218]The Skyline Problem [307]Range Sum Query - Mu ...

  8. Java使用对象类型作为方法的返回值

  9. HTTP协议缓存

    缓存的概念 缓存这个东西真的是无处不在, 有浏览器端的缓存, 有服务器端的缓存,有代理服务器的缓存, 有ASP.NET页面缓存,对象缓存. 数据库也有缓存, 等等. http中具有缓存功能的是浏览器缓 ...

  10. cocos2D-X 打包

    { //首先有java jdk,android sdk,android ndk //用android studio  import //匹配gradle 的版本 有些gradle可能下载不下来,不用慌 ...