springboot+nginx+shiro叢集共享session
阿新 • • 發佈:2019-02-10
今天在搭建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宕機的情況就不是很好處理了。