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. Centos6.5 使用YUM安装MariaDB

    1,第一步 [xxxxxx]$ cd /etc/yum.repos.d [xxxxxx]$ vi MariaDB.repo # MariaDB 10.0 CentOS repository list ...

  2. P4195 【模板】exBSGS/Spoj3105 Mod

    传送门 首先要懂得 $BSGS$,$BSGS$ 可以求出关于 $Y$ 的方程 $X^Y \equiv Z (mod\ mo)$ 的最小解,其中 $gcd(X,Z)=1$ $exBSGS$ 算是 $BS ...

  3. Go语言_更多类型:struct、slice 和映射

    更多类型:struct.slice 和映射 学习如何基于现有类型定义新的类型:本节课涵盖了结构体.数组.切片和映射. Go 作者组编写,Go-zh 小组翻译. https://tour.go-zh.o ...

  4. nginx各版本全自动编译安装脚本

    #!/bin/bash #作者:星云法师(头条号:西西图图---专注美食领域的研究) #环境:centos7,如果是其它的系统可以相应做调整.#--------选择安装方式,网络晚装还是本地安装--- ...

  5. 修改xampp中的MySQL密码

    1.开启MySQL服务后,点击XAMPP Control Panel上的Admin按钮 2.依次点击"账户"--最后一个"修改权限"--修改密码 3.输入两次相 ...

  6. python中序列类型

    Python中的序列类型使用 元组类型 一旦被创建,就无法被修改. 创建 使用()或者tuple()创建 creater1=('cat', 'dog', 'tiger', 'human') creat ...

  7. 微信小程序(6)--获取屏幕宽度及弹窗滚动与页面滚动冲突

    1.获取屏幕宽度,并赋值给view <view class="ships-img" style="height:{{windowWidth}}px;"&g ...

  8. Ubunton

    Ubunton 装完机后 root账户进不去 没密码 在自己账号下 sudo passwd 输入两次密码就是root的密码 之后就可以su root 密码进入了 外部客户端sftp方式连接: sudo ...

  9. Strcpy,strcpy使用注意

    一.char *strcpy(char *dest, const char *src) 参数 dest -- 指向用于存储复制内容的目标数组. src -- 要复制的字符串. 注意: 1.dest需要 ...

  10. 【学习笔记】可持久化并查集(BZOJ3673)

    好久之前就想学了 然后今天恰巧一道题需要用到就学了 前置芝士 1.主席树[可持久化数组] 2.并查集 如果你掌握了前面两个那么这个东西你就会觉得非常沙茶.. 构造 可持久化并查集 = 主席树  + 并 ...