spring-session实现多策略同时支持cookie&header验证

引入依赖

    <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基本上就已经整合完成了

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值