跳至主要內容

Session跨域、Spring-Session共享

wangdx大约 10 分钟

Session 跨域

  • 所谓 Session 跨域就是摒弃了系统(Tomcat)提供的 Session,而使用自定义的类似 Session 的机制来保存客户端数据的一种解决方案。
    • 如:通过设置 cookie 的 domain 来实现 cookie 的跨域传递。在 cookie 中传递一个自定义的 session_id。这个 session_id 是客户端的唯一标记。将这个标记作为 key,将客户端需要保存的数据作为 value,在服务端进行保存(数据库保存或 NoSQL 保存)。这种机制就是 Session 的跨域解决。
  • 什么跨域: 客户端请求的时候,请求的服务器,不是同一个 IP,端口,域名,主机名的时候,都称为跨域。
    • 什么是域:在应用模型,一个完整的,有独立访问路径的功能集合称为一个域。如:百度称为一个应用或系统。百度下有若干的域,如:搜索引擎(www.baidu.com),百度贴吧(tie.baidu.com),百度知道(zhidao.baidu.com),百度地图(map.baidu.com)等。域信息,有时也称为多级域名。域的划分: 以 IP,端口,域名,主机名为标准,实现划分。
  • localhost / 127.0.0.1
- 使用 cookie 跨域共享,是 session 跨域的一种解决方案。
- jsessionid 是和 servlet 绑定的 httpsession 的唯一标记。
- cookie 应用 - new Cookie("", "").
- request.getCookies() -> cookie[] -> 迭代找到需要使用的 cookie
- response.addCookie().
- cookie.setDomain() - 为 cookie 设定有效域范围。
- cookie.setPath() - 为 cookie 设定有效 URI 范围。

效果实现如下所示:

C:\Windows\System32\drivers\etc\host 配置文件配置一下地址映射。

sso test

192.168.0.102 www.test.com 192.168.0.102 sso.test.com 192.168.0.102 cart.test.com

package com.yix.action;

import com.yix.common.util.CookieUtils;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.UUID;

/**
 * @author wangdx
 */
@Controller
public class SsoController {
    /**
     * 请求控制层的方法
     *
     * @param request
     * @param response
     * @return
     */
    @RequestMapping("/sso")
    public String test(HttpServletRequest request, HttpServletResponse response) {
        // JSESSIONID代表了系统中http的唯一标记
        // custom_global_session_id。全局session的id
        String cookieName = "custom_global_session_id";
        String encodeString = "UTF-8";

        // 获取到cookie的value值
        String cookieValue = CookieUtils.getCookieValue(request, cookieName, encodeString);

        // 判断cookie的value值是否为空
        if (null == cookieValue || "".equals(cookieValue.trim())) {
            System.out.println("无cookie,生成新的cookie数据");
            // 生成一个cookie的value值
            cookieValue = UUID.randomUUID().toString();
        }

        // 根据cookieValue访问数据存储,获取客户端数据。
        // 将生产的cookie的value值设置到cookie中
        // 参数0代表关闭浏览器自动删除cookie.
        CookieUtils.setCookie(request, response, cookieName, cookieValue, 0, encodeString);

        return "/ok.jsp";
    }
}

package com.yix.common.util;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;

/**
 * @author wangdx
 */
public final class CookieUtils {
    /**
     * 得到Cookie的值, 不编码
     *
     * @param request
     * @param cookieName
     * @return
     */
    public static String getCookieValue(HttpServletRequest request, String cookieName) {
        return getCookieValue(request, cookieName, false);
    }

    /**
     * 得到Cookie的值,
     *
     * @param request
     * @param cookieName
     * @return
     */
    public static String getCookieValue(HttpServletRequest request, String cookieName, boolean isDecoder) {
        Cookie[] cookieList = request.getCookies();
        if (cookieList == null || cookieName == null) {
            return null;
        }
        String retValue = null;
        try {
            for (int i = 0; i < cookieList.length; i++) {
                if (cookieList[i].getName().equals(cookieName)) {
                    if (isDecoder) {
                        retValue = URLDecoder.decode(cookieList[i].getValue(), "UTF-8");
                    } else {
                        retValue = cookieList[i].getValue();
                    }
                    break;
                }
            }
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return retValue;
    }

    /**
     * 得到Cookie的值,
     *
     * @param request
     * @param cookieName
     * @return
     */
    public static String getCookieValue(HttpServletRequest request, String cookieName, String encodeString) {
        Cookie[] cookieList = request.getCookies();
        if (cookieList == null || cookieName == null) {
            return null;
        }
        String retValue = null;
        try {
            for (int i = 0; i < cookieList.length; i++) {
                if (cookieList[i].getName().equals(cookieName)) {
                    retValue = URLDecoder.decode(cookieList[i].getValue(), encodeString);
                    break;
                }
            }
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return retValue;
    }

    /**
     * 设置Cookie的值 不设置生效时间默认浏览器关闭即失效,也不编码
     */
    public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,
                                 String cookieValue) {
        setCookie(request, response, cookieName, cookieValue, -1);
    }

    /**
     * 设置Cookie的值 在指定时间内生效,但不编码
     */
    public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,
                                 String cookieValue, int cookieMaxage) {
        setCookie(request, response, cookieName, cookieValue, cookieMaxage, false);
    }

    /**
     * 设置Cookie的值 不设置生效时间,但编码
     */
    public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,
                                 String cookieValue, boolean isEncode) {
        setCookie(request, response, cookieName, cookieValue, -1, isEncode);
    }

    /**
     * 设置Cookie的值 在指定时间内生效, 编码参数
     */
    public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,
                                 String cookieValue, int cookieMaxage, boolean isEncode) {
        doSetCookie(request, response, cookieName, cookieValue, cookieMaxage, isEncode);
    }

    /**
     * 设置Cookie的值 在指定时间内生效, 编码参数(指定编码)
     */
    public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,
                                 String cookieValue, int cookieMaxage, String encodeString) {
        doSetCookie(request, response, cookieName, cookieValue, cookieMaxage, encodeString);
    }

    /**
     * 删除Cookie带cookie域名
     */
    public static void deleteCookie(HttpServletRequest request, HttpServletResponse response, String cookieName) {
        doSetCookie(request, response, cookieName, "", -1, false);
    }

    /**
     * 设置Cookie的值,并使其在指定时间内生效
     *
     * @param request      请求,请求对象,分析域信息
     * @param response     响应
     * @param cookieName   cookie的名称
     * @param cookieValue  cookie的值
     * @param cookieMaxage cookie生效的最大秒数。不做设定,关闭浏览器就无效了
     * @param isEncode     是否需要编码
     */
    private static final void doSetCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,
                                          String cookieValue, int cookieMaxage, boolean isEncode) {
        try {
            // 判断cookie的value值是否等于null
            if (cookieValue == null) {
                cookieValue = "";
            } else if (isEncode) {
                // 判断是否需要utf8编码
                cookieValue = URLEncoder.encode(cookieValue, "utf-8");
            }
            // 创建cookie,最好做非空判断的
            Cookie cookie = new Cookie(cookieName, cookieValue);
            if (cookieMaxage > 0) {
                // 如果cookie生效的最大秒数大于0,就设置这个值
                cookie.setMaxAge(cookieMaxage);
            }
            if (null != request) {
                // 分析解析域名
                String domainName = getDomainName(request);
                // 如果不等于localhost这个值,就设置一个domainName
                if (!"localhost".equals(domainName)) {
                    // 设置域名的cookie
                    cookie.setDomain(domainName);
                }
            }
            // 从根路径下的后面任意路径地址,cookie都有效
            cookie.setPath("/");
            // response响应写入到客户端即可
            response.addCookie(cookie);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 设置Cookie的值,并使其在指定时间内生效
     *
     * @param request
     * @param response
     * @param cookieName
     * @param cookieValue
     * @param cookieMaxage cookie生效的最大秒数
     * @param encodeString
     */
    private static final void doSetCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,
                                          String cookieValue, int cookieMaxage, String encodeString) {
        try {
            if (cookieValue == null) {
                cookieValue = "";
            } else {
                cookieValue = URLEncoder.encode(cookieValue, encodeString);
            }
            Cookie cookie = new Cookie(cookieName, cookieValue);
            if (cookieMaxage > 0)
                cookie.setMaxAge(cookieMaxage);
            if (null != request) {
                // 根据获取到的request请求,设置域名的cookie
                String domainName = getDomainName(request);
                if (!"localhost".equals(domainName)) {
                    // 设定一个域名。cookie就可以实现跨域访问了。
                    cookie.setDomain(domainName);
                }
            }
            cookie.setPath("/");
            response.addCookie(cookie);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 得到cookie的域名
     *
     * @param request 请求对象,包含了请求的信息
     * @return
     */
    private static final String getDomainName(HttpServletRequest request) {
        // 定义一个变量domainName
        String domainName = null;

        // 获取完整的请求URL地址。请求url,转换为字符串类型
        String serverName = request.getRequestURL().toString();
        // 判断如果请求url地址为空或者为null
        if (serverName == null || serverName.equals("")) {
            domainName = "";
        } else {
            // 不为空或者不为null,将域名转换为小写。域名不敏感的。大小写一样
            serverName = serverName.toLowerCase();
            // 判断开始如果以http://开头的
            if (serverName.startsWith("http://")) {
                // 截取前七位字符
                serverName = serverName.substring(7);
            } else if (serverName.startsWith("https://")) {
                // 否则如果开始以https://开始的。//截取前八位字符
                serverName = serverName.substring(8);
            }
            // 找到/开始的位置,可以判断end的值是否为-1,如果为-1的话说明没有这个值
            // 如果存在这个值,找到这个值的位置,否则返回值为-1
            final int end = serverName.indexOf("/");
            // .test.com www.test.com.cn/sso.test.com.cn/.test.com.cn spring.io/xxxx/xxx
            // 然后截取到0开始到/的位置
            if (end != -1) {
                // end等于-1。说明没有/。否则end不等于-1说明有这个值
                serverName = serverName.substring(0, end);
                // 这是将\\.是转义为.。然后进行分割操作。
                final String[] domains = serverName.split("\\.");
                // 获取到长度
                int len = domains.length;
                // 注意,如果是两截域名,一般没有二级域名。比如spring.io/xxx/xxx,都是在spring.io后面/拼接的
                // 如果是三截域名,都是以第一截为核心分割的。
                // 如果是两截的就保留。三截以及多截的就
                // 多截进行匹配域名规则,第一个相当于写*,然后加.拼接后面的域名地址
                if (len > 3) {
                    // 如果是大于等于3截的,去掉第一个点之前的。留下剩下的域名
                    domainName = "." + domains[len - 3] + "." + domains[len - 2] + "." + domains[len - 1];
                } else if (len <= 3 && len > 1) {
                    // 如果是2截和3截保留
                    domainName = "." + domains[len - 2] + "." + domains[len - 1];
                } else {
                    domainName = serverName;
                }
            }
        }
        // 如果域名不为空并且有这个:
        if (domainName != null && domainName.indexOf(":") > 0) {
            // 将:转义。去掉端口port号,cookie(cookie的domainName)和端口无关。只看域名的。
            String[] ary = domainName.split("\\:");
            domainName = ary[0];
        }
        // 返回域名
        System.out.println("==============================================" + domainName);
        return domainName;
    }

    public static void main(String[] args) {
        String serverName = "http://www.baidu.com/a";
        String domainName = null;
        // 判断如果请求url地址为空或者为null
        if (serverName == null || serverName.equals("")) {
            domainName = "";
        } else {
            // 不为空或者不为null,将域名转换为小写。域名不敏感的。大小写一样
            serverName = serverName.toLowerCase();
            // 判断开始如果以http://开头的
            if (serverName.startsWith("http://")) {
                // 截取前七位字符
                serverName = serverName.substring(7);
            } else if (serverName.startsWith("https://")) {
                // 否则如果开始以https://开始的。//截取前八位字符
                serverName = serverName.substring(8);
            }
            // 找到/开始的位置,可以判断end的值是否为-1,如果为-1的话说明没有这个值
            final int end = serverName.indexOf("/");
            // .test.com www.test.com.cn/sso.test.com.cn/.test.com.cn spring.io/xxxx/xxx
            // 然后截取到0开始到/的位置
            serverName = serverName.substring(0, end);
            final String[] domains = serverName.split("\\.");
            int len = domains.length;
            if (len > 3) {
                domainName = "." + domains[len - 3] + "." + domains[len - 2] + "." + domains[len - 1];
            } else if (len <= 3 && len > 1) {
                domainName = "." + domains[len - 2] + "." + domains[len - 1];
            } else {
                domainName = serverName;
            }
        }

        if (domainName != null && domainName.indexOf(":") > 0) {
            String[] ary = domainName.split("\\:");
            domainName = ary[0];
        }
    }
}

Spring Session 共享

spring-session 技术是 spring 提供的用于处理集群会话共享的解决方案。spring-session 技术是将用户 session 数据保存到三方存储容器中,如:mysql,redis 等。 Spring-session 技术是解决同域名下的多服务器集群 session 共享问题的。不能解决跨域 session 共享问题(如果要解决跨域 sesion,就要搭建前端服务器 nginx 来解决这个题)。 使用要求:

  配置一个 Spring 提供的 Filter,实现数据的拦截保存,并转换为 spring-session 需要的会话对象。必须提供一个数据库的表格信息(由 spring-session 提供,spring-session-jdbc.jar/org/springframework/session/jdbc/\*.sql,根据具体的数据库找对应的 SQL 文件,做表格的创建)。
  spring-session 表:保存客户端 session 对象的表格。
  spring-session-attributes 表:保存客户端 session 中的 attributes 属性数据的表格。
  spring-session 框架,是结合 Servlet 技术中的 HTTPSession 完成的会话共享机制。在代码中是直接操作 HttpSession 对象的。
 <?xml version="1.0" encoding="UTF-8"?>
 <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xmlns="http://java.sun.com/xml/ns/javaee"
     xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
     http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
     id="WebApp_ID" version="2.5">

     <display-name>sso-cross-domain</display-name>

     <welcome-file-list>
         <welcome-file>index.html</welcome-file>
     </welcome-file-list>

     <!-- 重要:spring-session的filter拦截器 完成数据转换的拦截器,spring提供的filter实现数据的拦截保存并转换为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>

     <!-- 字符集过滤器 -->
     <filter>
         <filter-name>charSetFilter</filter-name>
         <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
         <init-param>
             <param-name>encoding</param-name>
             <param-value>UTF-8</param-value>
         </init-param>
     </filter>
     <filter-mapping>
         <filter-name>charSetFilter</filter-name>
         <url-pattern>/*</url-pattern>
     </filter-mapping>

     <!-- 加载springmvc的配置文件 -->
     <servlet>
         <servlet-name>mvc</servlet-name>
         <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
         <init-param>
             <param-name>contextConfigLocation</param-name>
             <param-value>classpath:applicationContext-mvc.xml</param-value>
         </init-param>
         <load-on-startup>1</load-on-startup>
     </servlet>
     <servlet-mapping>
         <servlet-name>mvc</servlet-name>
         <url-pattern>/</url-pattern>
     </servlet-mapping>


 </web-app>
  <?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:mvc="http://www.springframework.org/schema/mvc"
      xmlns:context="http://www.springframework.org/schema/context"
      xmlns:dwr="http://directwebremoting.org/schema/spring-dwr/spring-dwr-3.0.xsd"
      xsi:schemaLocation="http://www.springframework.org/schema/beans
          http://www.springframework.org/schema/beans/spring-beans.xsd
          http://www.springframework.org/schema/mvc
          http://www.springframework.org/schema/mvc/spring-mvc.xsd
          http://www.springframework.org/schema/context
          http://www.springframework.org/schema/context/spring-context.xsd">

      <!-- 指定扫描的包 -->
      <context:component-scan base-package="com.bie.controller" />

      <!-- 为SpringMVC配置注解驱动 -->
      <mvc:annotation-driven />

      <!-- 为Spring基础容器开启注解配置信息 -->
      <context:annotation-config />
      <!-- 就是用于提供HttpSession数据持久化操作的Bean对象。
          对象定义后,可以实现数据库相关操作配置,自动的实现HttpSession数据的持久化操作(CRUD)
       -->
      <bean class="org.springframework.session.jdbc.config.annotation.web.http.JdbcHttpSessionConfiguration" />


      <bean id="dataSource"
          class="org.springframework.jdbc.datasource.DriverManagerDataSource">
          <property name="url"
              value="jdbc:mysql://localhost:3306/springsession?useUnicode=true&amp;characterEncoding=UTF8"></property>
          <property name="username" value="root"></property>
          <property name="password" value="123456"></property>
          <property name="driverClassName"
              value="com.mysql.jdbc.Driver"></property>
      </bean>

      <!-- 事务管理器。为JdbcHttpSessionConfiguration提供的事务管理器。 -->
      <bean
          class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
          <constructor-arg ref="dataSource" />
      </bean>

  </beans>
 package com.bie.controller;

 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;

 import org.springframework.stereotype.Controller;
 import org.springframework.web.bind.annotation.RequestMapping;

 /**
  *
  * @author biehl
  *
  *         1、SpringSession
  *
  *         spring-session表:保存客户端session对象的表格。
  *         spring-session-attributes表:保存客户端session中的attributes属性数据的表格。
  *         spring-session框架,是结合Servlet技术中的HTTPSession完成的会话共享机制。在代码中是直接操作HttpSession对象的。
  *
  */
 @Controller
 public class SpringSessionController {

     @RequestMapping("/springSession")
     public String test(HttpServletRequest request, HttpServletResponse response) {
         // 获取到attrName属性值
         Object attrName = request.getSession().getAttribute("attrName");
         // 如果获取到的获取到attrName属性值为空
         if (null == attrName) {
             // 获取到attrName属性值设置到session中
             request.getSession().setAttribute("attrName", "attrValue");
         }
         // 后台打印消息
         System.out.println("80: " + attrName);
         // 返回到jsp页面
         return "/ok.jsp";
     }

 }

demo


上次编辑于: