session是用来在服务器端保存用户登录信息的KV结构数据,用户在浏览器登录之后,服务器端生成sessionId,返回给浏览器端,浏览器下一次请求在Header的cookie中带上sessionId,服务器根据sessionId就可以获取用户的信息,从而进行登录鉴权等 *** 作。如果sessionId不存在,服务器端会返回一个新的sessionId。
2、分布式session在多个微服务部署的场景中,用户登录之后,从服务A生成session, 拿到sessionId,去请求服务B,服务B没有这个session, 就会出现鉴权不通过的情况。这时需要引入分布式session, 常见的是基于redis 的分布式session
需要引入一下依赖:
3、启动过程org.springframework.session spring-session-data-redisorg.springframework.boot spring-boot-starter-data-redis
核心注解是@EnableRedisHttpSession, 该注解定义了session有效时间、命名空间、刷新模式、定时清理cron表达式、保存模式等属性
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @documented @import(RedisHttpSessionConfiguration.class) @Configuration(proxyBeanMethods = false) public @interface EnableRedisHttpSession { // 过期时间,单位秒,默认30min int maxInactiveIntervalInSeconds() default MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS; // session命名空间,一应用一个,多个应用使用同一个redis,需要保持唯一 String redisNamespace() default RedisIndexedSessionRepository.DEFAULT_NAMESPACE; // redis session刷新模式 @Deprecated RedisFlushMode redisFlushMode() default RedisFlushMode.ON_SAVE; // redis session刷新模式默认是ON_SAVE,即调用SessionRepository#save(Session)之后才会刷新redis,IMMEDIATE:任何对session的更新都会刷新到redis FlushMode flushMode() default FlushMode.ON_SAVE; // session过期定时任务的cron表达式,默认每分钟运行 String cleanupCron() default RedisHttpSessionConfiguration.DEFAULT_CLEANUP_CRON; // session的保存模式,默认ON_SET_ATTRIBUTE:只保存对session的修改 SaveMode saveMode() default SaveMode.ON_SET_ATTRIBUTE; }
使用时可以新建配置文件,写入如下的内容(假设已经配置好redis,并使用lettuce)
@Configuration @EnableRedisHttpSession(maxInactiveIntervalInSeconds = 3600, redisNamespace = "xlt_namespace") public class RedisSessionConfig { @Bean public LettuceConnectionFactory redisConnectionFactory() { return new LettuceConnectionFactory(); } }
核心配置类是RedisHttpSessionConfiguration,redis 的session配置需要的信息主要是通过这个类完成。首先,setimportmetadata方法会根据EnableRedisHttpSession注解的配置,读取到spring session的相关配置信息
@Override @SuppressWarnings("deprecation") public void setimportmetadata(Annotationmetadata importmetadata) { // 获取EnableRedisHttpSession注解的配置信息 MapattributeMap = importmetadata .getAnnotationAttributes(EnableRedisHttpSession.class.getName()); // 拿到配置属性 AnnotationAttributes attributes = AnnotationAttributes.fromMap(attributeMap); // session失效时间 this.maxInactiveIntervalInSeconds = attributes.getNumber("maxInactiveIntervalInSeconds"); // 命名空间 String redisNamespacevalue = attributes.getString("redisNamespace"); if (StringUtils.hasText(redisNamespacevalue)) { this.redisNamespace = this.embeddedValueResolver.resolveStringValue(redisNamespacevalue); } // 刷新模式 FlushMode flushMode = attributes.getEnum("flushMode"); RedisFlushMode redisFlushMode = attributes.getEnum("redisFlushMode"); if (flushMode == FlushMode.ON_SAVE && redisFlushMode != RedisFlushMode.ON_SAVE) { flushMode = redisFlushMode.getFlushMode(); } this.flushMode = flushMode; // 保存模式 this.saveMode = attributes.getEnum("saveMode"); // session清理定时任务频率配置,cron表达式,默认是每分钟一次 "0 * * * * *" String cleanupCron = attributes.getString("cleanupCron"); if (StringUtils.hasText(cleanupCron)) { this.cleanupCron = cleanupCron; } }
然后是创建RedisIndexedSessionRepository,他主要是管理RedisSession对Redis数据库的 *** 作
@Bean public RedisIndexedSessionRepository sessionRepository() { // 创建redisTemplate,用来 *** 作redis,默认使用hset结构存储session信息 RedisTemplate
由于RedisHttpSessionConfiguration这个类继承了SpringHttpSessionConfiguration,在setimportmetadata方法完成之后,就会调用springSessionRepositoryFilter方法,新建SessionRepositoryFilter,HTTP的每个请求都要经过这个Filter的处理,校验其中的session信息。
@Bean publicSessionRepositoryFilter extends Session> springSessionRepositoryFilter(SessionRepositorysessionRepository) { SessionRepositoryFiltersessionRepositoryFilter = new SessionRepositoryFilter(sessionRepository); sessionRepositoryFilter.setHttpSessionIdResolver(this.httpSessionIdResolver); return sessionRepositoryFilter; }
SessionRepositoryFilter的doFilter方法实现如下:
@Order(-2147483598) public class SessionRepositoryFilterextends OncePerRequestFilter { protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { // 设置session属性 request.setAttribute(SESSION_REPOSITORY_ATTR, this.sessionRepository); // 打包请求 SessionRepositoryFilter.SessionRepositoryRequestWrapper wrappedRequest = new SessionRepositoryFilter.SessionRepositoryRequestWrapper(request, response); // 打包响应 SessionRepositoryFilter.SessionRepositoryResponseWrapper wrappedResponse = new SessionRepositoryFilter.SessionRepositoryResponseWrapper(wrappedRequest, response); try { // 执行下一个filter filterChain.doFilter(wrappedRequest, wrappedResponse); } finally { // 提交session wrappedRequest.commitSession(); } } }
然后初始化Redis消息监听器容器RedisMessageListenerContainer
@Configuration(proxyBeanMethods = false) public class RedisHttpSessionConfiguration extends SpringHttpSessionConfiguration implements BeanClassLoaderAware, EmbeddedValueResolverAware, importAware { @Bean public RedisMessageListenerContainer springSessionRedisMessageListenerContainer( RedisIndexedSessionRepository sessionRepository) { RedisMessageListenerContainer container = new RedisMessageListenerContainer(); //设置redis连接工厂 container.setConnectionFactory(this.redisConnectionFactory); // 设置任务执行器 if (this.redisTaskExecutor != null) { container.setTaskExecutor(this.redisTaskExecutor); } // 设置redis脚本执行器 if (this.redisSubscriptionExecutor != null) { container.setSubscriptionExecutor(this.redisSubscriptionExecutor); } // 添加消息监听器,监听session的删除、到期、创建事件 container.addMessageListener(sessionRepository, Arrays.asList(new ChannelTopic(sessionRepository.getSessionDeletedChannel()), new ChannelTopic(sessionRepository.getSessionExpiredChannel()))); container.addMessageListener(sessionRepository, Collections.singletonList(new PatternTopic(sessionRepository.getSessionCreatedChannelPrefix() + "*"))); return container; } }
接着开启Redis的键空间通知用来确保session的删除和到期会触发session销毁事件
@Configuration(proxyBeanMethods = false) public class RedisHttpSessionConfiguration extends SpringHttpSessionConfiguration implements BeanClassLoaderAware, EmbeddedValueResolverAware, importAware { @Bean public InitializingBean enableRedisKeyspaceNotificationsInitializer() { //开启Redis的键空间通知 return new EnableRedisKeyspaceNotificationsInitializer(this.redisConnectionFactory, this.configureRedisAction); } }
最后设置session事件监听适配器
@Order(-2147483598) public class SessionRepositoryFilter4、运行原理extends OncePerRequestFilter { @Bean public SessionEventHttpSessionListenerAdapter sessionEventHttpSessionListenerAdapter() { return new SessionEventHttpSessionListenerAdapter(this.httpSessionListeners); } }
配置了spring session之后,HTTP的每个请求都要经过SessionRepositoryFilter的处理,校验其中的session信息, 他的doFilterInternal方法定义如下:
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { // 设置session属性 request.setAttribute(SESSION_REPOSITORY_ATTR, this.sessionRepository); // 打包请求 SessionRepositoryFilter.SessionRepositoryRequestWrapper wrappedRequest = new SessionRepositoryFilter.SessionRepositoryRequestWrapper(request, response); // 打包响应 SessionRepositoryFilter.SessionRepositoryResponseWrapper wrappedResponse = new SessionRepositoryFilter.SessionRepositoryResponseWrapper(wrappedRequest, response); try { // 执行下一个filter filterChain.doFilter(wrappedRequest, wrappedResponse); } finally { // 提交session wrappedRequest.commitSession(); } }
其中,commitSession是进行session校验和持久化的关键
// 使用HttpSessionIdResolver把sessionId写入响应并且持久化session private void commitSession() { // 获取当前的包装session HttpSessionWrapper wrappedSession = getCurrentSession(); if (wrappedSession == null) { if (isInvalidateClientSession()) { SessionRepositoryFilter.this.httpSessionIdResolver.expireSession(this, this.response); } } else { // 获取session S session = wrappedSession.getSession(); // 清理请求session缓存,主要是把请求的sessionId等清空 clearRequestedSessionCache(); // 保存session SessionRepositoryFilter.this.sessionRepository.save(session); // 获取sessionId String sessionId = session.getId(); // 已请求的sessionId是否有效,并且是否和当前的sessionId if (!isRequestedSessionIdValid() || !sessionId.equals(getRequestedSessionId())) { // 不相等 // 生成新的sessionId返回 SessionRepositoryFilter.this.httpSessionIdResolver.setSessionId(this, this.response, sessionId); } } }
上面的保存session,会调用RedisIndexedSessionRepository的保存方法,将session信息保存到redis
@Override public void save(RedisSession session) { // 保存session session.save(); if (session.isNew) { // 新的session // 创建session缓存的Key String sessionCreatedKey = getSessionCreatedChannel(session.getId()); // 保存到redis this.sessionRedisOperations.convertAndSend(sessionCreatedKey, session.delta); session.isNew = false; } } private void save() { // 保存变更的sesssionId saveChangeSessionId(); // 保存属性 saveDelta(); } private void saveChangeSessionId() { // 获取当前的sessionId String sessionId = getId(); if (sessionId.equals(this.originalSessionId)) { // sessionId没有变 return; } if (!this.isNew) { // sessionId变了 // 原始key String originalSessionIdKey = getSessionKey(this.originalSessionId); // 新的key String sessionIdKey = getSessionKey(sessionId); try { // 更新redis中的值 RedisIndexedSessionRepository.this.sessionRedisOperations.rename(originalSessionIdKey, sessionIdKey); } catch (NonTransientDataAccessException ex) { handleErrNoSuchKeyError(ex); } // 原始过期key String originalExpiredKey = getExpiredKey(this.originalSessionId); // 新过期key String expiredKey = getExpiredKey(sessionId); try { // 更新过期key RedisIndexedSessionRepository.this.sessionRedisOperations.rename(originalExpiredKey, expiredKey); } catch (NonTransientDataAccessException ex) { handleErrNoSuchKeyError(ex); } } // 更新key this.originalSessionId = sessionId; } // 保存所有属性的变更并修改过期时间 private void saveDelta() { if (this.delta.isEmpty()) { return; } String sessionId = getId(); getSessionBoundHashOperations(sessionId).putAll(this.delta); String principalSessionKey = getSessionAttrNameKey( FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME); String securityPrincipalSessionKey = getSessionAttrNameKey(SPRING_SECURITY_CONTEXT); // 判断基本命名指数是否发生变化 if (this.delta.containsKey(principalSessionKey) || this.delta.containsKey(securityPrincipalSessionKey)) { if (this.originalPrincipalName != null) { String originalPrincipalRedisKey = getPrincipalKey(this.originalPrincipalName); RedisIndexedSessionRepository.this.sessionRedisOperations.boundSetOps(originalPrincipalRedisKey) .remove(sessionId); } Mapindexes = RedisIndexedSessionRepository.this.indexResolver.resolveIndexesFor(this); String principal = indexes.get(PRINCIPAL_NAME_INDEX_NAME); this.originalPrincipalName = principal; if (principal != null) { String principalRedisKey = getPrincipalKey(principal); RedisIndexedSessionRepository.this.sessionRedisOperations.boundSetOps(principalRedisKey) .add(sessionId); } } this.delta = new HashMap<>(this.delta.size()); // 过期时间=上一次访问时间+最大过期时间间隔 Long originalExpiration = (this.originalLastAccessTime != null) ? this.originalLastAccessTime.plus(getMaxInactiveInterval()).toEpochMilli() : null; // 持久化到redis RedisIndexedSessionRepository.this.expirationPolicy.onExpirationUpdated(originalExpiration, this); }
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)