springboot整合shiro,redis缓存session

Spring Boot + Redis 实现Shiro集群

为实现Web应用的分布式集群部署,要解决登录session的统一。本文利用shiro做权限控制,redis做session存储,结合spring boot快速配置实现session共享。

1、引入相关依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>        
<dependency>
	<groupId>org.apache.shiro</groupId>
	<artifactId>shiro-spring</artifactId>
	<version>1.3.2</version>
</dependency>

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<Object> {
    private Converter<Object, byte[]> serializer = new SerializingConverter();
    private Converter<byte[], Object> deserializer = new DeserializingConverter();
    static final byte[] EMPTY_ARRAY = new byte[0];

    public Object deserialize(byte[] bytes) {
        if (isEmpty(bytes)) {
            return null;
        }
        try {
            return deserializer.convert(bytes);
        } catch (Exception ex) {
            throw new SerializationException("Cannot deserialize", ex);
        }
    }

    public byte[] serialize(Object object) {
        if (object == null) {
            return EMPTY_ARRAY;
        }
        try {
            return serializer.convert(object);
        } catch (Exception ex) {
            return EMPTY_ARRAY;
        }
    }

    private boolean isEmpty(byte[] data) {
        return (data == null || data.length == 0);
    }
}

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<String, String> 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<Object, Object> redisTemplateObj(RedisConnectionFactory factory) {
        RedisTemplate<Object, Object> template = new RedisTemplate<Object, Object>();
        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<String, Object> 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<K, V> implements Cache<K, V> {

    private static final String REDIS_SHIRO_CACHE =RedisConstant.SHIRO_CACHE;
    private String cacheKey;
    @Resource(name="redisTemplateObj")
    private RedisTemplate<K, V> 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<K> keys() {
        return redisTemplate.keys(getCacheKey("*"));
    }

    @Override
    public Collection<V> values() {
        Set<K> set = keys();
        List<V> list = new ArrayList<V>();
        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<String, Object> redisTemplate;

    @Override
    public <K, V> Cache<K, V> getCache(String name) throws CacheException {
        return new ShiroCache<K, V>(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<String, String> filterChainDefinitionMap = new HashMap<String, String>();
        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<CommentCodeVO> roles=sTShiroService.getRoleByUserId(user.getId());
            
            if(roles!=null){
            	Set<String> roleSet=new HashSet<String>();
            	List<String> roleIds=new ArrayList<String>();
            	for(CommentCodeVO vo:roles){
            		roleSet.add(vo.getCode());
            		roleIds.add(vo.getId());
            	}
            	//添加角色
				info.setRoles(roleSet);
				
				Set<String> 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<String> sysanon;
	/**
	 * 跨域配置
	 */
	private List<String> 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<String> getSysanon() {
		return sysanon;
	}
	public void setSysanon(List<String> sysanon) {
		this.sysanon = sysanon;
	}
	public List<String> getAllowedOrigins() {
		return allowedOrigins;
	}
	public void setAllowedOrigins(List<String> 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

评论 15
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值