Session跨域共享解决方案

一、Session跨域
所谓session跨域就是摒弃了系统(tomcat)提供的session,而使用自定义的类似Session的机制来保存客户端数据的一种解决方案。如:通过设置cookie的domain来实现cookie的跨域传递。在cookie中传递一个自定义的session_id。这个session_id是客户端的唯一标记。将这个标记作为key,将客户端需要保存的数据作为value,在服务端进行保存(数据库保存或NOSQL)。这种机制是Session的跨域解决。

    什么是域,在应用模型中一个完整的,有独立访问路径的功能集合称为一个域。如,百度称为一个应用或系统。百度下有若干的域,如搜索引擎(www.baidu.com)百度贴吧(tie.baidu.com),百度知道,百度地图等。域信息有时也称为多级域名。域的划分,以IP,端口,主机名,域名为标准,实际划分。localhost / 127.0.0.1

    什么是跨域,客户端请求的服务器,IP,端口,域名,主机名任何一个不同,都称为跨域。

使用cookie跨域共享,是session跨域的一种解决方案。cookie.setDomain() 为cookie设定有效域范围,cookie.setPath() 为cookie设定有效URI范围。以上两个方法就实现了cookie跨域。这是在开发中性价比最高的跨域解决方案。

二、Spring Session 共享 (了解)
spring-session技术是spring提供的用于处理集群会话共享的解决方案。spring-session技术是将用户session数据保存到三方存储容器中。如:MySQL,redis等。
Spring-session技术是解决同域名下的多服务器集群session共享问题的。不能解决跨域session共享问题。所以互联网开发中越来越少使用这门技术。

实现步骤:

  1. 配置一个spring 提供的filter,实现数据拦截保存并转换为spring-session需要的会话对像。

2)必须提供一个数据库的表格信息(由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对象的。


spring-session共享.png

三、Nginx Session共享

nginx中的ip_hash技术能够将某个ip的请求定向到同一台后端,这样一来这个ip下的某个客户端和某个后端就能建立起稳固的session。ip_hash是在upstream配置中定义的,具体如下:

## 自定义被代理的域名和实际服务器地址。
upstream www.test.com {
server 192.168.74.131:8080;
server 192.168.74.131:8081;
ip_hash;
}

server {
listen 80;
server_name www.test.com; ## server_name要与自定义的upstream一致。
location / {
proxy_pass http://www.test.com; ## 添加代理路径。
}
}

ip_hash 是通过请求端的ip地址计算hash值,与后端服务器定位。由于相同的ip计算出的hash值都是一样的,所以同一台机器的请求每次都可以落到相同的服务器上。但这种方式的局限性太大。比如,客户端请求的ip是动态的,对应的后端服务器出现故障。这些都使session共享变得不可用。所以使用ip_hash方式实现的session共享不是一个最佳的解决方案。

四、Token 机制
使用基于 Token 的身份验证方法,在服务端不需要存储用户的登录记录大概的流程是这样的:

  1. 客户端使用用户名、密码请求登录。
  2. 服务端收到请求,去验证用户名、密码。
    验证成功后,服务端会签发一个 Token,再把这个 Token 发送给客户端。
  3. 客户端收到 Token 以后可以把它存储起来,比如放在 Cookie 里或者 Local Storage 、Session Storage里。
  4. 客户端每次向服务端请求资源的时候需要带着服务端签发的 Token
    服务端收到请求,然后去验证客户端请求里面带着的 Token,如果验证成功,就向客户端返回请求的数据。服务端验证源的Token存储在redis/mogodb/关系型数据库 等。

五、JSON Web Token(JWT)机制
JWT是一种紧凑且自包含的,用于在多方传递JSON对象的技术。传递的数据可以使用数字签名增加其安全行。可以使用HMAC加密算法或RSA公钥/私钥加密方式。
紧凑:数据小,可以通过URL,POST参数,请求头发送。且数据小代表传输速度快。
自包含:使用payload数据块记录用户必要且不隐私的数据,可以有效的减少数据库访问次数,提高代码性能。
JWT一般用于处理用户身份验证或数据信息交换。
用户身份验证:一旦用户登录,每个后续请求都将包含JWT,允许用户访问该令牌允许的路由,服务和资源。单点登录是当今广泛使用JWT的一项功能,因为它的开销很小,并且能够轻松地跨不同域使用。
数据信息交换:JWT是一种非常方便的多方传递数据的载体,因为其可以使用数据签名来保证数据的有效性和安全性。
官网: jwt.io

JWT数据结构

JWT的数据结构是 : A.B.C。 由字符点‘.’来分隔三部分数据。
A - header 头信息
B - payload (有效负载)
C - Signature 签名

Header

数据结构: {“alg”: “加密算法名称”, “typ” : “JWT”}
alg是加密算法定义内容,如:HMAC SHA256 或 RSA
typ是token类型,这里固定为JWT。

payload

在payload数据块中一般用于记录实体(通常为用户信息)或其他数据的。主要分为三个部分,分别是:已注册信息(registered claims),公开数据(public claims),私有数据(private claims)。

payload中常用信息有:iss(发行者),exp(到期时间),sub(主题),aud(受众)。前面列举的都是已注册信息。
公开数据部分一般都会在JWT注册表中增加定义。避免和已注册信息冲突。
公开数据和私有数据可以由程序员任意定义。注意:即使JWT有签名加密机制,但是payload内容都是明文记录,除非记录的是加密数据,否则不排除泄露隐私数据的可能。不推荐在payload中记录任何敏感数据。

Signature

签名信息。这是一个由开发者提供的信息。是服务器验证的传递的数据是否有效安全的标准。在生成JWT最终数据的之前。先使用header中定义的加密算法,将header和payload进行加密,并使用点进行连接。如:加密后的head.加密后的payload。再使用相同的加密算法,对加密后的数据和签名信息进行加密。得到最终结果。

JWT执行流程

JWT执行流程

#JWT核心赖
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.3.0</version>
</dependency>

#java开发JWT的依赖jar包
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>

关键代码

public class JWTUtils {

// 服务器的key。用于做加解密的key数据。 如果可以使用客户端生成的key。当前定义的常亮可以不使用。
private static final String JWT_SECERT = "test_jwt_secert" ;
private static final ObjectMapper MAPPER = new ObjectMapper();
public static final int JWT_ERRCODE_EXPIRE = 1005;//Token过期
public static final int JWT_ERRCODE_FAIL = 1006;//验证不通过

public static SecretKey generalKey() {
    try {
        // byte[] encodedKey = Base64.decode(JWT_SECERT); 
        // 不管哪种方式最终得到一个byte[]类型的key就行
        byte[] encodedKey = JWT_SECERT.getBytes("UTF-8");
        SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
        return key;
    } catch (Exception e) {
        e.printStackTrace();
         return null;
    }
}
/**
 * 签发JWT,创建token的方法。
 * @param id  jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。
 * @param iss jwt签发者
 * @param subject jwt所面向的用户。payload中记录的public claims。当前环境中就是用户的登录名。
 * @param ttlMillis 有效期,单位毫秒
 * @return token, token是一次性的。是为一个用户的有效登录周期准备的一个token。用户退出或超时,token失效。
 * @throws Exception
 */
public static String createJWT(String id,String iss, String subject, long ttlMillis) {
    // 加密算法
    SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
    // 当前时间。
    long nowMillis = System.currentTimeMillis();
    // 当前时间的日期对象。
    Date now = new Date(nowMillis);
    SecretKey secretKey = generalKey();
    // 创建JWT的构建器。 就是使用指定的信息和加密算法,生成Token的工具。
    JwtBuilder builder = Jwts.builder()
            .setId(id)  // 设置身份标志。就是一个客户端的唯一标记。 如:可以使用用户的主键,客户端的IP,服务器生成的随机数据。
            .setIssuer(iss)
            .setSubject(subject)
            .setIssuedAt(now) // token生成的时间。
            .signWith(signatureAlgorithm, secretKey); // 设定密匙和算法
    if (ttlMillis >= 0) { 
        long expMillis = nowMillis + ttlMillis;
        Date expDate = new Date(expMillis); // token的失效时间。
        builder.setExpiration(expDate);
    }
    return builder.compact(); // 生成token
}

/**
 * 验证JWT
 * @param jwtStr
 * @return
 */
public static JWTResult validateJWT(String jwtStr) {
    JWTResult checkResult = new JWTResult();
    Claims claims = null;
    try {
        claims = parseJWT(jwtStr);
        checkResult.setSuccess(true);
        checkResult.setClaims(claims);
    } catch (ExpiredJwtException e) { // token超时
        checkResult.setErrCode(JWT_ERRCODE_EXPIRE);
        checkResult.setSuccess(false);
    } catch (SignatureException e) { // 校验失败
        checkResult.setErrCode(JWT_ERRCODE_FAIL);
        checkResult.setSuccess(false);
    } catch (Exception e) {
        checkResult.setErrCode(JWT_ERRCODE_FAIL);
        checkResult.setSuccess(false);
    }
    return checkResult;
}

/**
 * 
 * 解析JWT字符串
 * @param jwt 就是服务器为客户端生成的签名数据,就是token。
 * @return
 * @throws Exception
 */
public static Claims parseJWT(String jwt) throws Exception {
    SecretKey secretKey = generalKey();
    return Jwts.parser()
        .setSigningKey(secretKey)
        .parseClaimsJws(jwt)
        .getBody(); // getBody获取的就是token中记录的payload数据。就是payload中保存的所有的claims。
}

/**
 * 生成subject信息
 * @param subObj - 要转换的对象。
 * @return java对象->JSON字符串出错时返回null
 */
public static String generalSubject(Object subObj){
    try {
        return MAPPER.writeValueAsString(subObj);
    } catch (JsonProcessingException e) {
        e.printStackTrace();
        return null;
    }
}
}

token保存位置

    使用JWT技术生成的token,客户端在保存的时候可以考虑cookie或localStorage.cookie保存方式,可以实现跨域传递数据。localStorage是域私有的本地存储,无法实现跨域。推荐用cookie,各浏览器兼容性高。

webstorage,localStorage,sessionStorage

webstorage可保存的数据容量为5M.且只能存储字符串数据。
webstorage分为localStorage和sessionStorage。
localStorage的生命周期是永久的。关闭页面或浏览器后localStorage中的数据也不会消失。localStorage除非主动删除数据,否则数据永远不会消失。
sessionStorage是会话相关的本地存储单元,生命周期是仅当前会话下有效。sessionStorage引入一个“浏览器窗口”的概念,sessionStorage是在同源的窗口中始终存在的数据。只要这个浏览器窗口没有关闭,即使刷新页面或者进入同源另一个页面,数据依然存在。但是sessionStorage在关闭了浏览器窗口后就会被销毁。同时独立的打开同一个窗口同一个页面,sessionStorage也是不一样的。(这个与创建cookie时不设置超时时间效果一样)。
以上了解即可。个人感觉还是使用cookie最好。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342