前言:
会话跟踪技术有三种:
- Cookie(客户端会话跟踪技术)
- 数据存储在客户端/浏览器当中
- 优点:HTTP协议中支持的技术(像Set-Cookie 响应头的解析以及 Cookie 请求头数据的携带,都是浏览器自动进行的,是无需我们手动操作的)
- 缺点:
- 移动端APP(Android、IOS)中无法使用Cookie
- 不安全,用户可以自己禁用Cookie
- Cookie不能跨域
- Session(服务端会话跟踪技术)
- 数据存储在储在服务端
- 优点:Session是存储在服务端的,安全
- 缺点:
- 服务器集群环境下无法直接使用Session
- 移动端APP(Android、IOS)中无法使用Cookie
- 用户可以自己禁用Cookie
- Cookie不能跨域
- 令牌技术
JWT令牌技术
令牌的形式有很多,我们使用的是功能强大的 JWT令牌。
1.JWT三部分的介绍
JWT全称:JSON Web Token (官网:https://jwt.io/)
-
定义了一种简洁的、自包含的格式,用于在通信双方以json数据格式安全的传输信息。由于数字签名的存在,这些信息是可靠的。
简洁:是指jwt就是一个简单的字符串。可以在请求参数或者是请求头当中直接传递。 自包含:指的是jwt令牌,看似是一个随机的字符串,但是我们是可以根据自身的需求在jwt令牌中存储自定义的数据内容。如:可以直接在jwt令牌中存储用户的相关信息。 简单来讲,jwt就是将原始的json数据格式进行了安全的封装,这样就可以直接基于jwt在通信双方安全的进行信息传输了。
JWT的组成: (JWT令牌由三个部分组成,三个部分之间使用英文的点来分割)
- 第一部分:Header(头), 记录令牌类型、签名算法等。 例如:
{"alg":"HS256","type":"JWT"}
- 第二部分:Payload(有效载荷),携带一些自定义信息、默认信息等。 例如:
{
"sub": "123456",
// Subject (主题),用于标识JWT所代表的主体
//这里指定了JWT令牌所代表的主体为ID为 "123456" 的用户。
"username": "john.doe",
"role": "user",
"exp": 1677313600 // Expiration Time (过期时间)
}
-
第三部分:Signature(签名),防止Token被篡改、确保安全性。将header、payload,并加入指定秘钥(仅在服务器端知道),通过指定签名算法计算而来。
签名的目的就是为了防jwt令牌被篡改,而正是因为jwt令牌最后一个部分数字签名的存在,所以整个jwt 令牌是非常安全可靠的。一旦jwt令牌当中任何一个部分、任何一个字符被篡改了,整个令牌在校验的时候都会失败,所以它是非常安全可靠的。
第一部分和第二部分是由base64编码生成的,而第三部分是使用指定的哈希算法对编码后的头部和负载进行签名生成签名部分。
2.令牌的原理和执行流程
-
创建JWT(签发令牌):
- 用户通过身份验证登录,并且服务器确认其身份后,生成JWT令牌。
- 服务器构建JWT令牌的头部(Header),负载(Payload),并使用密钥对其进行签名生成签名(Signature)。
- 最终将这三个部分组合在一起形成完整的JWT令牌:
header.payload.signature
。
-
传递JWT令牌:
- 服务器将生成的JWT令牌发送回客户端。
- 客户端可以将令牌存储在本地(例如,浏览器的本地存储或cookie)或者在后续请求中将其放在HTTP头部的Authorization字段中进行传递。
-
验证JWT令牌:
- 当客户端发送带有JWT令牌的请求到服务器时,服务器需要验证令牌的真实性和有效性。
- 验证过程涉及对JWT令牌的解析和签名验证。
- 服务器首先从JWT令牌中解析出头部和负载,然后使用相同的密钥对头部和负载进行签名验证,以确保签名是合法的。
- 如果签名验证通过并且JWT令牌没有过期,服务器将令牌解析后的负载作为用户身份的凭据,允许用户继续访问受保护的资源。
如果JWT令牌在传输过程中被截获,攻击者可以利用这个令牌进行伪造访问。这就是为什么使用HTTPS来加密通信在JWT传输中非常重要。HTTPS可以确保通信的机密性和完整性,使得令牌在传输过程中难以被截获和篡改。
生成令牌的java代码:
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.11.2</version> <!-- 使用最新版本 -->
</dependency>
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
//工具类:JwtUtil
public class JwtUtil {
// 假设这是服务器端的密钥
private static final String SECRET_KEY = "your_secret_key";
//生成密钥
public static String generateJwtToken(String subject, long expirationMillis) {
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
JwtBuilder builder = Jwts.builder()
.setSubject(subject)
.setIssuedAt(now)
.setExpiration(new Date(nowMillis + expirationMillis))
// 在负载部分添加自定义声明,例如用户名、角色等
.claim("username", "john.doe")
.claim("role", "user")
.signWith(SignatureAlgorithm.HS256, SECRET_KEY);
return builder.compact();
}
//解析密钥
public static Claims parseJwtToken(String jwtToken) {
JwtParser parser = Jwts.parser().setSigningKey(SECRET_KEY);
return parser.parseClaimsJws(jwtToken).getBody();
}
}
获得令牌的时候调用工具类然后传参:
public static void main(String[] args) {
// 生成JWT令牌
long expirationMillis = 3600000; // 1小时
String jwtToken = generateJwtToken("subject", expirationMillis);
System.out.println("Generated JWT Token: " + jwtToken);
// 解析JWT令牌
Claims claims = parseJwtToken(jwtToken);
System.out.println("Subject: " + claims.getSubject());
System.out.println("Username: " + claims.get("username"));
System.out.println("Role: " + claims.get("role"));
}
}