引入依赖
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session</artifactId>
<version>1.3.3.RELEASE</version>
</dependency>
增加spring-session.xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd">
<!-- tag::beans[] -->
<!--1-->
<context:annotation-config/>
<bean class="org.springframework.session.jdbc.config.annotation.web.http.JdbcHttpSessionConfiguration">
<!--session最长过期时间-->
<property name="maxInactiveIntervalInSeconds" value="7200"/>
<!--session策略-->
<property name="httpSessionStrategy" ref="headerAndCookieHttpSessionStrategy"/>
</bean>
<!--自定义实现类,同时支持header和cookie-->
<bean id="headerAndCookieHttpSessionStrategy" class="com.zhiyun.front.common.interceptor.HeaderAndCookieHttpSessionStrategy"/>
<!--2-->
<!-- <jdbc:embedded-database id="dataSource" database-name="ZYQJ_MGR" type="H2">
<jdbc:script location="classpath:org/springframework/session/jdbc/schema-oracle.sql" />
</jdbc:embedded-database>-->
<!--3-->
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<constructor-arg ref="dataSource"/>
</bean>
<!-- end::beans[] -->
</beans>
在整合完成spring-session后,官方默认要么只支持header策略,要么只支持cookie策略,然而在实际项目中,有些场景是不能只局限于cookie的,比如移动端某些前端框架,IOS等,而在浏览器端,使用cookie策略又显得非常的方便,所以有没有一种办法可以将cookie和header相结合达到理想化的效果呢?
在查阅了:
<!--使用header存储sessionID-->
<bean id="headerHttpSessionStrategy" class="org.springframework.session.web.http.HeaderHttpSessionStrategy"/>
<!--使用cookie存储sessionID-->
<bean id="cookieHttpSessionStrategy" class="org.springframework.session.web.http.CookieHttpSessionStrategy"/>
spring-session这两种策略的实现代码后(HeaderHttpSessionStrategy和CookieHttpSessionStrategy),我决定重新写一个实现类,将这两种策略综合起来
HeaderAndCookieHttpSessionStrategy.java代码如下:
package com.zhiyun.front.common.interceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.session.Session;
import org.springframework.session.web.http.*;
import org.springframework.util.Assert;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 同时支持header和cookie策略
*/
public class HeaderAndCookieHttpSessionStrategy implements MultiHttpSessionStrategy, HttpSessionManager {
private String headerName = "x-auth-token";
private static final String DEFAULT_DELIMITER = " ";
private static final String SESSION_IDS_WRITTEN_ATTR = CookieHttpSessionStrategy.class.getName().concat(".SESSIONS_WRITTEN_ATTR");
static final String DEFAULT_ALIAS = "0";
static final String DEFAULT_SESSION_ALIAS_PARAM_NAME = "_s";
private static final Pattern ALIAS_PATTERN = Pattern.compile("^[\\w-]{1,50}$");
private String sessionParam = "_s";
private CookieSerializer cookieSerializer = new DefaultCookieSerializer();
private String deserializationDelimiter = " ";
private String serializationDelimiter = " ";
public HeaderAndCookieHttpSessionStrategy() {
}
@Override
public String getRequestedSessionId(HttpServletRequest request) {
//获取请求头中的session信息
String header = request.getHeader(this.headerName);
if(header != null && !"".equals(header)){
return header;
}
//获取cookie中的session信息
Map<String, String> sessionIds = this.getSessionIds(request);
String sessionAlias = this.getCurrentSessionAlias(request);
return (String)sessionIds.get(sessionAlias);
}
public String getCurrentSessionAlias(HttpServletRequest request) {
if (this.sessionParam == null) {
return "0";
} else {
String u = request.getParameter(this.sessionParam);
if (u == null) {
return "0";
} else {
return !ALIAS_PATTERN.matcher(u).matches() ? "0" : u;
}
}
}
public String getNewSessionAlias(HttpServletRequest request) {
Set<String> sessionAliases = this.getSessionIds(request).keySet();
if (sessionAliases.isEmpty()) {
return "0";
} else {
long lastAlias = Long.decode("0");
Iterator var5 = sessionAliases.iterator();
while(var5.hasNext()) {
String alias = (String)var5.next();
long selectedAlias = this.safeParse(alias);
if (selectedAlias > lastAlias) {
lastAlias = selectedAlias;
}
}
return Long.toHexString(lastAlias + 1L);
}
}
private long safeParse(String hex) {
try {
return Long.decode("0x" + hex);
} catch (NumberFormatException var3) {
return 0L;
}
}
@Override
public void onNewSession(Session session, HttpServletRequest request, HttpServletResponse response) {
response.setHeader(this.headerName, session.getId());//将session信息设置到请求头中
Set<String> sessionIdsWritten = this.getSessionIdsWritten(request);
if (!sessionIdsWritten.contains(session.getId())) {
sessionIdsWritten.add(session.getId());
Map<String, String> sessionIds = this.getSessionIds(request);
String sessionAlias = this.getCurrentSessionAlias(request);
sessionIds.put(sessionAlias, session.getId());
String cookieValue = this.createSessionCookieValue(sessionIds);
this.cookieSerializer.writeCookieValue(new CookieSerializer.CookieValue(request, response, cookieValue));
}
}
private Set<String> getSessionIdsWritten(HttpServletRequest request) {
Set<String> sessionsWritten = (Set)request.getAttribute(SESSION_IDS_WRITTEN_ATTR);
if (sessionsWritten == null) {
sessionsWritten = new HashSet();
request.setAttribute(SESSION_IDS_WRITTEN_ATTR, sessionsWritten);
}
return (Set)sessionsWritten;
}
private String createSessionCookieValue(Map<String, String> sessionIds) {
if (sessionIds.isEmpty()) {
return "";
} else if (sessionIds.size() == 1 && sessionIds.keySet().contains("0")) {
return (String)sessionIds.values().iterator().next();
} else {
StringBuilder sb = new StringBuilder();
Iterator var3 = sessionIds.entrySet().iterator();
while(var3.hasNext()) {
Map.Entry<String, String> entry = (Map.Entry)var3.next();
String alias = (String)entry.getKey();
String id = (String)entry.getValue();
sb.append(alias);
sb.append(this.serializationDelimiter);
sb.append(id);
sb.append(this.serializationDelimiter);
}
sb.deleteCharAt(sb.length() - 1);
return sb.toString();
}
}
@Override
public void onInvalidateSession(HttpServletRequest request, HttpServletResponse response) {
response.setHeader(this.headerName, "");//将header中的session信息删除
Map<String, String> sessionIds = this.getSessionIds(request);
String requestedAlias = this.getCurrentSessionAlias(request);
sessionIds.remove(requestedAlias);
String cookieValue = this.createSessionCookieValue(sessionIds);
this.cookieSerializer.writeCookieValue(new CookieSerializer.CookieValue(request, response, cookieValue));
}
public void setSessionAliasParamName(String sessionAliasParamName) {
this.sessionParam = sessionAliasParamName;
}
public void setCookieSerializer(CookieSerializer cookieSerializer) {
Assert.notNull(cookieSerializer, "cookieSerializer cannot be null");
this.cookieSerializer = cookieSerializer;
}
/** @deprecated */
@Deprecated
public void setCookieName(String cookieName) {
DefaultCookieSerializer serializer = new DefaultCookieSerializer();
serializer.setCookieName(cookieName);
this.cookieSerializer = serializer;
}
public void setDeserializationDelimiter(String delimiter) {
this.deserializationDelimiter = delimiter;
}
public void setSerializationDelimiter(String delimiter) {
this.serializationDelimiter = delimiter;
}
public Map<String, String> getSessionIds(HttpServletRequest request) {
List<String> cookieValues = this.cookieSerializer.readCookieValues(request);
String sessionCookieValue = cookieValues.isEmpty() ? "" : (String)cookieValues.iterator().next();
Map<String, String> result = new LinkedHashMap();
StringTokenizer tokens = new StringTokenizer(sessionCookieValue, this.deserializationDelimiter);
if (tokens.countTokens() == 1) {
result.put("0", tokens.nextToken());
return result;
} else {
while(tokens.hasMoreTokens()) {
String alias = tokens.nextToken();
if (!tokens.hasMoreTokens()) {
break;
}
String id = tokens.nextToken();
result.put(alias, id);
}
return result;
}
}
public HttpServletRequest wrapRequest(HttpServletRequest request, HttpServletResponse response) {
request.setAttribute(HttpSessionManager.class.getName(), this);
return request;
}
public HttpServletResponse wrapResponse(HttpServletRequest request, HttpServletResponse response) {
return new HeaderAndCookieHttpSessionStrategy.MultiSessionHttpServletResponse(response, request);
}
public String encodeURL(String url, String sessionAlias) {
String encodedSessionAlias = this.urlEncode(sessionAlias);
int queryStart = url.indexOf("?");
boolean isDefaultAlias = "0".equals(encodedSessionAlias);
if (queryStart < 0) {
return isDefaultAlias ? url : url + "?" + this.sessionParam + "=" + encodedSessionAlias;
} else {
String path = url.substring(0, queryStart);
String query = url.substring(queryStart + 1, url.length());
String replacement = isDefaultAlias ? "" : "$1" + encodedSessionAlias;
query = query.replaceFirst("((^|&)" + this.sessionParam + "=)([^&]+)?", replacement);
String sessionParamReplacement = String.format("%s=%s", this.sessionParam, encodedSessionAlias);
if (!isDefaultAlias && !query.contains(sessionParamReplacement) && url.endsWith(query)) {
if (!query.endsWith("&") && query.length() != 0) {
query = query + "&";
}
query = query + sessionParamReplacement;
}
return path + "?" + query;
}
}
private String urlEncode(String value) {
try {
return URLEncoder.encode(value, "UTF-8");
} catch (UnsupportedEncodingException var3) {
throw new RuntimeException(var3);
}
}
class MultiSessionHttpServletResponse extends HttpServletResponseWrapper {
private final HttpServletRequest request;
MultiSessionHttpServletResponse(HttpServletResponse response, HttpServletRequest request) {
super(response);
this.request = request;
}
private String getCurrentSessionAliasFromUrl(String url) {
String currentSessionAliasFromUrl = null;
int queryStart = url.indexOf("?");
if (queryStart >= 0) {
String query = url.substring(queryStart + 1);
Matcher matcher = Pattern.compile(String.format("%s=([^&]+)", HeaderAndCookieHttpSessionStrategy.this.sessionParam)).matcher(query);
if (matcher.find()) {
currentSessionAliasFromUrl = matcher.group(1);
}
}
return currentSessionAliasFromUrl;
}
public String encodeRedirectURL(String url) {
String encodedUrl = super.encodeRedirectURL(url);
String currentSessionAliasFromUrl = this.getCurrentSessionAliasFromUrl(encodedUrl);
String alias = currentSessionAliasFromUrl != null ? currentSessionAliasFromUrl : HeaderAndCookieHttpSessionStrategy.this.getCurrentSessionAlias(this.request);
return HeaderAndCookieHttpSessionStrategy.this.encodeURL(encodedUrl, alias);
}
public String encodeURL(String url) {
String encodedUrl = super.encodeURL(url);
String currentSessionAliasFromUrl = this.getCurrentSessionAliasFromUrl(encodedUrl);
String alias = currentSessionAliasFromUrl != null ? currentSessionAliasFromUrl : HeaderAndCookieHttpSessionStrategy.this.getCurrentSessionAlias(this.request);
return HeaderAndCookieHttpSessionStrategy.this.encodeURL(encodedUrl, alias);
}
}
}
上述代码看似比较繁杂,但是其核心方法无非也就三个方法:
//获取sessionID
public String getRequestedSessionId(HttpServletRequest request) {
//获取请求头中的session信息
String header = request.getHeader(this.headerName);
if(header != null && !"".equals(header)){
return header;
}
//获取cookie中的session信息
Map<String, String> sessionIds = this.getSessionIds(request);
String sessionAlias = this.getCurrentSessionAlias(request);
return (String)sessionIds.get(sessionAlias);
}
//在session新创建时执行
public void onNewSession(Session session, HttpServletRequest request, HttpServletResponse response) {
response.setHeader(this.headerName, session.getId());//将session信息设置到请求头中
Set<String> sessionIdsWritten = this.getSessionIdsWritten(request);
if (!sessionIdsWritten.contains(session.getId())) {
sessionIdsWritten.add(session.getId());
Map<String, String> sessionIds = this.getSessionIds(request);
String sessionAlias = this.getCurrentSessionAlias(request);
sessionIds.put(sessionAlias, session.getId());
String cookieValue = this.createSessionCookieValue(sessionIds);
this.cookieSerializer.writeCookieValue(new CookieSerializer.CookieValue(request, response, cookieValue));
}
}
//在session销毁时执行
public void onInvalidateSession(HttpServletRequest request, HttpServletResponse response) {
response.setHeader(this.headerName, "");//将header中的session信息删除
Map<String, String> sessionIds = this.getSessionIds(request);
String requestedAlias = this.getCurrentSessionAlias(request);
sessionIds.remove(requestedAlias);
String cookieValue = this.createSessionCookieValue(sessionIds);
this.cookieSerializer.writeCookieValue(new CookieSerializer.CookieValue(request, response, cookieValue));
}
在这三个方法中,我的改造也很简单,只要在操作cookie前,加上操作header的代码就可以了
我使用的spring-session是基于jdbc的,还需要创建相对应的表:
CREATE TABLE SPRING_SESSION (
SESSION_ID CHAR(36) NOT NULL,
CREATION_TIME NUMBER(19,0) NOT NULL,
LAST_ACCESS_TIME NUMBER(19,0) NOT NULL,
MAX_INACTIVE_INTERVAL NUMBER(10,0) NOT NULL,
PRINCIPAL_NAME VARCHAR2(100 CHAR),
CONSTRAINT SPRING_SESSION_PK PRIMARY KEY (SESSION_ID)
);
CREATE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (LAST_ACCESS_TIME);
CREATE INDEX SPRING_SESSION_IX2 ON SPRING_SESSION (PRINCIPAL_NAME);
CREATE TABLE SPRING_SESSION_ATTRIBUTES (
SESSION_ID CHAR(36) NOT NULL,
ATTRIBUTE_NAME VARCHAR2(200 CHAR) NOT NULL,
ATTRIBUTE_BYTES BLOB NOT NULL,
CONSTRAINT SPRING_SESSION_ATTRIBUTES_PK PRIMARY KEY (SESSION_ID, ATTRIBUTE_NAME),
CONSTRAINT SPRING_SESSION_ATTRIBUTES_FK FOREIGN KEY (SESSION_ID) REFERENCES SPRING_SESSION(SESSION_ID) ON DELETE CASCADE
);
CREATE INDEX SPRING_SESSION_ATTRIBUTES_IX1 ON SPRING_SESSION_ATTRIBUTES (SESSION_ID);
配置连接池:
<!-- 配置连接池 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
<property name="driverClass" value="${jdbc.driver}" />
<property name="jdbcUrl" value="${jdbc.url}" />
<property name="user" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<!--连接池保持的最小连接数-->
<property name="minPoolSize"><value>5</value></property>
<!--连接池保持的最大连接数-->
<property name="maxPoolSize"><value>100</value></property>
<!--连接的最大空闲时间,如果超过这个时间,某个数据库连接还没有被使用,则会断开掉这个连接如果为0,则永远不会断开连接-->
<property name="maxIdleTime"><value>1800</value></property>
<!--当连接池中的连接耗尽的时候c3p0一次同时获取的连接数。Default: 3 -->
<property name="acquireIncrement"><value>2</value></property>
<!--JDBC的标准参数,用以控制数据源内加载的PreparedStatements数量-->
<property name="maxStatements"><value>1000</value></property>
<!--连接池初始化时创建的连接数(介于maxPoolSize和minPoolSize之间)-->
<property name="initialPoolSize"><value>5</value></property>
<!--每隔几秒检查所有连接池中的空闲连接-->
<property name="idleConnectionTestPeriod"><value>300</value></property>
<!--定义在从数据库获取新连接失败后重复尝试的次数-->
<property name="acquireRetryAttempts"><value>30</value></property>
<!--获取连接失败将会引起所有等待连接池来获取连接的线程抛出异常。但是数据源仍有效 保留,并在下次调用getConnection()的时候继续尝试获取连接。如果设为true,那么在尝试 获取连接失败后该数据源将申明已断开并永久关闭-->
<property name="breakAfterAcquireFailure"><value>false</value></property>
<!--如果为true,在连接释放的同事将校验连接的有效性。-->
<property name="testConnectionOnCheckout"><value>true</value></property>
</bean>
在web.xml增加spring-session相应的配置
<filter>
<filter-name>springSessionRepositoryFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSessionRepositoryFilter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>ERROR</dispatcher>
</filter-mapping>
完成了上述配置之后,spring-session基本上就已经整合完成了