Cookie、Session 和 JWT 到底是什么?有什么不同?
HTTP 是无状态的协议 (对于事务处理没有记忆能力,每次客户端和服务端会话完成时,服务端不会保存任何会话信息):
每个请求都是完全独立的,服务端无法确认当前访问者的身份信息,无法分辨上一次的请求发送者和这一次的发送者是不是同一个客户端。
所以服务器与浏览器为了进行会话跟踪(知道是谁在访问我),就必须主动的去维护一个状态,这个状态用于告知服务端前后两个请求是否来自同一浏览器。 而这个状态需要通过 cookie 或者 session 去实现。
比较
Cookie 与 Session 的关系
-
当浏览器
第一次访问服务器时,服务器创建一个 session 对象(该对象有一个唯一的 ID,一般称之为 sessionId),服务器会将 sessionId 以 cookie 的方式发送给浏览器。 -
当浏览器
再次访问服务器时,会在请求头携带 sessionId,服务器依据 sessionId 就可以找到对应的 session 对象。
JWT 和 Cookie、Session 的比较
-
JWT(JSON Web Token)是一个用在客户端和服务端之间、以 JSON 对象的形式安全传输信息的令牌。 -
Cookie是一个用来辨别用户身份、进行 Session 跟踪的小型文本文件。 -
Session是一个用来存放单一用户当前访问服务器产生的信息的对象。 -
Cookie 和 Session 一般是一起使用,
用户访问的信息用 Session 对象存储在服务端,对应的 SessionID 以 Cookie 对象存储在客户端。 -
JWT 不能存储用户访问的信息,只是
用作用户访问服务端的授权令牌,由服务端生成,存储在客户端,在有效期内表示已经用户已登陆。 -
JWT 适用于:有效期短、只希望使用一次 的场景。 -
Cookie、Session 适用于:单点登录、会话管理 的场景。 -
JWT 的优点:-
不占用服务端内存,只在客户端存储。
-
传输的数据以 JSON 的形式进行,适应性很强。
-
在分布式部署下,不需要进行多机数据共享、redis 缓存、或数据库存储,唯一性极高。
-
JWT 是无状态的,也不会在服务端存储任何状态。
-
-
JWT 的缺点:-
安全问题:JWT 存储用户信息的 有效载荷 是用采用 Base64 编码的,没有加密,
不能存储机密信息。 -
开销方面:当 需要存储的信息变多时,
JWT 就会变得很长,因此 相应的 HTTP 请求开销也变大。 -
JWT 是
一次性的,想要更改信息,就需要重新签发一个 JWT。 -
JWT 是
无法废弃的,在到期之前一直有效,想要主动废弃 JWT,就需要添加业务逻辑来处理。
-
-
Cookie、Session 的优点:-
安全问题:存储 用户信息 的 Session 是
保存在服务端的,可以存储机密信息。 -
开销方面:Cookie 不大于 4KB,因此 相应的 HTTP 请求
开销很小,且基本不变。 -
Cookie、Session 是
持久性的,信息更改都在 服务端,SessionId 不用改动。 -
Cookie、Session 是
可以主动废除的,想要主动废弃时,调用 Session 的 invalidate() 方法,然后添加 SessionId 无效业务即可。
-
-
Cookie、Session 的缺点:-
开销方面:需要额为的内存空间来存储 Session 对象,
服务端开销大。 -
在分布式部署下,需要进行多机数据共享、redis 缓存、或数据库存储 等操作,保证数据无误。
-
Cookie 大小受到限制,显然影响不大,但可变换空间也相应变小。
-
Cookie 如果被获取了,很容易受到跨站请求伪造的攻击。(因为是基于 Cookie 来进行用户识别的)
-
跨域问题
-
同源策略是一种约定, 它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。 -
可以说 Web 是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。
-
同源策略会阻止一个域的 JavaScript 脚本和另外一个域的内容进行交互。 -
所谓
同源(即指在同一个域)就是:两个页面具有相同的协议(protocol),主机(host)和端口号(port)。 -
跨域:是指一个域下的文档或脚本试图去请求另一个域下的资源。(广义的)
-
跨域:是由浏览器同源策略限制的一类请求场景,从一个域名的网页去请求另一个域名的资源时,域名、端口、协议任一不同,都是跨域。(狭义的) -
URL:最基本的组成是协议(protocol)、域名(主机、host)、端口(port),即:[协议]://[host]:[port]/。-
协议:可以是 http 或 https,必须写。 -
域名:用来指定要访问哪一个主机(域名),本机是:127.0.0.1、localhost,必须写。 -
port:一般同一个后台之间的服务就是用端口号来区分,默认是 8080,一般都要写。
-
-
域名:又称网域,是由一串用点分隔的名字组成的 Internet 上某一台计算机或计算机组的名称,用于在数据传输时对计算机的定位标识。-
IP 地址能够唯一地标记网络上的计算机,但由于IP 地址是一长串数字,不直观,而且用户记忆十分不方便,又发明了另一套字符型的地址方案(域名地址)。 -
IP 地址和域名是一一对应的,这份域名地址的信息存放在一个叫域名服务器(DNS,Domain Name Server)的主机内,使用者只需了解易记的域名地址,其对应转换工作就留给了域名服务器。 -
域名服务器(DNS)就是
提供 IP 地址和域名之间的转换服务的服务器。 -
域名由两个或两个以上的词构成, 中间由点号 ‘.’ 分隔开,
最右边的那个词称为顶级域名(常见的有:cn、com)。 -
顶级域名的下一级,就是我们所说的
一级域名(又称为 主域名),从右往左数的第二个。 -
二级域名的下一级,就是我们所说的
二级域名(又称为 分域名、主名),从右往左数的第三个。 -
一般就是三级结构,如:域名 www.baidu.com 中,顶级域名:com;主域名:百度;分域名:www。
-
| 当前页面 URL | 被请求的页面 URL | 是否跨域 | 原因 |
|---|---|---|---|
| http://localhost:8080/ | http://localhost:8080/index.html | 否 | 协议、域名、端口 都相同 |
| http://localhost:8080/ | https://localhost:8080/index.html | 是 | 协议不同,由 http 访问 https |
| http://www.test.com:8080/ | http://www.baidu.com:8080/ | 是 | 主域名不同,由 www.test.com 访问 www.baidu.com |
| http://baike.baidu.com:8080/ | http://www.baidu.com:8080/ | 是 | 分域名不同,由 baike.baidu.com 访问 www.baidu.com |
| http://localhost:8080/ | http://localhost:8071/ | 是 | 端口不同,由 8080 访问 8071 |
Java 后端解决跨域问题
package com.example.demo.config;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Service;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
@Service
public class CoreService {
/**
* CorsFilter 过滤器
* <p>
* CORS:全称"跨域资源共享"(Cross-origin resource sharing)
* <p>
* ajax请求 会产生跨域问题
* <p>
* Spring MVC 提供了 CorsFilter 过滤器
*
* @return CorsFilter Cors过滤器
*/
@Bean
public CorsFilter corsFilter() {
// 初始化cors配置对象
CorsConfiguration corsConfiguration = new CorsConfiguration();
// 添加允许跨域访问的域名,如果要携带 Cookie,不要写*,*:代表所有域名都可以跨域访问
corsConfiguration.addAllowedOrigin("http://localhost:8080");
// 设置允许携带cookie
corsConfiguration.setAllowCredentials(true);
// 代表所有的请求方法:GET POST PUT DELETE...
corsConfiguration.addAllowedMethod("*");
// 允许携带任何头信息
corsConfiguration.addAllowedHeader("*");
// 初始化cors配置源对象
UrlBasedCorsConfigurationSource urlBasedCorsConfigurationSource = new UrlBasedCorsConfigurationSource();
urlBasedCorsConfigurationSource.registerCorsConfiguration("/**", corsConfiguration);
// 返回corsFilter实例,参数:cors配置源对象
return new CorsFilter(urlBasedCorsConfigurationSource);
}
}
JWT(JSON Web Token)
-
JSON Web 令牌 (JWT) 是一种开放标准 (RFC 7519),它定义了一种紧凑且独立的方式,用于在各方之间
以 JSON 对象的形式安全地传输信息。 -
此信息可以
验证和信任,因为它是经过数字加密的。JWT 可以使用密钥(使用HMAC算法)或使用RSA或ECDSA 的公钥/私钥对进行加密。 -
页眉(Header)和有效载荷(Payload)虽然受到篡改保护,但任何人都可以读取。不能用于存放机密信息。
-
使用 JWT 时:
JWT 存储在 请求头的 Authorization 字段的值中。
JWT 的结构
-
是
xxxx.yyyy.zzzz 的结构,由页眉(Header)、有效载荷(Payload)、签名(Signature)三部分组成。 -
即:
[Header].[Payload].[Signature],三部分之间用 ‘.’ 点号连接。
页眉(Header)
- 用来存放用户的一些公开信息、令牌的类型(即 JWT)、正在使用的签名算法(例如 HMAC SHA256 或 RSA)等等。
{
"alg": "HS256",
"typ": "JWT"
}
- 对页眉(Header)进行 Base64Url 编码以形成 JSON Web 令牌的第一部分。
有效载荷(Payload)
-
是存放 JWT 的主体内容的部分。
-
用来存放这个 token 的声明。有三种类型的声明:已注册、公共和私人声明。
-
已注册声明:这些是一组预定义的声明,这些声明不是必需的,但建议使用,以提供一组有用的、可互操作的声明。- 例如:iss(发行人),exp(到期时间),sub(subject),aud(audience)等。
iss:发行人
exp:到期时间
sub:主题
aud:用户
nbf:在此之前不可用
iat:发布时间
jti:JWT ID 用于标识该 JWT
-
公共声明:这些可以由使用 JWT 的人随意定义。但为了避免冲突,它们应该在 IANA JSON Web 令牌注册表中定义,或者定义为包含抗冲突命名空间的 URI。 -
专用声明:这些是创建的自定义声明,用于在同意使用它们的各方之间共享信息,既不是注册声明也不是公共声明。
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
- 对有效负载(Payload)进行 Base64Url 编码以形成 JSON Web 令牌的第二部分。
签名(Signature)
-
签名(Signature):用于验证消息在传递过程中未被更改。
-
并且在使用私钥签名的令牌的情况下,它还可以验证 JWT 的发件人是否正确(发件人是不是它所说的人)。
-
创建签名:需要获取
页眉、有效载荷、盐值、页眉中指点的算法并对其进行加密。 -
采用不可逆的加密算法、配合盐值进行加密。 -
目前,JWT 的签名算法有三种:
-
HMAC(哈希消息验证码):HS256、HS384、HS512(对称加密算法)
-
RSASSA(RSA签名算法):RS256、RS384、RS512(非对称加密算法)
-
ECDSA(椭圆曲线数据签名算法):ES256、ES384、ES512(非对称加密算法)
-
-
对称加密算法:加密和解密使用相同的密钥的算法。
-
非对称加密算法:需要两个密钥,公开密钥(publicKey,公钥)和私有密钥(privateKey,私钥),它们是一对。
加密和解密使用的是两个不同的密钥,使用公钥(私钥)加密信息后,需要用配套的私钥(公钥)进行解密的算法。
// 如果要使用 HMAC SHA256 算法,将按以下方式创建签名:
HMACSHA256(
base64UrlEncode(header)+"."+
base64UrlEncode(payload),
secret)
Cookie
-
Cookie 是
为了辨别用户身份,进行 Session 跟踪而储存在用户本地终端上的数据(通常经过加密),由用户客户端计算机暂时或永久保存的信息(小型文本文件)。 -
Cookie 是一段不超过 4KB 的小型文本数据,由一个名称(Name)、一个值(Value)和其它几个用于控制 Cookie 有效期、安全性、使用范围的可选属性组成。
-
Cookie 存储在客户端: cookie 是服务器发送到用户浏览器并保存在本地的一小块数据,它会在浏览器下次向同一服务器再发起请求时被携带并发送到服务器上。 -
Cookie 是不可跨域的: 每个 cookie 都会绑定单一的域名,无法在别的域名下获取使用,一级域名和二级域名之间是允许共享使用的(靠的是 domain 属性)。 -
Cookie 的生命周期:会话性 Cookie,仅保存在客户端内存中,并在用户关闭浏览器时失效;持久性 Cookie,会保存在用户的硬盘中,直至生存期到期或用户注销。 -
Cookie 的常见属性(不同的包下的 Cookie 类,有不同的属性,但大体上相同):
| 属性 | 说明 |
|---|---|
| Name=Value | 设置 Cookie 的名称及相对应的值,对于认证 Cookie,Value 值包括 Web 服务器所提供的访问令牌 |
| Domain | 指定了可以访问该 Cookie 的 Web 站点或域。Cookie 机制并未遵循严格的同源策略,允许一个子域可以设置或获取其父域的 Cookie。当需要实现单点登录方案时,Cookie 的上述特性非常有用,然而也增加了 Cookie 受攻击的危险,比如攻击者可以借此发动会话定置攻击 |
| Path | 定义了 Web 站点上可以访问该 Cookie 的目录 |
| MaxAge | |
| Expires | 设置 Cookie 的生存期。有两种存储类型的 Cookie:会话性(默认)与持久性 |
| Secure | 指定是否使用 HTTPS 安全协议发送 Cookie |
| HTTPOnly | 用于防止客户端脚本通过 document.cookie 属性访问 Cookie,有助于保护 Cookie 不被跨站脚本攻击窃取或篡改 |
| Comment | 描述(没有实际用途,就是文本) |
| Version | 版本(0 版本通用性最好,1 版本有些浏览器不支持) |
@Controller
@RequestMapping("/test")
public class TestController {
@GetMapping("/hello1")
public String hello(HttpServletRequest request, HttpServletResponse response) {
// 获取 请求头中的 Cookie 列表
Cookie[] cookies = request.getCookies();
for(Cookie cookie : cookies) {
System.out.println(cookie);
}
Cookie hello = CookieUtils.generate("hello", "5509");
// 将 Cookie 添加到 响应头中
response.addCookie(hello);
return "hello";
}
}
创建 Cookie
- 而创建 Cookie 则是用 Java、SpringBoot
自带的包,相应的包举例:javax.servlet.http.Cookie;、org.springframework.boot.web.server.Cookie;、...
import javax.servlet.http.Cookie;
public class CookieUtils {
public static Cookie generate(String name, String sessionId) {
Cookie cookie = new Cookie(name, sessionId);
// 设置这个 cookie 的版本(0 版本通用性最好,1 版本有些浏览器不支持)
cookie.setVersion(0);
// 设置这个 cookie 的描述(没有实际用途,就是文本)
cookie.setComment("测试 Cookie");
return cookie;
}
}
Session
-
Session 是:
存储在服务端,用来存放单一用户信息的对象。有一个唯一的字符串 ID。 -
Session 是用于
保持客户端在 Web 服务器端的状态的方法。通过将对象存储在 Web 服务器的内存中来实现,在整个用户会话中,保持任何对象。 -
Session 存储在服务端: 在客户端第一次访问时创建,用一个唯一的字符串来作为 ID,区分不同的 Session。 -
sessionId 会以 cookie 的形式发送到客户端。
-
Session 的属性:
| 属性 | 说明 |
|---|---|
| timeout | Session 对象的超时时间,默认是 1200s(20 分钟) |
| creationTime | Session 对象的 创建时间 |
| accessedTime | Session 对象的 最后活跃时间 |
| maxInactiveInterval | Session 对象的 超时时间 |
-
Session 的常用方法:
-
isNew()判断 Session 对象 是否为 新建的。 -
getId()返回 Session 对象的 ID(由服务器自动创建,唯一)。 -
setAttribute(String, Object)设置 Session 对象的属性。 -
invalidate()使该 Session 对象失效。
-
创建 Session
import javax.servlet.http.HttpSession;
public class SessionUtils {
public static String generate(HttpServletRequest request) {
// 创建 Session
HttpSession session = request.getSession();
// 给 Session 添加相应的属性
session.setAttribute("", "");
// 获取 Session ID
return session.getId();
}
}
-
getSession(boolean flag) 方法中,
-
flag=true 时,先
查看请求当中是否有 sessionId,不存在 sessionId 时,创建一个 session 对象;存在 sessionId
时,则依据 sessionId 查找对应的 session对象。 -
在查找 Session 时,直接返回查找到的 Session 对象,
如果找不到,创建一个新的 session 对象返回。 -
getSession() 方法就是 getSession(true) 方法。
-

803

被折叠的 条评论
为什么被折叠?



