目录
什么是令牌技术?
令牌技术: 是一种用于身份验证 和授权的方法,常用于网络安全和信息技术领域。在计算机系统中,令牌是一种用于证明用户身份或授权访问的凭证,可以是基于硬件的安全令牌,也可以是基于软件的生成代码或数据。令牌技术允许用户获取一个特定的令牌后,在之后的通信中使用该令牌来证明自己的身份或获得授权。
为什么要使用令牌技术?
我们通过一个例子:用户登录 来进一步理解令牌技术的使用
在实现用户登录时,我们可以使用 cookie 和 session 来验证用户身份:
用户提交用户名和密码后,登录页面将用户名、密码提交给服务器
服务器验证用户名密码是否正确
若验证成功,服务器创建一个 Session,并将SessionId 存储在 Cookie中,将用户身份信息存储在Session中
在之后的请求中,用户的游览器都会发送包含SessionId 的 Cookie 到服务器
服务器根据这个SessionId 来检索 Session 数据,并根据其中存储的身份信息来验证用户的身份
使用 cookie 和 session 实现身份验证时,需要服务器存储用户的身份信息到会话中,
然而,此时就会存在问题:集群环境下无法使用Session
对于开发的项目,当部署在一台机器上时,容易发送单点故障(当该服务器出现故障或需要维护时,所有用户的会话数据都会丢失,导致所有用户需要重新登录),因此,在通常情况下,一个Web应用会部署在多个服务器上,通过Nginx等实现负载均衡,此时来自用户的请求就会被分配到不同的服务器上
若使用Session进行会话跟踪,可能出现一些情况:
用户登录:用户登录请求,经过负载均衡,把请求转给了第一台服务器,第一台服务器进行账号密码验证,验证成功后,将Session存放在了第一台服务器上
查询操作:用户登录成功之后,携带Cookie(里面有SessionId)继续查询操作,此时请求转发到了第二台机器,第二台机器会进行权限验证操作(通过SessionId验证用户是否登录),此时由于第二台机器上未存储该用户的Session,就会出现问题
为了解决上述问题,我们可以将 Session 存储在可靠的持久化存储中,如数据库或缓存服务
但此时就需要存储大量用户数据信息
为了解决上述问题,我们可以使用令牌技术:
当用户提交账号和密码后,服务器进行校验
校验成功,生成令牌(Token),将其返回给客户端
客户端携带 Token 再次进行访问,服务器对 Token 进行校验
服务器校验 Token 的真假,若为真,则提供服务
此时,由客户端存储令牌,减轻了服务器的存储压力,且在集群环境下也能进行认证
我们再通过一个生活中的例子进一步理解令牌技术:
令牌就类似于我们的身份证:
用户提供户口本等信息,交由公安局进行校验,公安局校验成功后,发放身份证
用户保存身份证
用户上学、出行、旅游等携带身份证
酒店等通过公安局提供的专用设备核验身份证真假,若为真,则提供服务
JWT令牌
我们可以通过令牌来解决上述问题:
**令牌(Token)**的本质就是一个字符串,其实现方式很多,如JWT令牌、刷新令牌、Bearer令牌等
在这里,我们学习JWT令牌
**JWT令牌:JWT(JSON Web Token)**是一种开放的标准(RFC 7519),用于在各方之间安全地传输信息作为 JSON 对象。JWT 可以通过数字签名或加密来验证其可靠性,通常用于在客户端和服务器之间安全地传递身份验证信息
JWT官网:JSON Web Tokens - jwt.io
JWT由三部分组成,分别是头部(Header) 、载荷(Payload) 和签名(Signature),每个部分之间使用 . 分割
头部(Header) :包括令牌类型 (即JWT)和使用的哈希算法(如 HMAC、SHA256)
载荷(Payload):存放有效信息,其中是一些自定义的内容(如:{"userName": "zhangsan"}),也可以存储JWT提供的默认字段,如 exp(过期时间戳)等
JWT指定了七个默认字段:
iss(Issuer):该JWT的签发者
sub(Subject):令牌的主题,表示该JWT的主体,即该JWT所代表的用户或实体
aud(Audience):JWT的受众,该JWT的使用对象
exp(Expiration Time):JWT的过期时间,表示该JWT的有效期限,超过该时间后JWT将不再可用
nbf(Not Before):JWT的生效时间,在该时间之前,该JWT都是不可用的
iat(Issued At):JWT的签发时间
jti(JWT ID):JWT的唯一标识符,表示该JWT的唯一性,可用于防止重放攻击
签名(Signature) :用于验证消息的完整性和可靠性,防止JWT内容被篡改,确保安全性
签名用于验证消息的完整性和可靠性,JWT中任何一个字符被篡改,整个令牌都会校验失败
但其不确保其保密性,即不能防止被解析,也就是任何人都可以看到JWT中的信息(就如身份证,任何人都可以看到身份证上的信息,但不能篡改身份证上的信息)
因此 JWT令牌不适合存放敏感信息(如用户密码等)
当没有签名时,我们仍然能够看到JWT中的信息,即令牌不保证保密性
在了解了JWT令牌后,我们来学习JWT令牌的生成和校验
JWT令牌的生成和校验
首先,我们需要引入 JWT 令牌的依赖
java
<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-api -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-impl -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId> <!-- or jjwt-gson if Gson is preferred -->
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
接着,我们就可以使用Jar包中提供的API来完成JWT令牌的生成和校验了
我们首先来看生成令牌:
Header:
JWT的头部包含两部分信息:
令牌类型(alg):JWT
加密算法(typ):可选择 HS256、HS384、HS512、RS256...
java
//头部
Map<String, Object> header = new HashMap<>();
header.put("alg", "HS256");
header.put("typ", "JWT");
//生成令牌
String token = Jwts.builder()
.setHeader(header) // 可以不设定,此时使用默认的
Payload:
Paylod通常是一个JSON对象,包含了JWT的所有声明(claims)以及其他需要传递的信息
载荷包含两种类型数据:
自定义数据
标准中注册的声明数据
java
long JWT_EXPIRATION = 60 * 60 * 1000;
// 自定义信息
Map<String, Object> claim = new HashMap<>();
claim.put("id", 1);
claim.put("userName", "zhangsan");
//生成令牌
String token = Jwts.builder()
.setClaims(claim) // 自定义信息
.setIssuedAt(new Date()) // 签发时间
.setExpiration(new Date(System.currentTimeMillis() + JWT_EXPIRATION)) // 过期时间
Signature:
签名内容需要通过计算得出:
base64UrlEncode(header) + "." + base64UrlEncode(payload) + "." + your-256-bit-secret
将待签名消息进行编码:
通常采用Base64编码将 header 和 payload转换为字符串
选择密钥:
我们以 HS256(HMAC with SHA-256)加密算法为例,我们需要选择一个密钥(密钥是进行签名计算的关键)
使用加密算法计算签名:
然后通过 header 中声明的加密算法 进行签名计算(仍以 HS256为例),将 编码后的消息 和 密钥输入 HMAC 算法中,进行 SHA-256哈希处理,生成签名
将签名添加到JWT中:
将生成的签名添加到JWT的尾部,形成最终的JWT
我们首先生成随机密钥:
java
@Test
//生成随机密钥
public void genKey() {
SecretKey secretKey = Keys.secretKeyFor(SignatureAlgorithm.HS256);
String secretStr = Encoders.BASE64.encode(secretKey.getEncoded());
System.out.println(secretStr);
}
运行,得到密钥:
以运行结果作为随机密钥:
java
String secretStr = "IufNeZtdSwoMADOxF3yofGqrpjAhNx58C/bomQ27NKg=";
Key key = Keys.hmacShaKeyFor(Decoders.BASE64.decode(secretStr));
// 生成令牌
String token = Jwts.builder()
.setHeader(header) // 可以不设定,此时使用默认的
.setClaims(claim) // 自定义信息
.setIssuedAt(new Date()) // 签发时间
.setExpiration(new Date(System.currentTimeMillis() + JWT_EXPIRATION)) // 过期时间
.signWith(key)
.compact();
生成令牌:
java
private static final long JWT_EXPIRATION = 60 * 60 * 1000;
@Test
public void genJwt() {
//头部
Map<String, Object> header = new HashMap<>();
header.put("alg", "HS256");
header.put("typ", "JWT");
// 自定义信息
Map<String, Object> claim = new HashMap<>();
claim.put("id", 1);
claim.put("userName", "zhangsan");
// 生成密钥
String secretStr = "IufNeZtdSwoMADOxF3yofGqrpjAhNx58C/bomQ27NKg=";
Key key = Keys.hmacShaKeyFor(Decoders.BASE64.decode(secretStr));
// 生成令牌
String token = Jwts.builder()
.setHeader(header) // 可以不设定,此时使用默认的
.setClaims(claim) // 自定义信息
.setIssuedAt(new Date()) // 签发时间
.setExpiration(new Date(System.currentTimeMillis() + JWT_EXPIRATION)) // 过期时间
.signWith(key) // 签名
.compact();
System.out.println(token);
}
运行,查看得到的令牌:
我们将得到的令牌复制到官网上解析:
将密钥复制到 your-256-bit-secret位置
校验通过
接下来,我们实现令牌的校验:
我们需要使用之前生成的随机密钥来进行解密:
java
private static final String secretStr = "IufNeZtdSwoMADOxF3yofGqrpjAhNx58C/bomQ27NKg=";
private static final Key key = Keys.hmacShaKeyFor(Decoders.BASE64.decode(secretStr));
@Test
public void parseToken() {
// 令牌
String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwidXNlck5hbWUiOiJ6aGFuZ3NhbiIsImlhdCI6MTcxNzgyMDA3MywiZXhwIjoxNzE3ODIzNjczfQ.MKqOIgYTmmJMPktpbjsWq4WDmkuceCQPq2tJQoJVSpE";
// 解析令牌
JwtParser build = Jwts.parserBuilder().setSigningKey(key).build();
Claims claims = build.parseClaimsJws(token).getBody();
System.out.println(claims);
}
运行,我们可以看到解析的消息
令牌的优缺点
优点:
解决了集群环境下的认证问题
减轻了服务器的存储压力,即无需在服务器端存储
令牌可以通过加密或签名等方式进行安全传输和存储,减少了被盗用的风险,更安全
缺点:
相对于传统的基于会话的认证机制,令牌认证通常需要更多的技术和知识,包括令牌签发、验证、刷新等流程,因此实现和维护的成本较高
如果令牌泄露,可能会导致安全风险,因此需要进行令牌的安全存储和传输