完全参考:https://www.cnblogs.com/guitu18/p/11262106.html
这主要用来做Shiro笔记用的
Shiro结合Redis实现Session共享
Shiro的登录也是基于Session的,默认情况下Session是保存在内存中。既然要做Session共享,那么肯定是将Session抽取出来,放到一个多个服务器都能访问到的地方。
在集群环境下,我们仅仅需要继承AbstractSessionDAO,实现一下Session的增删改查等几个方法就可以很方便的实现Session共享,Shiro已经将完整的流程都做好了。这里涉及到的设计模式是模板方法模式,我们仅需要参与部分业务就可以完善整个流程了,当然我们不参与这部分流程的话,Shiro也有默认的实现方式,那就是将Session管理在当前应用的内存中。
具体的Session管理(共享)怎么实现由我们自己决定,可以存放在数据库,也可以通过网络传输,甚至可以通过IO流写入文件都行,但就性能来讲,我们一般都将Session放入Redis中。
自定义RedisSessionDAO
继承AbstractSessionDAO后实现Session增删改查的几个方法,然后再分布式系统中所有的项目再需要存储或获取Session时都会走Redis操作,这样就做到了集群环境的Session共享了。
package com.qingfeng.springbootjspshiro.realm; import org.apache.shiro.session.Session; import org.apache.shiro.session.UnknownSessionException; import org.apache.shiro.session.mgt.eis.AbstractSessionDAO; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; import java.io.Serializable; import java.util.Collection; import java.util.concurrent.TimeUnit; /** * RedisSessionDao,以Redis持久化方式做Session共享,无需配置即可支持集群 */ @Component public class RedisSessionDao extends AbstractSessionDAO { @Value("${session.redis.expireTime}") private long expireTime; @Autowired private RedisTemplate redisTemplate; @Override protected Serializable doCreate(Session session) { Serializable sessionId = this.generateSessionId(session); this.assignSessionId(session, sessionId); redisTemplate.opsForValue().set(session.getId(), session, expireTime, TimeUnit.SECONDS); return sessionId; } @Override protected Session doReadSession(Serializable sessionId) { return sessionId == null ? null : (Session) redisTemplate.opsForValue().get(sessionId); } @Override public void update(Session session) throws UnknownSessionException { if (session != null && session.getId() != null) { session.setTimeout(expireTime * 1000); redisTemplate.opsForValue().set(session.getId(), session, expireTime, TimeUnit.SECONDS); } } @Override public void delete(Session session) { if (session != null && session.getId() != null) { redisTemplate.opsForValue().getOperations().delete(session.getId()); } } @Override public Collection<Session> getActiveSessions() { return redisTemplate.keys("*"); } }
application.properties配置文件
server.port=8888
server.servlet.context-path=/shiro
spring.application.name=shiro
###redis连接配置 spring.redis.host=127.0.0.1 spring.redis.port=6379 spring.redis.password=123456 ### Session过期时间(秒) session.redis.expireTime=3600
注入RedisSessionDao
上面只是我们自己实现的管理Session的方式,现在需要将其注入SessionManager中,并设置过期时间等相关参数。
/** * Session超时时间(秒) */ @Value("${session.redis.expireTime}") private long expireTime; @Bean("defaultWebSessionManager") public DefaultWebSessionManager defaultWebSessionManager(RedisSessionDao redisSessionDao) { DefaultWebSessionManager sessionManager = new DefaultWebSessionManager(); sessionManager.setGlobalSessionTimeout(expireTime * 1000); sessionManager.setDeleteInvalidSessions(true); sessionManager.setSessionDAO(redisSessionDao); sessionManager.setSessionValidationSchedulerEnabled(true); sessionManager.setDeleteInvalidSessions(true); /** * 修改Cookie中的SessionId的key,默认为JSESSIONID,自定义名称 */ sessionManager.setSessionIdCookie(new SimpleCookie("JSESSIONID")); return sessionManager; }
再将SessionManager注入Shiro的安全管理器SecurityManager中,前面说过,我们围绕安全相关的所有操作,都需要与SecurityManager打交道,这位才是Shiro中真正的老大哥。
// 2、创建安全管理器 @Bean("getSecurityManager") public DefaultWebSecurityManager getSecurityManager(@Qualifier("getRealm") Realm realm,RedisSessionDao redisSessionDao){ DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager(); //设置realm defaultWebSecurityManager.setRealm(realm); // 取消Cookie中的RememberMe参数 defaultWebSecurityManager.setRememberMeManager(null); defaultWebSecurityManager.setSessionManager(defaultWebSessionManager(redisSessionDao)); return defaultWebSecurityManager; }
完整的ShiroConfig配置类
package com.qingfeng.springbootjspshiro.config; import com.qingfeng.springbootjspshiro.realm.CustomerRealm; import com.qingfeng.springbootjspshiro.realm.RedisSessionDao; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.credential.HashedCredentialsMatcher; import org.apache.shiro.realm.CachingRealm; import org.apache.shiro.realm.Realm; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.apache.shiro.web.servlet.SimpleCookie; import org.apache.shiro.web.session.mgt.DefaultWebSessionManager; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.HashMap; import java.util.Map; /** * 用来整合shiro框架的相关配置 */ @Configuration public class ShiroConfig { // 1、创建shiroFilter 负责拦截所有请求 @Bean public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("getSecurityManager")DefaultWebSecurityManager securityManager){ //创建shiro的filter ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); //注入安全管理器 shiroFilterFactoryBean.setSecurityManager(securityManager); //配置系统的受限资源 Map<String ,String> map = new HashMap<>(); map.put("/user/login","anon"); //anon:设置为公共资源 map.put("/user/register","anon"); map.put("/register.jsp","anon"); map.put("/**","authc"); //authc:表示请求这个资源需要认证和授权 //配置系统的公共资源 //默认认证界面路径--->login.jsp shiroFilterFactoryBean.setLoginUrl("/login.jsp"); shiroFilterFactoryBean.setFilterChainDefinitionMap(map); return shiroFilterFactoryBean; } /** * Session超时时间(秒) */ @Value("${session.redis.expireTime}") private long expireTime; @Bean("defaultWebSessionManager") public DefaultWebSessionManager defaultWebSessionManager(RedisSessionDao redisSessionDao) { DefaultWebSessionManager sessionManager = new DefaultWebSessionManager(); sessionManager.setGlobalSessionTimeout(expireTime * 1000); sessionManager.setDeleteInvalidSessions(true); sessionManager.setSessionDAO(redisSessionDao); sessionManager.setSessionValidationSchedulerEnabled(true); sessionManager.setDeleteInvalidSessions(true); /** * 修改Cookie中的SessionId的key,默认为JSESSIONID,自定义名称 */ sessionManager.setSessionIdCookie(new SimpleCookie("JSESSIONID")); return sessionManager; } // 2、创建安全管理器 @Bean("getSecurityManager") public DefaultWebSecurityManager getSecurityManager(@Qualifier("getRealm") Realm realm,RedisSessionDao redisSessionDao){ DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager(); //设置realm defaultWebSecurityManager.setRealm(realm); // 取消Cookie中的RememberMe参数 defaultWebSecurityManager.setRememberMeManager(null); defaultWebSecurityManager.setSessionManager(defaultWebSessionManager(redisSessionDao)); return defaultWebSecurityManager; } // 3、创建自定义Realm @Bean("getRealm") public Realm getRealm(){ CustomerRealm customerRealm = new CustomerRealm(); //设置hashed凭证匹配器 HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher(); //设置md5加密 credentialsMatcher.setHashAlgorithmName("md5"); //设置散列次数 credentialsMatcher.setHashIterations(1024); customerRealm.setCredentialsMatcher(credentialsMatcher); return customerRealm; } }
这样Redis实现的Session共享就完成了
测试:SessionController
package com.qingfeng.springbootjspshiro.controller; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authz.annotation.RequiresRoles; import org.apache.shiro.subject.Subject; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class SessionController { //在业务逻辑中代码实现判断用户权限 //http://localhost:8888/shiro/testSession @RequiresRoles(value = {"admin"})////用来判断角色 同时具有 admin @RequestMapping("testSession") public String testSession(){ System.out.println("集群Session共享成功"); return "集群Session共享成功"; } }
springboot项目有时候需要进行集群,这时候其实就是同一个项目只是端口不一致在使用命令行得时候,只需要跟得参数调整一下即可
启动两个端口号为8888和端口号为8889的项目
进行登录成功
分别访问:http://localhost:8888/shiro/testSession和http://localhost:8889/shiro/testSession结果
它们的JSESSIONID值都是aad59a0d-7705-4c25-a70c-412df3ed45c5