springboot+nginx+shiro集群共享session

  今天在搭建springboot+shiro+nginx的多服务器应用时,遇到了在一个服务器shiro认证通过之后另一个服务器没有认证shiro,所以在访问另一个服务器的时候会抛出shiro未认证的错误,后来发现是shiro中session没有共享的问题。

网上找了一些博客文档也描述的不是很清楚,因为我本身也是用的redis集群,所以在看到网上序列化session存储的时候就想到了将单机redis改redis集群来使用,结果反而还被自己坑了,当然也是博客上面描述的不是很清楚。这边整理了一下之后再分享一下这个坑的解决方法。

首先必须有springboot的一些基本配置以及springboot整合shiro的一些配置。这里还需要引入shiro-redis这个jar包

<!-- shiro+redis缓存插件 -->
<dependency>
	<groupId>org.crazycake</groupId>
	<artifactId>shiro-redis</artifactId>
	<version>2.8.24</version>
</dependency>

下面是shiroCofig的一些配置 由于shiroConfig类中LifecycleBeanPostProcessor这个bean会影响@Value注入属性,所以这边把该bean配置移到了@SpringbootApplication中


import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.apache.shiro.mgt.SecurityManager;
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.session.mgt.DefaultWebSessionManager;
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.MethodInvokingFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;

import club.pinea.school.shiro.ShiroDBRealm;
import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.JedisPool;

@Configuration
public class ShiroConfig {
	
	@Value("${spring.redis.cluster.nodes}")
	private String clusterNodes;

	@Bean
	public ShiroDBRealm shiroDbRealm() {
		return new ShiroDBRealm();
	}
	

	@Bean
	public JedisCluster jedisCluster() {
		// 截取集群节点
		String[] cluster = clusterNodes.split(",");
		// 创建set集合
		Set<HostAndPort> nodes = new HashSet<HostAndPort>();
		// 循环数组把集群节点添加到set集合中
		for (String node : cluster) {
			String[] host = node.split(":");
			// 添加集群节点
			nodes.add(new HostAndPort(host[0], Integer.parseInt(host[1])));
		}
		JedisCluster jc = new JedisCluster(nodes);
		return jc;
	}
	
	/**
	 * 权限管理,配置主要是Realm的管理认证
	 */
	@Bean
	public DefaultWebSecurityManager securityManager() {
		DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
		securityManager.setRealm(shiroDbRealm());
		// 自定义缓存实现 使用redis
        securityManager.setCacheManager(cacheManager());
        // 自定义session管理 使用redis
        securityManager.setSessionManager(SessionManager());
		return securityManager;
	}
	
	/**
     * shiro session的管理
     */
    public DefaultWebSessionManager SessionManager() {
        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        sessionManager.setSessionDAO(redisSessionDAO());
        return sessionManager;
    }
	
	/**
     * 配置shiro redisManager
     * 
     * @return
     */
    public RedisManager redisManager() {
        RedisManager redisManager = new RedisManager();
        JedisCluster cluster = jedisCluster();
        Iterator<Entry<String, JedisPool>> iterator = cluster.getClusterNodes().entrySet().iterator();
        JedisPool pool = null;
        if(iterator.hasNext()) {
        	pool = iterator.next().getValue();
        }
        redisManager.setJedisPool(pool);
        redisManager.setExpire(1800);// 配置过期时间
        // redisManager.setTimeout(timeout);
        // redisManager.setPassword(password);
        return redisManager;
    }

    /**
     * cacheManager 缓存 redis实现
     * 
     * @return
     */
    public RedisCacheManager cacheManager() {
        RedisCacheManager redisCacheManager = new RedisCacheManager();
        redisCacheManager.setRedisManager(redisManager());
        return redisCacheManager;
    }
    
    /**
     * RedisSessionDAO shiro sessionDao层的实现 通过redis
     */
    public RedisSessionDAO redisSessionDAO() {
        RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
        redisSessionDAO.setRedisManager(redisManager());
        return redisSessionDAO;
    }
    
    
	/**
	 * Filter工厂,设置对应的过滤条件和跳转条件
	 */
	@Bean
	public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
		ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
		shiroFilterFactoryBean.setSecurityManager(securityManager);
		Map<String, String> map = new HashMap<>();
		map.put("/test/**", "anon");//测试不需要认证
		map.put("/login/**", "anon");//登录接口不需要认证
		map.put("/noUser", "anon");//不需要认证
		// 对所有用户认证
		map.put("/**", "authc");
		// 登录
		shiroFilterFactoryBean.setLoginUrl("/noUser");
		// 首页
		shiroFilterFactoryBean.setSuccessUrl("/index");
		// 没有权限跳转的页面
		shiroFilterFactoryBean.setUnauthorizedUrl("/unauth");
		// 错误页面,认证不通过跳转
		shiroFilterFactoryBean.setUnauthorizedUrl("/error");
		shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
		return shiroFilterFactoryBean;
	}

	/**
	 * 加入注解的使用,不加入这个注解不生效
	 */
	@Bean
	public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
		AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
		authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
		return authorizationAttributeSourceAdvisor;
	}

	/**
	 * 在方法中 注入 securityManager,进行代理控制
	 * @return
	 */
	@Bean
	public MethodInvokingFactoryBean methodInvokingFactoryBean() {
		MethodInvokingFactoryBean methodInvokingFactoryBean = new MethodInvokingFactoryBean();
		methodInvokingFactoryBean.setStaticMethod("org.apache.shiro.SecurityUtils.setSecurityManager");
		methodInvokingFactoryBean.setArguments(securityManager());
		return methodInvokingFactoryBean;
	}

	@Bean
	@DependsOn("lifecycleBeanPostProcessor")
	public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
		return new DefaultAdvisorAutoProxyCreator();
	}

//	/**
//	 * 保证实现了Shiro内部lifecycle函数的bean执行
//   * 这边由于运行的时候会报springboot配置文件属性注入为空的问题,所以将该配置移到了springbootApplication中
//	 * @return
//	 */
//	@Bean
//	public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
//		return new LifecycleBeanPostProcessor();
//	}

	/**
	 * 启用shrio授权注解拦截方式
	 * @return
	 */
	@Bean
	public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
		AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
		authorizationAttributeSourceAdvisor.setSecurityManager(securityManager());
		return authorizationAttributeSourceAdvisor;
	}

}

这里是启动类SpringbootApplication的配置

import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@SpringBootApplication
@Configuration
@MapperScan("club.pinea.school.mapper")//mapper扫描配置
public class SchoolApplication {

	@Bean
	public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
		return new LifecycleBeanPostProcessor();
	}
	
	public static void main(String[] args) {
		SpringApplication.run(SchoolApplication.class, args);
	}
}

这边配置完之后再启动,终于解决了问题,但是还是有一些不好的地方是这个插件只能支持单机redis。并不能支持集群。我也联系了插件的作者,给他提了这方面的建议,也希望以后这个插件可以支持redis的集群支持。不然难免有可能出现单机redis宕机的情况就不是很好处理了。

猜你喜欢

转载自blog.csdn.net/qq_39226486/article/details/82228381
今日推荐