一、概述
JWT是一种用于身份验证和授权的开放标准(RFC 7519),它定义了一种紧凑且自包含的方式来在不同系统之间传输信息。JWT由三部分组成:头部(Header)、负载(Payload)和签名(Signature)。
- 头部包含了描述该JWT的元数据,通常包含了使用的算法和令牌类型。
- 负载包含了要传输的信息,可以自定义一些声明,例如用户ID、角色等。
- 签名是对头部和负载的签名,用于验证JWT的真实性和完整性。
- JWT是无状态的,可以在不同的请求之间传递,服务器不需要存储任何信息。由于JWT是自包含的,可以直接从JWT中读取信息,无需通过数据库或其他方式进行查询。
- JWT可以被用于各种场景,例如身份验证、用户授权、信息交换等。
基于session认证所显露的问题
Session: 每个用户经过我们的应用认证之后,我们的应用都要在服务端做一次记录,以方便用户下次请求的鉴别,通常而言session都是保存在内存中,而随着认证用户的增多,服务端的开销会明显增大。
扩展性: 用户认证之后,服务端做认证记录,如果认证的记录被保存在内存中的话,这意味着用户下次请求还必须要请求在这台服务器上,这样才能拿到授权的资源,这样在分布式的应用上,相应的限制了负载均衡器的能力。这也意味着限制了应用的扩展能力。
CSRF (跨站请求伪造):因为是基于cookie来进行用户识别的, cookie如果被截获,用户就会很容易受到跨站请求伪造的攻击。
为了解决这些问题,出现了JWT和Token。
二、JWT的三部分
JWT是由三段信息构成的,将这三段信息文本用.链接一起就构成了Jwt字符串。就像这样:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
1.头部
jwt的头部承载两部分信息:
-
声明类型,这里是jwt
-
声明加密的算法 通常直接使用 HMAC HS256
完整的头部就像下面这样的JSON:
{'typ': 'JWT','alg': 'HS256'}-->重新进行编码(不是加密)
然后将头部进行base64转码,构成了第一部分.
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
2.载荷
载荷就是存放有效信息的地方。这个名字像是特指飞机上承载的货品,这些有效信息包含三个部分
-
标准中注册的声明
-
公共的声明
-
公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务 需要的必要信息.但不建议添加敏感信息(例如密码),因为该部分在客户端可解密. id,用户名,头像名
-
私有的声明
定义一个payload
{ "sub": "1234567890", "name": "John Doe","admin": true}
然后将其进行base64转码,得到Jwt的第二部分。
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9
3.签名(类似于比特币)
jwt的第三部分是一个签证信息,这个签证信息由三部分组成:
-
header (base64后的)
-
payload (base64后的)
-
secret
这个部分需要base64转码后的header和base64转码后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。
TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
JWT生成Token的过程类似于比特币生成新区块的过程,这里的签名相当于是私钥,只有自己知道且难以用方法来破解。
三、Java中使用JWT生成token
1.引入JWT依赖
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.8.2</version>
</dependency>
注:可以根据个人情况引入,没必要必须用这个。
2.生成token
/**
* jwt生成token
* @param id
* @param account
* @return
*/
public static String token (Integer id, String account){
String token = "";
try {
//过期时间 为1970.1.1 0:0:0 至 过期时间 当前的毫秒值 + 有效时间
Date expireDate = new Date(new Date().getTime() + 10*1000);
//秘钥及加密算法
Algorithm algorithm = Algorithm.HMAC256("ZCEQIUBFKSJBFJH2020BQWE");
//设置头部信息
Map<String,Object> header = new HashMap<>();
header.put("typ","JWT");
header.put("alg","HS256");
//携带id,账号信息,生成签名
token = JWT.create()
.withHeader(header)
.withClaim("id",id)
.withClaim("account",account)
.withExpiresAt(expireDate)
.sign(algorithm);
}catch (Exception e){
e.printStackTrace();
return null;
}
return token;
}
3.验证token是否正确
public static boolean verify(String token){
try {
//验签
Algorithm algorithm = Algorithm.HMAC256("ZCEQIUBFKSJBFJH2020BQWE");
JWTVerifier verifier = JWT.require(algorithm).build();
DecodedJWT jwt = verifier.verify(token);
return true;
} catch (Exception e) {//当传过来的token如果有问题,抛出异常
return false;
}
}
4.获取token中的负载信息
public class main {
public static void main(String[] args) {
String token;
Date expireDate = new Date(new Date().getTime() + 10*1000);
//秘钥及加密算法
Algorithm algorithm = Algorithm.HMAC256("ZCEQIUBFKSJBFJH2020BQWE");
//设置头部信息
Map<String,Object> header = new HashMap<String, Object>();
header.put("typ","JWT");
header.put("alg","HS256");
//携带id,账号信息,生成签名
token = JWT.create()
.withHeader(header)
.withClaim("id",123)
.withClaim("account",123)
.withExpiresAt(expireDate)
.sign(algorithm);
DecodedJWT verify = JWT.require(algorithm).build().verify(token);
System.out.println(verify.getClaim("id").asInt());
System.out.println(verify.getClaim("account").asInt());
}
}