spring-session实现分布式session共享及自定义sessionid
官方文档地址:http://projects.spring.io/spring-session/
http://docs.spring.io/spring-session/docs/current/reference/html5/guides/httpsession.html
Spring Session为企业级Java应用的session管理带来了革新,使得以下的功能更加容易实现:
- 将session所保存的状态卸载到特定的外部session存储中,如Redis、mongo或gemfire中,它们能够以独立于应用服务器的方式提供高质量的集群。
- 当用户使用WebSocket发送请求的时候,能够保持HttpSession处于活跃状态。
- 在非Web请求的处理代码中,能够访问session数据,比如在JMS消息的处理代码中。
- 支持每个浏览器上使用多个session,从而能够很容易地构建更加丰富的终端用户体验。
- 控制session id如何在客户端和服务器之间进行交换,这样的话就能很容易地编写Restful API,因为它可以从HTTP 头信息(或者参数)中获取session id,而不必再依赖于cookie。
话不多说,上集成代码吧(*^__^*) …
首先加入maven引用:
<dependency> <groupId>org.springframework.session</groupId> <artifactId>spring-session-data-redis</artifactId> <version>1.2.2.RELEASE</version> <type>pom</type> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>4.2.5.RELEASE</version> </dependency>
HttpSessionConfig:
//maxInactiveIntervalInSeconds为session过期时间,这里注意session过期时间配置在web.xml里面是不起作用的 @Configuration @EnableRedisHttpSession(maxInactiveIntervalInSeconds=999) public class HttpSessionConfig { //这里有个小坑,如果服务器用的是云服务器,不加这个会报错 @Bean public static ConfigureRedisAction configureRedisAction() { return ConfigureRedisAction.NO_OP; } //这里是reids连接配置 @Bean public JedisConnectionFactory connectionFactory() { JedisConnectionFactory connection = new JedisConnectionFactory(); connection.setPort(6379); connection.setHostName("127.0.0.1"); connection.setPassword("ps123456"); connection.setDatabase(8); return connection; } //session策略,这里配置的是Header方式(有提供Header,Cookie等方式),可自定义,后面会详细讲 @Bean public HttpSessionStrategy httpSessionStrategy() { return new HeaderHttpSessionStrategy(); } }
Initializer:
public class Initializer extends AbstractHttpSessionApplicationInitializer { }
到这里就可以把工程跑起来了,上面提到使用的session策略是HeaderHttpSessionStrategy,restful可以使用这种方式。看HeaderHttpSessionStrategy源码可以知道,HeaderHttpSessionStrategy实现了HttpSessionStrategy接口。哈~也就是说,如果要自定义策略的话,也实现HttpSessionStrategy就可以了。
getRequestedSessionId()方法,获取客户端传过来的sessionid,如果没有传spring会通过UUID的方式分配一个(这里springsession没有支持自定义sessionid),
然后onNewSession()和onInvalidateSession()就不用说了,创建和销毁嘛~
public class HeaderHttpSessionStrategy implements HttpSessionStrategy { private String headerName = "x-auth-token"; public String getRequestedSessionId(HttpServletRequest request) { return request.getHeader(this.headerName); } public void onNewSession(Session session, HttpServletRequest request, HttpServletResponse response) { response.setHeader(this.headerName, session.getId()); } public void onInvalidateSession(HttpServletRequest request, HttpServletResponse response) { response.setHeader(this.headerName, ""); } /** * The name of the header to obtain the session id from. Default is "x-auth-token". * * @param headerName the name of the header to obtain the session id from. */ public void setHeaderName(String headerName) { Assert.notNull(headerName, "headerName cannot be null"); this.headerName = headerName; } }
好啦,下面就来说自定义sessionid吧~
一些特别的鉴权场景,需要由应用层自己控制生成sessionid,那么先看看源码~
public final class MapSession implements ExpiringSession, Serializable { /** * Default {@link #setMaxInactiveIntervalInSeconds(int)} (30 minutes). */ public static final int DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS = 1800; private String id; private Map<String, Object> sessionAttrs = new HashMap<String, Object>(); private long creationTime = System.currentTimeMillis(); private long lastAccessedTime = this.creationTime; /** * Defaults to 30 minutes. */ private int maxInactiveInterval = DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS; /** * Creates a new instance with a secure randomly generated identifier. */ public MapSession() { this(UUID.randomUUID().toString()); } /** * Creates a new instance with the specified id. This is preferred to the default * constructor when the id is known to prevent unnecessary consumption on entropy * which can be slow. * * @param id the identifier to use */ public MapSession(String id) { this.id = id; }
看org.springframework.session.MapSession发现,新建session的时候,构造 方法默认就是使用UUID做id,并调用MapSession(String id)构造方法给id赋值。
在看org.springframework.session.SessionRepository接口:
public interface SessionRepository<S extends Session> { S createSession(); void save(S session); S getSession(String id); void delete(String id); }
发现并没有S createSession(String id);的方法,后面通过改源码的方式实现了自定义id的功能。加了一个S createSession(String id);的方法,并通过getRequestedSessionId();把id拿过来传参。这里就不详讲了,因为这种方式特别不友好。
Another way:
Another way:
可以自定义一个session策略,在用户id和sessionid之间加一个映射保存在redis,在onNewSession()创建映射关系,在getRequestedSessionId()时找用户id对应的sessionid返回。
下面是MyHttpSessionStrategy代码,HttpSessionConfig里面的httpSessionStrategy()改为自己写的就可以了。
public class MyHttpSessionStrategy implements HttpSessionStrategy { private final Logger logger = LoggerFactory.getLogger(WlwHttpSessionStrategy.class); //这用Qualifier注解,如果你的工程还集成了spring-data-redis,需要指定一下用哪一个 @Qualifier("sessionRedisTemplate") @Autowired private RedisTemplate redisTemplate; //过期时间,与session过期时间保持一致 private Long maxInactiveIntervalInSeconds = 999L; private String xxxRedisName = "spring:session:xxx:"; //当客户端没有传xxx参数的时候,避免创建多个无用的session占用redis空间 private String defaultSessionId = "default-sessionid"; /** * 客户端传过来的是xxx,需要通过xxx查找映射关系,拿到sessionid返回 */ public String getRequestedSessionId(HttpServletRequest request) { String xxx = request.getParameter("xxx"); ValueOperations<String, String> vops = redisTemplate.opsForValue(); if (xxx != null && !xxx.equals("")) { String sessionid = vops.get(xxxRedisName + xxx); if(sessionid!=null){ redisTemplate.expire(xxxRedisName + xxx, maxInactiveIntervalInSeconds, TimeUnit.SECONDS); } return sessionid; } else { return vops.get(xxxRedisName+defaultSessionId); } } /** * 创建session时,保存xxx和sessionid的映射关系 */ public void onNewSession(Session session, HttpServletRequest request, HttpServletResponse response) { String xxx = request.getParameter("xxx"); String sessionid = session.getId(); ValueOperations<String, String> vops = redisTemplate.opsForValue(); if (xxx != null && !xxx.equals("")) { //保存xxx和sessionid映射关系 vops.set(xxxRedisName + xxx, sessionid); redisTemplate.expire(xxxRedisName + xxx, maxInactiveIntervalInSeconds, TimeUnit.SECONDS); }else{ //没有传xxx时,保存为默认 vops.set(xxxRedisName+defaultSessionId, sessionid); redisTemplate.expire(xxxRedisName+defaultSessionId, maxInactiveIntervalInSeconds, TimeUnit.SECONDS); } } public void onInvalidateSession(HttpServletRequest request, HttpServletResponse response) { String xxx = request.getParameter("xxx"); redisTemplate.expire(xxxRedisName + xxx, 0, TimeUnit.SECONDS); } }
好了,完工了。。。