Spring Boot + Redis 实现Shiro集群
为实现Web应用的分布式集群部署,要解决登录session的统一。本文利用shiro做权限控制,redis做session存储,结合spring boot快速配置实现session共享。
1、引入相关依赖
org.springframework.boot
spring-boot-starter-data-redis
org.apache.shiro
shiro-spring
1.3.2
2、Redis相关
2.1.redis配置
spring.redis.host=localhost
spring.redis.port=6379
spring.redis.password=
2.2.redis缓存的对象必须序列化,通用序列化
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.serializer.support.DeserializingConverter;
import org.springframework.core.serializer.support.SerializingConverter;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;
/**
* redis序列化对象
*/
public class RedisObjectSerializer implements RedisSerializer
2.3 RedisTemplate 配置
package com.st.redis.conf;
import java.lang.reflect.Method;
import org.springframework.cache.CacheManager;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
@Configuration
public class RedisConfig{
/**
* 生成key的策略
* @return
*/
@Bean
public KeyGenerator keyGenerator() {
return new KeyGenerator() {
@Override
public Object generate(Object target, Method method, Object... params) {
StringBuilder sb = new StringBuilder();
sb.append(target.getClass().getName());
sb.append(method.getName());
for (Object obj : params) {
sb.append(obj.toString());
}
return sb.toString();
}
};
}
/**
* 管理缓存
*/
@SuppressWarnings("rawtypes")
@Bean
public CacheManager cacheManager(RedisTemplate redisTemplate) {
RedisCacheManager rcm = new RedisCacheManager(redisTemplate);
return rcm;
}
/**
* RedisTemplate配置
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
@Bean
@Primary
public RedisTemplate redisTemplate(RedisConnectionFactory factory) {
StringRedisTemplate template = new StringRedisTemplate(factory);
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
template.setValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
@Bean("redisTemplateObj")
public RedisTemplate redisTemplateObj(RedisConnectionFactory factory) {
RedisTemplate template = new RedisTemplate();
template.setConnectionFactory(factory);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new RedisObjectSerializer());
return template;
}
}
3.Redis实现shiro的SessionDao存取session
package com.st.shiro.dao;
import java.io.Serializable;
import java.util.concurrent.TimeUnit;
import javax.annotation.Resource;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import com.st.shiro.conf.STShiroConf;
import com.st.shiro.constant.RedisConstant;
@Component
public class RedisSessionDAO extends EnterpriseCacheSessionDAO {
private static Logger logger = LoggerFactory.getLogger(RedisSessionDAO.class);
@Autowired
private STShiroConf sTShiroConf;
private static String prefix = RedisConstant.SHIRO_SESSION+":";
@Resource(name="redisTemplateObj")
private RedisTemplate redisTemplate;
// 创建session,保存到数据库
@Override
protected Serializable doCreate(Session session) {
Serializable sessionId = super.doCreate(session);
logger.debug("创建session:{}", session.getId());
redisTemplate.opsForValue().set(prefix + sessionId.toString(), session,sTShiroConf.getSessionTimeout(),TimeUnit.SECONDS);
return sessionId;
}
// 获取session
@Override
protected Session doReadSession(Serializable sessionId) {
logger.debug("获取session:{}", sessionId);
// 先从缓存中获取session,如果没有再去数据库中获取
Session session = super.doReadSession(sessionId);
if (session == null) {
session = (Session) redisTemplate.opsForValue().get(prefix + sessionId.toString());
}
return session;
}
// 更新session的最后一次访问时间
@Override
protected void doUpdate(Session session) {
super.doUpdate(session);
logger.debug("获取session:{}", session.getId());
String key = prefix + session.getId().toString();
if (!redisTemplate.hasKey(key)) {
redisTemplate.opsForValue().set(key, session);
}
redisTemplate.expire(key, sTShiroConf.getSessionTimeout(), TimeUnit.SECONDS);
}
// 删除session
@Override
protected void doDelete(Session session) {
logger.debug("删除session:{}", session.getId());
super.doDelete(session);
redisTemplate.delete(prefix + session.getId().toString());
}
}
4.实现cache共享
package com.st.shiro.cache;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import javax.annotation.Resource;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.springframework.data.redis.core.RedisTemplate;
import com.st.shiro.constant.RedisConstant;
public class ShiroCache implements Cache {
private static final String REDIS_SHIRO_CACHE =RedisConstant.SHIRO_CACHE;
private String cacheKey;
@Resource(name="redisTemplateObj")
private RedisTemplate redisTemplate;
private long globExpire = 1800;
@SuppressWarnings({ "rawtypes", "unchecked" })
public ShiroCache(String name, RedisTemplate redisTemplate) {
this.cacheKey = REDIS_SHIRO_CACHE+":"+ name + ":";
this.redisTemplate = redisTemplate;
}
@SuppressWarnings({ "rawtypes", "unchecked" })
public ShiroCache(String name, RedisTemplate redisTemplate,long globExpire) {
this.cacheKey = REDIS_SHIRO_CACHE+":"+ name + ":";
this.redisTemplate = redisTemplate;
this.globExpire=globExpire;
}
@Override
public V get(K key) throws CacheException {
redisTemplate.boundValueOps(getCacheKey(key)).expire(globExpire, TimeUnit.MINUTES);
return redisTemplate.boundValueOps(getCacheKey(key)).get();
}
@Override
public V put(K key, V value) throws CacheException {
V old = get(key);
redisTemplate.boundValueOps(getCacheKey(key)).set(value);
return old;
}
@Override
public V remove(K key) throws CacheException {
V old = get(key);
redisTemplate.delete(getCacheKey(key));
return old;
}
@Override
public void clear() throws CacheException {
redisTemplate.delete(keys());
}
@Override
public int size() {
return keys().size();
}
@Override
public Set keys() {
return redisTemplate.keys(getCacheKey("*"));
}
@Override
public Collection values() {
Set set = keys();
List list = new ArrayList();
for (K s : set) {
list.add(get(s));
}
return list;
}
@SuppressWarnings("unchecked")
private K getCacheKey(Object k) {
return (K) (this.cacheKey + k);
}
}
实现shiro 的CacheManager
package com.st.shiro.cache;
import javax.annotation.Resource;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.apache.shiro.cache.CacheManager;
import org.springframework.data.redis.core.RedisTemplate;
public class RedisCacheManager implements CacheManager {
private long globExpire=1800;
public RedisCacheManager(){
}
public RedisCacheManager(Long globExpire){
}
@Resource(name="redisTemplateObj")
private RedisTemplate redisTemplate;
@Override
public Cache getCache(String name) throws CacheException {
return new ShiroCache(name, redisTemplate,globExpire);
}
}
5.配置
package com.st.shiro.conf;
import java.util.HashMap;
import java.util.Map;
import org.apache.shiro.session.mgt.SessionManager;
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.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.Cookie;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.annotation.Order;
import com.st.common.util.AssertUtil;
import com.st.shiro.cache.RedisCacheManager;
import com.st.shiro.dao.RedisSessionDAO;
import com.st.shiro.realm.MyShiroRealm;
/**
* shiro配置
* @author ming
*
*/
@Configuration
@Order(2)
public class ShiroConfiguration {
private static final Logger logger = LoggerFactory.getLogger(ShiroConfiguration.class);
/*@Bean
public EhCacheManager getEhCacheManager() {
EhCacheManager em = new EhCacheManager();
em.setCacheManagerConfigFile("classpath:ehcache-shiro.xml");
return em;
} */
@Bean
public MyShiroRealm myShiroRealm() {
MyShiroRealm myShiroRealm=new MyShiroRealm();
//myShiroRealm.setCacheManager(getEhCacheManager());
return myShiroRealm;
}
@Bean
public LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
@Bean
public RedisCacheManager redisCacheManager() {
return new RedisCacheManager();
}
@Bean
public RedisSessionDAO redisSessionDAO(){
return new RedisSessionDAO();
}
@Bean
public SessionManager sessionManager() {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
sessionManager.setSessionDAO(redisSessionDAO());
sessionManager.setGlobalSessionTimeout(getSTShiroConf().getSessionTimeout());
sessionManager.setCacheManager(redisCacheManager());
sessionManager.setDeleteInvalidSessions(true);//删除过期的session
sessionManager.setSessionIdCookieEnabled(true);
sessionManager.setSessionIdCookie(sessionIdCookie());
return sessionManager;
}
//设置cookie
@Bean
public Cookie sessionIdCookie(){
Cookie sessionIdCookie=new SimpleCookie("STID");
sessionIdCookie.setMaxAge(-1);
sessionIdCookie.setHttpOnly(true);
return sessionIdCookie;
}
@Bean
public DefaultWebSecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//设置realm
securityManager.setRealm(myShiroRealm());
// session管理器
securityManager.setSessionManager(sessionManager());
//设置cache
securityManager.setCacheManager(redisCacheManager());
return securityManager;
}
@Bean
public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor() {
AuthorizationAttributeSourceAdvisor aasa = new AuthorizationAttributeSourceAdvisor();
aasa.setSecurityManager(securityManager());
return new AuthorizationAttributeSourceAdvisor();
}
@Bean
public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator daap = new DefaultAdvisorAutoProxyCreator();
daap.setProxyTargetClass(true);
return daap;
}
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean() {
Map filterChainDefinitionMap = new HashMap();
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager());
shiroFilterFactoryBean.setLoginUrl(getSTShiroConf().getLoginView());
shiroFilterFactoryBean.setSuccessUrl(getSTShiroConf().getSuccessUrl());
logger.info("***************************从数据库读取权限规则,加载到shiroFilter中*****************************");
//不拦截的路径
if(!AssertUtil.isEmpty(this.getSTShiroConf().getSysanon())){
for(String str:this.getSTShiroConf().getSysanon()){
filterChainDefinitionMap.put(str, "anon");
logger.debug("shiro:["+str+":"+"anon"+"]");
}
}
filterChainDefinitionMap.put("/**", "authc");
//filterChainDefinitionMap.put("/**", "anon");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
/**
* st-shiro配置
* @return
*/
@ConfigurationProperties(prefix="st.shiro.conf")
@Bean("sTShiroConf")
@Primary
public STShiroConf getSTShiroConf(){
return new STShiroConf();
}
}
6、Realm
package com.st.shiro.realm;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.annotation.PostConstruct;
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.authc.UsernamePasswordToken;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import com.st.shiro.conf.STShiroConf;
import com.st.shiro.dto.CommentCodeVO;
import com.st.shiro.dto.SysUser;
import com.st.shiro.service.STShiroService;
public class MyShiroRealm extends AuthorizingRealm {
private static final Logger logger = LoggerFactory.getLogger(MyShiroRealm.class);
@Autowired
private STShiroService sTShiroService;
@Autowired
private STShiroConf sTShiroConf;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
logger.info("***************************执行Shiro权限认证******************************");
//获取当前登录输入的用户名,等价于(String) principalCollection.fromRealm(getName()).iterator().next();
String loginName = (String)super.getAvailablePrincipal(principalCollection);
//到数据库查是否有此对象
SysUser user=sTShiroService.getUserByUid(loginName);
if(user!=null){
//权限信息对象info,用来存放查出的用户的所有的角色(role)及权限(permission)
SimpleAuthorizationInfo info=new SimpleAuthorizationInfo();
//获取角色
Set roles=sTShiroService.getRoleByUserId(user.getId());
if(roles!=null){
Set roleSet=new HashSet();
List roleIds=new ArrayList();
for(CommentCodeVO vo:roles){
roleSet.add(vo.getCode());
roleIds.add(vo.getId());
}
//添加角色
info.setRoles(roleSet);
Set permission=sTShiroService.getPermissionsByRoleId(roleIds);
if(permission!=null){
info.addStringPermissions(permission);
}
}
return info;
}
// 返回null的话,就会导致任何用户访问被拦截的请求时,都会自动跳转到unauthorizedUrl指定的地址
return null;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
logger.info("***************************执行Shiro登录认证******************************");
//UsernamePasswordToken对象用来存放提交的登录信息
UsernamePasswordToken token=(UsernamePasswordToken) authenticationToken;
logger.info("验证当前Subject时获取到token为:"+token);
//查出是否有此用户
SysUser user=sTShiroService.getUserByUid(token.getUsername());
if(user!=null){
// 若存在,将此用户存放到登录认证info中,无需自己做密码对比,Shiro会为我们进行密码对比校验
return new SimpleAuthenticationInfo(user.getLoginAccount(), user.getLoginPass(),ByteSource.Util.bytes(user.getSalt()),getName());
}
return null;
}
/**
* 设置加密方式
*/
@PostConstruct
public void initCredentialsMatcher() {
HashedCredentialsMatcher matcher=new HashedCredentialsMatcher(sTShiroConf.getAlgorithmName());
matcher.setHashIterations(sTShiroConf.getHashIterations());
setCredentialsMatcher(matcher);
}
}
7、shiro配置(yml)
st:
#Shiro配置
shiro:
conf:
domain: .midea.com
cookiePath: /
successUrl: /index
loginView: /login
openToken: false
sessionTimeout: 1800000
algorithmName: md5
hashIterations: 5
#不拦截的路径
sysanon:
- /login
- /regist
#跨域配置
allowedOrigins:
- /**
8、STShiroConf 配置
package com.st.shiro.conf;
import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* shiro加密配置
* @author ming
*
*/
//@Component
@ConfigurationProperties(prefix="st.shiro.conf")
public class STShiroConf {
/**
* 机密算法名称
*/
private String algorithmName;
/**
* 加密迭代次数
*/
private int hashIterations;
/**
* 系统不拦截的路径
*/
private List sysanon;
/**
* 跨域配置
*/
private List allowedOrigins;
/**
* 登录路径
*/
private String loginView;
/**
* 成功跳转路径
*/
private String successUrl;
/**
* 是否开启token
*/
private boolean openToken;
/**
* session时效
*/
private Long sessionTimeout;
/**
* Cookie所属的网站域名
*/
private String domain;
/**
* 写Cookie的程序的访问路径是
*/
private String cookiePath;
/**
* cookie名称
*/
private String cookieName;
/**
* 设置cookie时效
*/
private int cookieMaxAge;
/**
* token名称
*/
private String tokenName;
/**
* 是否单点登录
*/
private boolean openSSO;
public String getAlgorithmName() {
return algorithmName;
}
public void setAlgorithmName(String algorithmName) {
this.algorithmName = algorithmName;
}
public int getHashIterations() {
return hashIterations;
}
public void setHashIterations(int hashIterations) {
this.hashIterations = hashIterations;
}
public List getSysanon() {
return sysanon;
}
public void setSysanon(List sysanon) {
this.sysanon = sysanon;
}
public List getAllowedOrigins() {
return allowedOrigins;
}
public void setAllowedOrigins(List allowedOrigins) {
this.allowedOrigins = allowedOrigins;
}
public String getLoginView() {
return loginView;
}
public void setLoginView(String loginView) {
this.loginView = loginView;
}
public String getSuccessUrl() {
return successUrl;
}
public void setSuccessUrl(String successUrl) {
this.successUrl = successUrl;
}
public boolean isOpenToken() {
return openToken;
}
public void setOpenToken(boolean openToken) {
this.openToken = openToken;
}
public Long getSessionTimeout() {
return sessionTimeout;
}
public void setSessionTimeout(Long sessionTimeout) {
this.sessionTimeout = sessionTimeout;
}
public String getDomain() {
return domain;
}
public void setDomain(String domain) {
this.domain = domain;
}
public String getCookiePath() {
return cookiePath;
}
public void setCookiePath(String cookiePath) {
this.cookiePath = cookiePath;
}
public String getCookieName() {
return cookieName;
}
public void setCookieName(String cookieName) {
this.cookieName = cookieName;
}
public int getCookieMaxAge() {
return cookieMaxAge;
}
public void setCookieMaxAge(int cookieMaxAge) {
this.cookieMaxAge = cookieMaxAge;
}
public String getTokenName() {
return tokenName;
}
public void setTokenName(String tokenName) {
this.tokenName = tokenName;
}
public boolean isOpenSSO() {
return openSSO;
}
public void setOpenSSO(boolean openSSO) {
this.openSSO = openSSO;
}
}
多次从redis获取session问题仍未解决,求各位大神给解决方案
组件项目源码链接:
http://download.csdn.net/download/qq_16055765/10247345