这里简单做了点尝试估计还是有漏洞,而且逻辑有些复杂
以后再做个思路简单的
这里的设计思路:每个用户在数据库里面都有一个UserId
我们就在这个UserId上面做文章
每个用户登录成功后就会用自己的UserId做为key,这次登录的SessionId作为value存储一对键值对到redis中这个我们可以靠securitymanage管理的SessionDao来管理
下面我把我的shiroConfig贴出来
package com.crsri.config;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.Filter;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authc.credential.CredentialsMatcher;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.session.SessionListener;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator;
import org.apache.shiro.session.mgt.eis.SessionDAO;
import org.apache.shiro.session.mgt.eis.SessionIdGenerator;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.mindrot.jbcrypt.BCrypt;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.MethodInvokingFactoryBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.crsri.realm.LoginRealm;
import com.crsri.service.system.MenuService;
import com.crsri.util.PropertiesUtils;
@Configuration
public class ShiroConfiguration {
@Autowired
private MenuService menuService;
@Value("
s
p
r
i
n
g
.
r
e
d
i
s
.
h
o
s
t
"
)
p
r
i
v
a
t
e
S
t
r
i
n
g
h
o
s
t
;
@
V
a
l
u
e
(
"
{spring.redis.host}") private String host; @Value("
spring.redis.host")privateStringhost;@Value("{spring.redis.port}")
private int port;
//@Value("${spring.redis.password}")
//private int password;
/**
* shiro权限配置
*
* @param securityManager
* @return
*/
@Bean(name = “shiroFilter”)
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
/*LinkedHashMap<String, Filter> filtsMap=new LinkedHashMap<String, Filter>();*/
/* filtsMap.put("perms",new MyPermsFilter())*/;
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
//手机端ajax判断
Map<String, Filter> filters = shiroFilterFactoryBean.getFilters();
filters.put("authc", new ShiroLoginFilter());
/* shiroFilterFactoryBean.setFilters(filters);*/
shiroFilterFactoryBean.setLoginUrl("/login.html");
/* shiroFilterFactoryBean.setSuccessUrl("/index.html");*/
/*shiroFilterFactoryBean.setUnauthorizedUrl("/403.html");*/
/*shiroFilterFactoryBean.setFilters(filtsMap);*/
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
filterChainDefinitionMap.put("/plugin/**", "anon");
filterChainDefinitionMap.put("/css/system/main.css", "anon");
filterChainDefinitionMap.put("/pageoffice.js", "anon");
filterChainDefinitionMap.put("/jquery.min.js", "anon");
filterChainDefinitionMap.put("/login2.html", "anon");
filterChainDefinitionMap.put("/login1.html", "anon");
filterChainDefinitionMap.put("/static/**", "anon");
filterChainDefinitionMap.put("/library/**", "anon");
filterChainDefinitionMap.put("/js/**", "anon");
filterChainDefinitionMap.put("/img/**", "anon");
filterChainDefinitionMap.put("/tologin/**", "anon");
filterChainDefinitionMap.put("/login", "anon");
filterChainDefinitionMap.put("/login1", "anon");
filterChainDefinitionMap.put("/getcode", "anon");
filterChainDefinitionMap.put("/oauth/**", "anon");
filterChainDefinitionMap.put("/api/**", "anon");
filterChainDefinitionMap.put("/api-login/**", "anon");
/* filterChainDefinitionMap.put("/**", "roles[liufei1]");*/
List<Map<String, Object>> perms = menuService.PermsInit();// 初始化权限
if(perms!=null) {
for (Map<String, Object> m : perms) {
if(((String)m.get("Url")).length()!=0) {
//System.out.println("url--------------"+m.get("Url"));
//System.out.println("perms---------------"+m.get("Perms"));
filterChainDefinitionMap.put((String) m.get("Url"), "perms["+(String)m.get("Perms") + "]");
}
}
}
filterChainDefinitionMap.put("/**", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
/**
* 配置核心安全事务管理器
*
* @return
*/
@Bean(name = "securityManager")
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 设置自定义realm.
securityManager.setRealm(securityRealm());
// 配置记住我
securityManager.setRememberMeManager(rememberMeManager());
// 配置redis缓存
securityManager.setCacheManager(cacheManager());
// 配置自定义session管理,使用redis
securityManager.setSessionManager(sessionManager());
return securityManager;
}
/**
* Shiro生命周期处理器
*/
@Bean(name = "lifecycleBeanPostProcessor")
public static LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
@Bean
public Realm securityRealm() {
// TODO Auto-generated method stub
LoginRealm loginRealm = new LoginRealm();
loginRealm.setCredentialsMatcher(new CredentialsMatcher() {// 加密认证
// @Override
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
// TODO Auto-generated method stub
UsernamePasswordToken userToken = (UsernamePasswordToken) token;
// 要验证的明文密码
String plaintext = new String(userToken.getPassword());
// 数据库中加密后的密文
String hashed = info.getCredentials().toString();// 获取密码数据库中用户加密密码
return BCrypt.checkpw(plaintext, hashed);
}
});
// 启用身份验证缓存,即缓存AuthenticationInfo信息,默认false
/*loginRealm.setAuthenticationCachingEnabled(true);*/
// 缓存AuthenticationInfo信息的缓存名称 在ehcache-shiro.xml中有对应缓存的配置
/*loginRealm.setAuthenticationCacheName("authenticationCache");*/
// 启用授权缓存,即缓存AuthorizationInfo信息,默认false
loginRealm.setAuthorizationCachingEnabled(true);
// 缓存AuthorizationInfo信息的缓存名称 在ehcache-shiro.xml中有对应缓存的配置
loginRealm.setAuthorizationCacheName("authorizationCache");
return loginRealm;
}
/**
* cookie对象;会话Cookie模板 ,默认为: JSESSIONID 问题:
* 与SERVLET容器名冲突,重新定义为sid或rememberMe,自定义
*
* @return
*/
@Bean
public SimpleCookie rememberMeCookie() {
// 这个参数是cookie的名称,对应前端的checkbox的name = rememberMe
SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
// setcookie的httponly属性如果设为true的话,会增加对xss防护的安全系数。它有以下特点:
// setcookie()的第七个参数
// 设为true后,只能通过http访问,javascript无法访问
// 防止xss读取cookie
simpleCookie.setHttpOnly(true);
simpleCookie.setPath("/");
// <!-- 记住我cookie生效时间30天 ,单位秒;-->
simpleCookie.setMaxAge(2592000);
return simpleCookie;
}
/**
* cookie管理对象;记住我功能,rememberMe管理器
*
* @return
*/
@Bean
public CookieRememberMeManager rememberMeManager() {
CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
cookieRememberMeManager.setCookie(rememberMeCookie());
// rememberMe cookie加密的密钥 建议每个项目都不一样 默认AES算法 密钥长度(128 256 512 位)
cookieRememberMeManager.setCipherKey(Base64.decode("4AvVhmFLUs0KTA3Kprsdag=="));
return cookieRememberMeManager;
}
/**
* FormAuthenticationFilter 过滤器 过滤记住我
*
* @return
*/
@Bean
public FormAuthenticationFilter formAuthenticationFilter() {
FormAuthenticationFilter formAuthenticationFilter = new FormAuthenticationFilter();
// 对应前端的checkbox的name = rememberMe
formAuthenticationFilter.setRememberMeParam("rememberMe");
return formAuthenticationFilter;
}
/**
* shiro缓存管理器; 需要添加到securityManager中
*
* @return
*/
@Bean
public RedisCacheManager cacheManager() {
RedisCacheManager redisCacheManager = new RedisCacheManager();
redisCacheManager.setRedisManager(redisManager());
// redis中针对不同用户缓存
redisCacheManager.setPrincipalIdFieldName("username");
// 用户权限信息缓存时间
redisCacheManager.setExpire(3600);
return redisCacheManager;
}
/**
* 让某个实例的某个方法的返回值注入为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;
}
/**
* 配置session监听
*
* @return
*/
@Bean("sessionListener")
public ShiroSessionListener sessionListener() {
ShiroSessionListener sessionListener = new ShiroSessionListener();
return sessionListener;
}
/**
* 配置会话ID生成器
*
* @return
*/
@Bean
public SessionIdGenerator sessionIdGenerator() {
return new JavaUuidSessionIdGenerator();
}
@Bean
public RedisManager redisManager() {
RedisManager redisManager = new RedisManager();
redisManager.setHost(host);
redisManager.setPort(port);
// redisManager.setPassword("123456");
return redisManager;
}
/**
* SessionDAO的作用是为Session提供CRUD并进行持久化的一个shiro组件 MemorySessionDAO 直接在内存中进行会话维护
* EnterpriseCacheSessionDAO
* 提供了缓存功能的会话维护,默认情况下使用MapCache实现,内部使用ConcurrentHashMap保存缓存的会话。
*
* @return
*/
@Bean
public SessionDAO sessionDAO() {
RedisSessionKey redisSessionDAO = new RedisSessionKey();
//RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
redisSessionDAO.setRedisManager(redisManager());
// session在redis中的保存时间,最好大于session会话超时时间
redisSessionDAO.setExpire(1800);
return redisSessionDAO;
}
/**
* 配置保存sessionId的cookie 注意:这里的cookie 不是上面的记住我 cookie 记住我需要一个cookie session管理
* 也需要自己的cookie 默认为: JSESSIONID 问题: 与SERVLET容器名冲突,重新定义为sid
*
* @return
*/
@Bean("sessionIdCookie")
public SimpleCookie sessionIdCookie() {
// 这个参数是cookie的名称
SimpleCookie simpleCookie = new SimpleCookie("sid");
// setcookie的httponly属性如果设为true的话,会增加对xss防护的安全系数。它有以下特点:
// setcookie()的第七个参数
// 设为true后,只能通过http访问,javascript无法访问
// 防止xss读取cookie
simpleCookie.setHttpOnly(true);
simpleCookie.setPath("/");
// maxAge=-1表示浏览器关闭时失效此Cookie
simpleCookie.setMaxAge(-1);
return simpleCookie;
}
/**
* 配置会话管理器,设定会话超时及保存
*
* @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(cacheManager());
// 全局会话超时时间(单位毫秒),默认30分钟 暂时设置为2小时用来测试
sessionManager.setGlobalSessionTimeout(1800000 * 4);
// 是否开启删除无效的session对象 默认为true
sessionManager.setDeleteInvalidSessions(true);
// 是否开启定时调度器进行检测过期session 默认为true
sessionManager.setSessionValidationSchedulerEnabled(true);
// 设置session失效的扫描时间, 清理用户直接关闭浏览器造成的孤立会话 默认为 1个小时
// 设置该属性 就不需要设置 ExecutorServiceSessionValidationScheduler
// 底层也是默认自动调用ExecutorServiceSessionValidationScheduler
sessionManager.setSessionValidationInterval(3600000);
// 取消url 后面的 JSESSIONID
sessionManager.setSessionIdUrlRewritingEnabled(false);
return sessionManager;
}
/**
* 开启shiro 注解模式 可以在controller中的方法前加上注解 如 @RequiresPermissions("getProjectName:add")
*
* @param securityManager
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(
@Qualifier("securityManager") SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
@Bean
public ServletRegistrationBean servletRegistrationBean() {
com.zhuozhengsoft.pageoffice.poserver.Server poserver = new com.zhuozhengsoft.pageoffice.poserver.Server();
String poSysPath = PropertiesUtils.getBaseFilePath() + File.separator + "lic" + File.separator;
File registerDir = new File(poSysPath);
if (!registerDir.exists()) {
registerDir.mkdirs();
}
poserver.setSysPath(poSysPath);//设置PageOffice注册成功后,license.lic文件存放的目录
ServletRegistrationBean srb = new ServletRegistrationBean(poserver);
srb.addUrlMappings("/poserver.zz");
srb.addUrlMappings("/posetup.exe");
srb.addUrlMappings("/jquery.min.js");
srb.addUrlMappings("/pobstyle.css");
srb.addUrlMappings("/pageoffice.js");
srb.addUrlMappings("/sealsetup.exe");
return srb;//
}
}
核心代码在这里:
secutrityManget管理SessionManage
然后SessionManager管理SessionDao
然后SessionDao里面new一个RedisSessionDAO 然后来管理redisManger
这里是redisManager
这样我们的session就完全交给了redis来管理,这里以后我还要多看下源码,怎么交给的,暂时不懂
我们这里在这里做下手脚
RedisSessionDAO
这里进去我们可以看到代码是这样
package org.crazycake.shiro;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.UnknownSessionException;
import org.apache.shiro.session.mgt.eis.AbstractSessionDAO;
import org.crazycake.shiro.exception.SerializationException;
import org.crazycake.shiro.serializer.ObjectSerializer;
import org.crazycake.shiro.serializer.RedisSerializer;
import org.crazycake.shiro.serializer.StringSerializer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.Serializable;
import java.util.*;
public class RedisSessionDAO extends AbstractSessionDAO {
private static Logger logger = LoggerFactory.getLogger(RedisSessionDAO.class);
private static final String DEFAULT_SESSION_KEY_PREFIX = "shiro:session:";
private String keyPrefix = DEFAULT_SESSION_KEY_PREFIX;
private static final long DEFAULT_SESSION_IN_MEMORY_TIMEOUT = 1000L;
/**
* doReadSession be called about 10 times when login.
* Save Session in ThreadLocal to resolve this problem. sessionInMemoryTimeout is expiration of Session in ThreadLocal.
* The default value is 1000 milliseconds (1s).
* Most of time, you don't need to change it.
*/
private long sessionInMemoryTimeout = DEFAULT_SESSION_IN_MEMORY_TIMEOUT;
// expire time in seconds
private static final int DEFAULT_EXPIRE = -2;
private static final int NO_EXPIRE = -1;
/**
* Please make sure expire is longer than sesion.getTimeout()
*/
private int expire = DEFAULT_EXPIRE;
private static final int MILLISECONDS_IN_A_SECOND = 1000;
private IRedisManager redisManager;
private RedisSerializer keySerializer = new StringSerializer();
private RedisSerializer valueSerializer = new ObjectSerializer();
private static ThreadLocal sessionsInThread = new ThreadLocal();
@Override
public void update(Session session) throws UnknownSessionException {
this.saveSession(session);
}
/**
* save session
* @param session
* @throws UnknownSessionException
*/
private void saveSession(Session session) throws UnknownSessionException {
if (session == null || session.getId() == null) {
logger.error("session or session id is null");
throw new UnknownSessionException("session or session id is null");
}
byte[] key;
byte[] value;
try {
key = keySerializer.serialize(getRedisSessionKey(session.getId()));
value = valueSerializer.serialize(session);
} catch (SerializationException e) {
logger.error("serialize session error. session id=" + session.getId());
throw new UnknownSessionException(e);
}
if (expire == DEFAULT_EXPIRE) {
this.redisManager.set(key, value, (int) (session.getTimeout() / MILLISECONDS_IN_A_SECOND));
return;
}
if (expire != NO_EXPIRE && expire * MILLISECONDS_IN_A_SECOND < session.getTimeout()) {
logger.warn("Redis session expire time: "
+ (expire * MILLISECONDS_IN_A_SECOND)
+ " is less than Session timeout: "
+ session.getTimeout()
+ " . It may cause some problems.");
}
this.redisManager.set(key, value, expire);
}
@Override
public void delete(Session session) {
if (session == null || session.getId() == null) {
logger.error("session or session id is null");
return;
}
try {
redisManager.del(keySerializer.serialize(getRedisSessionKey(session.getId())));
} catch (SerializationException e) {
logger.error("delete session error. session id=" + session.getId());
}
}
@Override
public Collection<Session> getActiveSessions() {
Set<Session> sessions = new HashSet<Session>();
try {
Set<byte[]> keys = redisManager.keys(this.keySerializer.serialize(this.keyPrefix + "*"));
if (keys != null && keys.size() > 0) {
for (byte[] key:keys) {
Session s = (Session) valueSerializer.deserialize(redisManager.get(key));
sessions.add(s);
}
}
} catch (SerializationException e) {
logger.error("get active sessions error.");
}
return sessions;
}
@Override
protected Serializable doCreate(Session session) {
if (session == null) {
logger.error("session is null");
throw new UnknownSessionException("session is null");
}
Serializable sessionId = this.generateSessionId(session);
this.assignSessionId(session, sessionId);
this.saveSession(session);
return sessionId;
}
@Override
protected Session doReadSession(Serializable sessionId) {
if (sessionId == null) {
logger.warn("session id is null");
return null;
}
Session s = getSessionFromThreadLocal(sessionId);
if (s != null) {
return s;
}
logger.debug("read session from redis");
try {
s = (Session) valueSerializer.deserialize(redisManager.get(keySerializer.serialize(getRedisSessionKey(sessionId))));
setSessionToThreadLocal(sessionId, s);
} catch (SerializationException e) {
logger.error("read session error. settionId=" + sessionId);
}
return s;
}
private void setSessionToThreadLocal(Serializable sessionId, Session s) {
Map<Serializable, SessionInMemory> sessionMap = (Map<Serializable, SessionInMemory>) sessionsInThread.get();
if (sessionMap == null) {
sessionMap = new HashMap<Serializable, SessionInMemory>();
sessionsInThread.set(sessionMap);
}
SessionInMemory sessionInMemory = new SessionInMemory();
sessionInMemory.setCreateTime(new Date());
sessionInMemory.setSession(s);
sessionMap.put(sessionId, sessionInMemory);
}
private Session getSessionFromThreadLocal(Serializable sessionId) {
Session s = null;
if (sessionsInThread.get() == null) {
return null;
}
Map<Serializable, SessionInMemory> sessionMap = (Map<Serializable, SessionInMemory>) sessionsInThread.get();
SessionInMemory sessionInMemory = sessionMap.get(sessionId);
if (sessionInMemory == null) {
return null;
}
Date now = new Date();
long duration = now.getTime() - sessionInMemory.getCreateTime().getTime();
if (duration < sessionInMemoryTimeout) {
s = sessionInMemory.getSession();
logger.debug("read session from memory");
} else {
sessionMap.remove(sessionId);
}
return s;
}
private String getRedisSessionKey(Serializable sessionId) {
return this.keyPrefix + sessionId;
}
public IRedisManager getRedisManager() {
return redisManager;
}
public void setRedisManager(IRedisManager redisManager) {
this.redisManager = redisManager;
}
public String getKeyPrefix() {
return keyPrefix;
}
public void setKeyPrefix(String keyPrefix) {
this.keyPrefix = keyPrefix;
}
public RedisSerializer getKeySerializer() {
return keySerializer;
}
public void setKeySerializer(RedisSerializer keySerializer) {
this.keySerializer = keySerializer;
}
public RedisSerializer getValueSerializer() {
return valueSerializer;
}
public void setValueSerializer(RedisSerializer valueSerializer) {
this.valueSerializer = valueSerializer;
}
public long getSessionInMemoryTimeout() {
return sessionInMemoryTimeout;
}
public void setSessionInMemoryTimeout(long sessionInMemoryTimeout) {
this.sessionInMemoryTimeout = sessionInMemoryTimeout;
}
public int getExpire() {
return expire;
}
public void setExpire(int expire) {
this.expire = expire;
}
}
我来继承一下它然后改造下它,
下面是我写的继承类RedisSessionKey
package com.crsri.config;
import java.io.Serializable;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import javax.annotation.Resource;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.UnknownSessionException;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.support.DefaultSubjectContext;
import org.crazycake.shiro.IRedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.crazycake.shiro.SessionInMemory;
import org.crazycake.shiro.exception.SerializationException;
import org.crazycake.shiro.serializer.ObjectSerializer;
import org.crazycake.shiro.serializer.RedisSerializer;
import org.crazycake.shiro.serializer.StringSerializer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate;
import com.crsri.domain.system.User;
public class RedisSessionKey extends RedisSessionDAO{
@Resource
private RedisTemplate<String, Object>redisTemplate;
private static Logger logger = LoggerFactory.getLogger(RedisSessionDAO.class);
private static final String DEFAULT_SESSION_KEY_PREFIX = "shiro:session:";
private String keyPrefix = DEFAULT_SESSION_KEY_PREFIX;
private static final long DEFAULT_SESSION_IN_MEMORY_TIMEOUT = 1000L;
/**
* doReadSession be called about 10 times when login.
* Save Session in ThreadLocal to resolve this problem. sessionInMemoryTimeout is expiration of Session in ThreadLocal.
* The default value is 1000 milliseconds (1s).
* Most of time, you don't need to change it.
*/
private long sessionInMemoryTimeout = DEFAULT_SESSION_IN_MEMORY_TIMEOUT;
// expire time in seconds
private static final int DEFAULT_EXPIRE = -2;
private static final int NO_EXPIRE = -1;
/**
* Please make sure expire is longer than sesion.getTimeout()
*/
private int expire = DEFAULT_EXPIRE;
private static final int MILLISECONDS_IN_A_SECOND = 1000;
private IRedisManager redisManager;
private RedisSerializer keySerializer = new StringSerializer();
private RedisSerializer valueSerializer = new ObjectSerializer();
private static ThreadLocal sessionsInThread = new ThreadLocal();
@Override
public void update(Session session) throws UnknownSessionException {
String userID = null;
//通过session获取Principal对象
PrincipalCollection principal =(PrincipalCollection)(session.getAttribute((DefaultSubjectContext.PRINCIPALS_SESSION_KEY)));
//如果principal不为null,说明其现在可以转换成User
if(principal!=null) {
//根据session来获得sessionId的信息,将其转换成字符串
String id = session.getId().toString();
User user=(User)principal.getPrimaryPrincipal();
userID = user.getUserID();
//生成一个redis的key,value将其保存到redis里面,30分钟,之后每次改变的时候与sessionId同步保存30分钟
/**if(redisTemplate.hasKey(userID)) {
String sessionId = redisTemplate.opsForValue().get(userID).toString();
if(!("0".equals(sessionId))) {
this.redisTemplate.opsForValue().set(userID, id,1800,TimeUnit.SECONDS);
}else {
this.redisTemplate.opsForValue().set(userID, "0",1800,TimeUnit.SECONDS);
}
}else {
this.redisTemplate.opsForValue().set(userID, id,1800,TimeUnit.SECONDS);
}**/
if(!(redisTemplate.hasKey(userID))){
this.redisTemplate.opsForValue().set(userID, id,1800,TimeUnit.SECONDS);
}
}
this.saveSession(session);
}
/**
* save session
* @param session
* @throws UnknownSessionException
*/
private void saveSession(Session session) throws UnknownSessionException {
if (session == null || session.getId() == null) {
logger.error("session or session id is null");
throw new UnknownSessionException("session or session id is null");
}
byte[] key;
byte[] value;
try {
key = keySerializer.serialize(getRedisSessionKey(session.getId()));
value = valueSerializer.serialize(session);
} catch (SerializationException e) {
logger.error("serialize session error. session id=" + session.getId());
throw new UnknownSessionException(e);
}
if (expire == DEFAULT_EXPIRE) {
this.redisManager.set(key, value, (int) (session.getTimeout() / MILLISECONDS_IN_A_SECOND));
return;
}
if (expire != NO_EXPIRE && expire * MILLISECONDS_IN_A_SECOND < session.getTimeout()) {
logger.warn("Redis session expire time: "
+ (expire * MILLISECONDS_IN_A_SECOND)
+ " is less than Session timeout: "
+ session.getTimeout()
+ " . It may cause some problems.");
}
this.redisManager.set(key, value, expire);
}
@Override
public void delete(Session session) {
logger.info("*****redis删除session");
if (session == null || session.getId() == null) {
logger.error("session or session id is null");
return;
}
try {
redisManager.del(keySerializer.serialize(getRedisSessionKey(session.getId())));
} catch (SerializationException e) {
logger.error("delete session error. session id=" + session.getId());
}
}
@Override
public Collection<Session> getActiveSessions() {
Set<Session> sessions = new HashSet<Session>();
try {
Set<byte[]> keys = redisManager.keys(this.keySerializer.serialize(this.keyPrefix + "*"));
if (keys != null && keys.size() > 0) {
for (byte[] key:keys) {
Session s = (Session) valueSerializer.deserialize(redisManager.get(key));
sessions.add(s);
}
}
} catch (SerializationException e) {
logger.error("get active sessions error.");
}
return sessions;
}
@Override
protected Serializable doCreate(Session session) {
logger.info("*****redis创建session");
if (session == null) {
logger.error("session is null");
throw new UnknownSessionException("session is null");
}
Serializable sessionId = this.generateSessionId(session);
this.assignSessionId(session, sessionId);
this.saveSession(session);
return sessionId;
}
@Override
protected Session doReadSession(Serializable sessionId) {
if (sessionId == null) {
logger.warn("session id is null");
return null;
}
Session s = getSessionFromThreadLocal(sessionId);
if (s != null) {
return s;
}
logger.debug("read session from redis");
try {
s = (Session) valueSerializer.deserialize(redisManager.get(keySerializer.serialize(getRedisSessionKey(sessionId))));
setSessionToThreadLocal(sessionId, s);
} catch (SerializationException e) {
logger.error("read session error. settionId=" + sessionId);
}
return s;
}
private void setSessionToThreadLocal(Serializable sessionId, Session s) {
Map<Serializable, SessionInMemory> sessionMap = (Map<Serializable, SessionInMemory>) sessionsInThread.get();
if (sessionMap == null) {
sessionMap = new HashMap<Serializable, SessionInMemory>();
sessionsInThread.set(sessionMap);
}
SessionInMemory sessionInMemory = new SessionInMemory();
sessionInMemory.setCreateTime(new Date());
sessionInMemory.setSession(s);
sessionMap.put(sessionId, sessionInMemory);
}
private Session getSessionFromThreadLocal(Serializable sessionId) {
Session s = null;
if (sessionsInThread.get() == null) {
return null;
}
Map<Serializable, SessionInMemory> sessionMap = (Map<Serializable, SessionInMemory>) sessionsInThread.get();
SessionInMemory sessionInMemory = sessionMap.get(sessionId);
if (sessionInMemory == null) {
return null;
}
Date now = new Date();
long duration = now.getTime() - sessionInMemory.getCreateTime().getTime();
if (duration < sessionInMemoryTimeout) {
s = sessionInMemory.getSession();
logger.debug("read session from memory");
} else {
sessionMap.remove(sessionId);
}
return s;
}
private String getRedisSessionKey(Serializable sessionId) {
return this.keyPrefix + sessionId;
}
public IRedisManager getRedisManager() {
return redisManager;
}
public void setRedisManager(IRedisManager redisManager) {
this.redisManager = redisManager;
}
public String getKeyPrefix() {
return keyPrefix;
}
public void setKeyPrefix(String keyPrefix) {
this.keyPrefix = keyPrefix;
}
public RedisSerializer getKeySerializer() {
return keySerializer;
}
public void setKeySerializer(RedisSerializer keySerializer) {
this.keySerializer = keySerializer;
}
public RedisSerializer getValueSerializer() {
return valueSerializer;
}
public void setValueSerializer(RedisSerializer valueSerializer) {
this.valueSerializer = valueSerializer;
}
public long getSessionInMemoryTimeout() {
return sessionInMemoryTimeout;
}
public void setSessionInMemoryTimeout(long sessionInMemoryTimeout) {
this.sessionInMemoryTimeout = sessionInMemoryTimeout;
}
public int getExpire() {
return expire;
}
public void setExpire(int expire) {
this.expire = expire;
}
}
下面是核心代码
主要就是这做拦截后的初始化
然后我们看下手动删除的业务代码,这里禁止用户状态设置为‘0’,如果允许后就在redis中把这个key,value删掉
@Log(“更新用户”)
@RequestMapping(value="/updateUser",method=RequestMethod.PUT)
@ResponseBody
public ApiResponse updateUser(@RequestBody User user) {
if(ADMINID.equals(user.getUserID())) {
if(!(ADMINNAME.equals(user.getUsername()))) {
return ApiResponse.ofMsg(208,“admin名字不许被更改”);
}
}
if(user.getPassword()!=null && !"".equals(user.getPassword())){
String hashedPassword = Encryptiontools.encryptPassword(user.getPassword());//加密密码
user.setPassword(hashedPassword);//设置密文密码
};
userService.updateUserById(user);
Integer userStatus = user.getUserStatus();
String userID = user.getUserID();
Boolean hasKey = redisTemplate.hasKey(userID);
if(0==userStatus) {
//String sessionId = redisTemplate.opsForValue().get(userID).toString();
//redisTemplate.delete(“shiro:session:”+sessionId);
redisTemplate.opsForValue().set(userID, “0”,1800,TimeUnit.SECONDS);
}else {
if(hasKey) {
redisTemplate.delete(userID);
}
}
ShiroUtil.clearPermsCache();
setLogInfo(Update, “修改用户’”+user.getUsername()+"'密码成功",null);
return ApiResponse.ofMsg(200,“操作成功”);
}
核心代码是下面
这样我们的userId设置的key就指向value为0了
下面我们来重写下shiro的权限校验规则
下面
核心代码在刚刚的shiroconfigurantion里面
我们创建一个类继承UserFilter当然也可以继承大家喜欢用的AccessControlFilter
下面是改造后的代码
package com.crsri.config;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authc.UserFilter;
import org.springframework.data.redis.core.RedisTemplate;
import com.alibaba.fastjson.JSONObject;
import com.crsri.controller.response.ApiResponse;
import com.crsri.domain.system.User;
public class ShiroLoginFilter extends UserFilter{
private String sessionId;
private RedisTemplate<String, ?> redisTemplate;
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
// TODO Auto-generated method stub
if (redisTemplate == null) {
redisTemplate = (RedisTemplate) ShiroSpringUtils.getBean("redisTemplate");
}
Subject subject = getSubject(request, response);
boolean authenticated = subject.isAuthenticated();
if (subject.isAuthenticated()) {
User user =(User) (subject.getPrincipal());
String userID = user.getUserID();
sessionId = redisTemplate.opsForValue().get(userID).toString();
if("0".equals(sessionId)) {
return false;
}
}
return super.isAccessAllowed(request, response, mappedValue);
}
@Override
protected boolean onAccessDenied(ServletRequest request,ServletResponse response) throws Exception {
HttpServletRequest req= (HttpServletRequest) request;
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
if("0".equals(sessionId)) {
Subject subject = SecurityUtils.getSubject();
subject.logout();
//httpServletResponse.sendRedirect("/login.html");
}
if(isAjax(req)){//如果是ajax请求返回状态码
httpServletResponse.setCharacterEncoding("UTF-8");
httpServletResponse.setContentType("application/json");
httpServletResponse.getWriter().write(JSONObject.toJSON(ApiResponse.ofMsg(403,"您还未登录,重新登录")).toString());
httpServletResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);
}else{
/**
* @Mark 非ajax请求重定向为登录页面
*/
httpServletResponse.sendRedirect("/login.html");
}
return false;
}
public boolean isAjax(HttpServletRequest request){
String header = request.getHeader("X-Requested-With");
boolean isAjax = "XMLHttpRequest".equalsIgnoreCase(header) ? true:false;
return isAjax;
}
}
核心代码解析: