Springboot統合shiro,redisキャッシュセッション


Spring Boot+RedisによるShiroクラスタの実現
Webアプリケーションの分散クラスタ配置を実現するためには,ログインセッションの統一を解決する.本稿では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 {
    private Converter serializer = new SerializingConverter();
    private Converter 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 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アクセスセッションを実現する
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