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. C. Trailing Loves (or L'oeufs?) (质因数分解)

    C. Trailing Loves (or L'oeufs?) 题目传送门 题意: 求n!在b进制下末尾有多少个0? 思路: 类比与5!在10进制下末尾0的个数是看2和5的个数,那么 原题就是看b进行 ...

  2. React入门-JSX和虚拟dom

    1.JSX理解 举例: const element = <h1>Hello, world!</h1>; 这被称为 JSX,是一个 JavaScript 的语法扩展.建议在 Re ...

  3. ipv6地址在URL中的表达方式,如何在URL地址中包含ipv6地址

    摘要 本文档定义了在WWW浏览器的URL中执行的文本IPv6地址的格式.在包括Microsoft的IE,Mozilla和Lynx等几个已经被广泛安装使用的浏览器的IPv6版本中,这种格式已经被使用.并 ...

  4. go语言学习之从例子开始

    [目录] go语言从例子开始之Example1.helloworld go语言从例子开始之Example2.类型 go语言从例子开始之Example3.变量 go语言从例子开始之Example4.常量 ...

  5. "Unable to locate package lrzsz"的解决办法

    某天安装一些常用软件,比如lrzsz的时候出错了 $ sudo apt-get install lrzsz Reading package lists... Done Building depende ...

  6. CS184.1X 计算机图形学导论(第三讲)

    第一单元(介绍关于变换的数学知识) :基本二维变换 模型坐标系,世界坐标系 1.缩放 Scale(规模,比例) Sx表示在x方向上放大的倍数,Sy表示在y方向上放大的倍数,因此X坐标乘以Sx,Y坐标乘 ...

  7. ltp-ddt eth_iperf_tcp iperf dualtest遇到的问题

    ltp-ddt eth_iperf_tcp server端:iperf -s -i 5 -w 1M client端将ddt的核心代码抠出来: iperf -c 1921.68.40.41 -m -M ...

  8. spark 计算结果写入mysql 案例及常见问题解决

    package com.jxd import org.apache.spark.SparkContextimport org.apache.spark.SparkConfimport java.sql ...

  9. 【串线篇】SQL映射文件select简单查询标签

    一.参数(Parameters)传递 单个参数 基本类型:取值#{hahaha}随便写 多个参数 <!--   public Employee getEmpById(Integer id,Str ...

  10. 苹果的AR赌注仍然有很多需要证明的

    苹果公司为开发者主题发布会做准备,其中一个更大的公告很可能是其增强现实平台的新变化.自从去年宣布ARKit以来,这家科技巨头几乎对其对AR的潜力抱有信心. 在很多讨论背后,人们都相信技术的实用性,但在 ...