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. 汇编语言之寄存器使用bx si di bp

    转载自:https://www.cnblogs.com/youxin/archive/2012/05/29/2524780.html 如果你看到这篇博客时正在做第七章问题,强烈建议先去把8.1~8.4 ...

  2. py2 一键转换成py3

    找到python自带的2to3-3.7 文件,在终端里输入 python 2to3-3.7 -w /Users/v_fanjiexiong/PycharmProjects/HugeGraph_fan/ ...

  3. php 从7.0升级到7.2

    下面的方法安装的php是非线程安全的,apache2服务器用不了 1. 添加Ondřej Surý提供的PHP源: sudo apt-get install software-properties-c ...

  4. Python 无法安装PyAudio问题

    一.错误与原因 在Windows上没有用于Python 3.7的轮子(预构建包)(有一个用于Python 2.7和3.4到3.6),因此需要在PC上准备构建环境以使用此包.因为有些软件包很难在Wind ...

  5. 13.以太坊中web3访问合约账户出现问题——2019年09月29日

    title: 合约交互时发现访问不了地址的bug date: "2019-09-29 10:17:16" tags: Dapp开发 categories: 技术驿站 在编写合约交互 ...

  6. linux(二)用户和用户组管理

    root用户可以访问自己的家目录,访问配置文件,访问普通用户的家目录 普通用户只能访问自己的家目录和root用户开放的目录 普通用户之间无法访问对方的家目录 创建用户,用id查看用户信息,root的u ...

  7. 【leetcode】44. Wildcard Matching

    题目如下: 解题思路:本题和[leetcode]97. Interleaving String非常相似,同样可以采用动态规划的方法.记dp[i][j] = 1或者0 表示pattern[0:i]是否匹 ...

  8. Navicat Premium 12 如何连接阿里云虚拟主机SQL Server 数据库

    这个是一台 阿里云购买云虚拟主机!密码已经重置完毕,现在我们 需要知道 数据连接的地址,数据库名,帐号以及密码. 根据不同的运营商 选择 这里我们选择阿里云 云数据库 SQL Server版本 填写 ...

  9. windows之cmd常用命令

    一.简单介绍 CMD全称command,即命令提示符,是内置在windows图形操作系统内的磁盘操作系统,通过CMD可以方便用户查询比较复杂的信息或快速查找实现某些功能等,比如说打开文件.系统设置等操 ...

  10. 使用CSS在页面中嵌入字体

    http://jingyan.baidu.com/article/3065b3b6e9b2d9becff8a4c1.html 首先感谢css9.net照抄原话: 字体使用是网页设计中不可或缺的一部分. ...