session和token有什么区别?
众所周知,HTTP协议它是无状态的协议,浏览器多次请求服务器,服务器它无法感知是不是同一用户的请求,于是就需要身份验证。
# session的状态保持
Session 是服务器为了保存用户状态而创建的一个特殊的对象,存储在服务器端,该对象有一个唯一的id,一般称之为sessionId。
当浏览器第一次用用户名和密码请求Web服务器,服务器会创建一个Session存放在服务器里(可持久化到数据库中),然后通过响应头将Session id(桥梁作用)返回给浏览器写入到Cookie中或重写到url中,浏览器下次请求就会将Sessiond id以Cookie形式传递给服务器端,服务器端获取Session id后再去寻找对应的Session。查询到,就会将查询到的用户信息返回,从而实现状态保持。
# 基于服务器验证的弊端
- **服务器压力增大:**session是存储在内存中的,每个用户通过认证之后都会将session数据保存在服务器的内存中,当越来越多的用户发请求时,内存的开销会不断增加。
- **可扩展性不强:**若服务器做了负载均衡,用户的下一次请求可能会被定向到其它服务器节点,若那台节点上没有用户的Session信息,就会导致会话验证失败。所以Session默认机制下是不适合分布式部署的。
- **CORS(跨域资源共享):**当我们需要让数据跨多台移动设备上使用时,跨域资源的共享会是一个让人头疼的问题。在使用Ajax抓取另一个域的资源,就可以会出现禁止请求的情况。
- **CSRF(跨站请求伪造):**session是基于cookie进行用户识别的, cookie如果被截获,用户就会很容易受到跨站请求伪造的攻击。
# 基于Token的身份验证
Token称为**令牌,是访问资源接口的凭证,一般组成:**uid(用户唯一的身份标识userId)、time(当前时间的时间戳)、sign(签名,通过MD5、SHA等签名算法将密钥、时间戳等元素加密)(userId用于与其他表进行相关操作,一般须携带)
private static long tokenExpiration = 365 * 24 * 60 * 60 * 1000;
private static String tokenSignKey = "123456";
//创建token
public static String createToken(Long userId, String username) {
String token = Jwts.builder()
.setSubject("AUTH-USER")
.setExpiration(new Date(System.currentTimeMillis() + tokenExpiration))
.claim("userId", userId)
.claim("username", username)
.signWith(SignatureAlgorithm.HS512, tokenSignKey)
.compressWith(CompressionCodecs.GZIP)
.compact();
return token;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
# token的身份验证流程
①用户名与密码认证成功后,对当前用户数据签发一个加密字符串token,返还给客户端(服务器端并不进行保存)
②客户端将接收到的token值存储在Local Storage中(通过js代码写入Local Storage,通过js获取,并不会像cookie一样自动携带)或者cookie中,或者内存、Redis中
③再次访问时,服务器对浏览器传来的token值进行解密并验证,如果验证成功,则返回请求数据,实现状态保持。
//登录认证,成功后创建token
@PostMapping("login")
public Result login(@RequestBody LoginVo loginVo) {
SysUser sysUser = sysUserService.getByUsername(loginVo.getUsername());
if(null == sysUser) {
throw new GuiguException(201,"用户不存在");
}
if(!MD5.encrypt(loginVo.getPassword()).equals(sysUser.getPassword())) {
throw new GuiguException(201,"密码错误");
}
if(sysUser.getStatus().intValue() == 0) {
throw new GuiguException(201,"用户被禁用");
}
Map<String, Object> map = new HashMap<>();
map.put("token", JwtHelper.createToken(sysUser.getId(), sysUser.getUsername()));
return Result.ok(map);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 服务端token创建及验证详情
比如用HMAC-SHA256 算法,加上一个只有我才知道的密钥, 对数据做一个签名, 把这个签名和数据一起作为token , 由于密钥别人不知道, 就无法伪造token了。
这个token 我不保存, 当小F把这个token 给我发过来的时候,我再用同样的HMAC-SHA256 算法和同样的密钥,对数据再计算一次签名, 和token 中的签名做个比较, 如果相同, 我就知道小F已经登录过了,并且可以直接取到小F的user id , 如果不相同, 数据部分肯定被人篡改过, 我就告诉发送者: 对不起,没有认证。
Token 中的数据是明文保存的(虽然我会用Base64做下编码, 但那不是加密), 还是可以被别人看到的, 所以我不能在其中保存像密码这样的敏感信息。
# Token的优点:
- **减轻服务器压力:**不保存session id, 只是生成token , 然后验证token , 用CPU计算时间获取了session存储空间
- **无状态,可扩展:**即使有了多台服务器,服务器也只是做了token的解密和用户数据的查询,它不需要在服务端去保留用户的认证信息或者会话信息,这就意味着基于token认证机制的应用不需要去考虑用户在哪一台服务器登录了,这就为应用的扩展提供了便利,解决了session扩展性的弊端。
- **多平台跨域:**只要用户有一个通过了验证的token,数据和资源就能够在任何域上被请求到。
- 解决跨站请求伪造:token存储在Local Storage中,请求时一般放在请求头中,即使仍使用cookie存储token,cookie也仅仅是一个存储机制而不用于认证。只要页面没有XSS漏洞泄露Token,那么接口的CSRF攻击就无法成功,也是现在主流的解决方案。
- **可拓展使用:**当我们在程序中认证了信息并取得token之后,我们将token授权给第三方应用程序,这些第三方程序能够获取到我们的数据
- 可用于移动端:移动端对 cookie 的支持不是很好,而 session 需要基于 cookie 实现,所以移动端常用的是 token
# Token 和 Session 的区别
- Session 是一种记录服务器和客户端会话状态的机制,使服务端有状态化,可以记录会话信息。而 Token 是令牌,访问资源接口(API)时所需要的资源凭证,Token 使服务端无状态化,不会存储会话信息。
- Session 和 Token 并不矛盾,作为身份认证 Token 安全性比 Session 好,因为每一个请求都有签名还能防止监听以及重放攻击,而 Session 就必须依赖链路层来保障通讯安全了。如果你需要实现有状态的会话,仍然可以增加 Session 来在服务器端保存一些状态。
- 所谓 Session 认证只是简单的把 User 信息存储到 Session 里,因为 SessionID 的不可预测性,暂且认为是安全的。而 Token ,如果指的是 OAuth Token 或类似的机制的话,提供的是 认证 和 授权 ,认证是针对用户,授权是针对 App 。其目的是让某 App 有权利访问某用户的信息。这里的 Token 是唯一的。不可以转移到其它 App上,也不可以转到其它用户上。Session 只提供一种简单的认证,即只要有此 SessionID ,即认为有此 User 的全部权利。是需要严格保密的,这个数据应该只保存在站方,不应该共享给其它网站或者第三方 App。所以简单来说:如果你的用户数据可能需要和第三方共享,或者允许第三方调用 API 接口,用 Token 。如果永远只是自己的网站,自己的 App,用什么就无所谓了。
# JWT
- JSON Web Token(简称 JWT)是目前最流行的跨域认证解决方案。
- 是一种认证授权机制。
- JWT 是为了在网络应用环境间传递声明而执行的一种基于 JSON 的开放标准(RFC 7519)。JWT 的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源。比如用在用户登录上。
- 可以使用 HMAC 算法或者是 RSA 的公/私秘钥对 JWT 进行签名。因为数字签名的存在,这些传递的信息是可信的。
# 结构
- 标头(Header)
- 有效载荷(Payload)
- 签名(Signature)
token格式:head.payload.singurater
https://jwt.io/ (opens new window) Header:有令牌的类型和所使用的签名算法,如HMAC、SHA256、RSA;使用Base64编码组成;(Base64是一种编码,不是一种加密过程,可以被翻译成原来的样子)
{
"alg" : "HS256",
"type" : "JWT"
}
2
3
4
**Payload :**有效负载,包含声明;声明是有关实体(通常是用户)和其他数据的声明,不放用户敏感的信息,如密码。同样使用Base64编码
{
"sub" : "123",
"name" : "John Do",
"admin" : true
}
2
3
4
5
**Signature :**前面两部分都使用Base64进行编码,前端可以解开知道里面的信息。Signature需要使用编码后的header和payload加上我们提供的一个密钥,使用header中指定的签名算法(HS256)进行签名。签名的作用是保证JWT没有被篡改过
HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret);
# 使用方式
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
</dependency>
2
3
4
方式一
- 当用户希望访问一个受保护的路由或者资源的时候,可以把它放在 Cookie 里面自动发送,但是这样不能跨域,所以更好的做法是放在 HTTP 请求头信息的 Authorization 字段里,使用 Bearer 模式添加 JWT。
方式二
- 跨域的时候,可以把 JWT 放在 POST 请求的数据体里。
方式三
- 通过 URL 传输
http://www.example.com/user?token=xxx
# Token 和 JWT 的区别
相同:
- 都是访问资源的令牌
- 都可以记录用户的信息
- 都是使服务端无状态化
- 都是只有验证成功后,客户端才能访问服务端上受保护的资源
区别:
- Token:服务端验证客户端发送过来的 Token 时,还需要查询数据库获取用户信息,然后验证 Token 是否有效。
- JWT:将 Token 和 Payload 加密后存储于客户端,服务端只需要使用密钥解密进行校验(校验也是 JWT 自己实现的)即可,不需要查询或者减少查询数据库,因为 JWT 自包含了用户信息和加密的数据。
# 总之
Session和Token机制原理上差不多,都是用户身份验证的一种识别手段,它们都有过期时间的限制。 1、Session是存放在服务器端的,采用空间换时间的策略来进行身份识别,若Session没有持久化落地存储,一旦服务器重启,Session数据会丢失。 2、Token是放在客户端存储的,采用了时间换空间策略,它是无状态的,所以在分布式环境中应用广泛。 选择? 技术选型依据业务而来,特定的场景适合特定的业务,一般购物车功能会采用Session验证,接口校验一般会采用Token验证,具体采用何种方法,需要大家根据自己的业务进行选择。
参考代码 https://blog.csdn.net/Top_L398/article/details/109361680 (opens new window)