什么是 JWT
JSON Web Token(JWT)是目前最流行的跨域身份验证解决方案
JSON Web Token Introduction - jwt.ioLearn about JSON Web Tokens, what are they, how they work, when and why you should use them.https://jwt.io/introduction
一、常见会话
cookie、session、token
-cookie:客户端浏览器的键值对
-session:服务的的键值对(djangosession表,内存中,文件,缓存数据库)
-token:服务的生成的加密字符串,如果存在客户端浏览器上,就叫cookie
-三部分:头,荷载,签名
-签发:登录成功,签发
-认证:认证类中认证
jwt (Json web token)
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
二、base64编码和解码
base64并不是一种加密反射,只是编码解码方式
- 字符串,可以转成base64编码格式:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
- eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 解码成base64
import json
import base64
d = {'user_id': 1, 'username': "lqz"}
d_str = json.dumps(d)
print(d_str)
# # 对字符串进行bashe64 编码
res=base64.b64encode(bytes(d_str,encoding='utf-8'))
print(res) # eyJ1c2VyX2lkIjogMSwgInVzZXJuYW1lIjogImxxeiJ9
# 解码
res=base64.b64decode('TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ=')
print(res)
ps:记住: base64 编码,长度一定是4的倍数。如果不够,用 = 补齐
base64的用途
- 互联网中,前后端数据交互,使用base64编码
- jwt 字符串使用base64编码
- 互联网中一些图片,使用base64编码
s = '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'
res = base64.b64decode(s)
with open('code.png', 'wb') as f:
f.write(res)
三、 JWT认证
在用户注册或登录后,我们想记录用户的登录状态,或者为用户创建身份认证的凭证。我们不再使用Session认证机制,而使用Json Web Token(本质就是token)认证机制
使用jwt认证和使用session认证的区别
加密原理
在对称加密的时代,加密和解密用的是同一个密钥,这个密钥既用于加密,又用于解密。这样做有一个明显的缺点,如果两个人之间传输文件,两个人都要知道密钥,如果是三个人呢,五个人呢?于是就产生了非对称加密,用一个密钥进行加密(公钥),用另一个密钥进行解密(私钥)
1.公钥私钥原理
张三有两把钥匙,一把是公钥,另一把是私钥。
张三把公钥送给他的朋友们李四、王五、赵六每人一把。
李四要给张三写一封保密的信。她写完后用张三的公钥加密,就可以达到保密的效果。
张三收信后,用私钥解密,就看到了信件内容。这里要强调的是,只要张三的私钥不泄露,这封信就是安全的,即使落在别人手里,也无法解密。
张三给李四回信,决定采用“数字签名”。他写完后先用Hash函数,生成信件的摘要(digest)。然后利用私钥将摘要进行加密,张三将这个签名,附在信件下面,一起发给李四。
李四收信后,取下数字签名,用张三的公钥解密,得到信件的摘要。由此证明,这封信确实是张三发出的。李四再对信件本身使用Hash函数,将得到的结果,与上一步得到的摘要进行对比。如果两者一致,就证明这封信未被修改过。
四、JWT生产公钥与私钥
找到JDK环境的keytool(证书管理工具)
生成私钥
keytool -genkeypair -alias test110 -keyalg RSA -keypass miyaomima -keystore xc.keystore -storepass miyaokumima
建值 | 解释说明 | 测试用例 |
-alias | 证书的别名 | test110 |
-keyalg | 使用的hash算法 | RSA |
-keypass | 密钥的访问密码 | miyaomima |
-keystore | 生成的文件的名字 | xc.keystore |
xc.keystore | 保存了生成的证书 | |
-storepass | 密钥库的访问密码 | miyaokumima |
Windows系统需以管理员身份运行
查看证书文件是否生成成功
查看密钥
keytool -list -keystore xc.keystore
删除别名
keytool -delete -alias test110 -keystore xc.keystore
生成公钥
配置openssl
Win32/Win64 OpenSSL Installer for Windows - Shining Light Productionshttp://slproweb.com/products/Win32OpenSSL.html找一个合适自己计算机版本的进行配置
点击下载
这里小编使用默认路径进行安装
配置path环境变量
本教程配置在C:\Program Files\OpenSSL-Win64\bin
cmd进入xc.keystore文件所在目录执行命令 | 右键终端打开
keytool -list -rfc --keystore xc.keystore | openssl x509 -inform pem -pubkey
将上边的公钥拷贝到文本public.key文件中,合并为一行,可以将它放到需要实现授权认证的工程中
Attention
证书文件不要放在有中文路径下
所有文字都要在同一行
五、项目实战
引入JWT的依赖
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.10.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.10.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.10.5</version>
<scope>runtime</scope>
</dependency>
引入其他依赖
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>2.9.4</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.16</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.54</version>
</dependency>
创建User对象
public class UserInfo {
private Long id;//用户ID
private String username;//用户名称
private String role;//用户角色
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
}
创建JWT工具类
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONUtil;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.joda.time.DateTime;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.Base64;
import java.util.UUID;
public class JwtUtils {
private static final String JWT_PAYLOAD_USER_KEY = "user";
/**
* 私钥加密token
*
* @param info 载荷中的数据
* @param privateKey 私钥
* @param expire 过期时间,单位分钟
* @return JWT
*/
public static String generateTokenExpireInMinutes(Object info, PrivateKey privateKey, int expire) {
return Jwts.builder()
//claim: 往Jwt的载荷存入数据
.claim(JWT_PAYLOAD_USER_KEY, ObjectUtil.toString(info))
//往Jwt的载荷存入数据,设置固定id的key
.setId(createJTI())
//往Jwt的载荷存入数据,设置固定exp的key
.setExpiration(DateTime.now().plusMinutes(expire).toDate())
//设置token的签名
.signWith(privateKey, SignatureAlgorithm.RS256)
.compact();
}
/**
* 私钥加密token
*
* @param info 载荷中的数据
* @param privateKey 私钥
* @param expire 过期时间,单位秒
* @return JWT
*/
public static String generateTokenExpireInSeconds(Object info, PrivateKey privateKey, int expire) {
return Jwts.builder()
.claim(JWT_PAYLOAD_USER_KEY, ObjectUtil.toString(info))
.setId(createJTI())
.setExpiration(DateTime.now().plusSeconds(expire).toDate())
.signWith(privateKey, SignatureAlgorithm.RS256)
.compact();
}
/**
* 公钥解析token
*
* @param token 用户请求中的token
* @param publicKey 公钥
* @return Jws<Claims>
*/
private static Jws<Claims> parserToken(String token, PublicKey publicKey) {
return Jwts.parser().setSigningKey(publicKey).parseClaimsJws(token);
}
private static String createJTI() {
return new String(Base64.getEncoder().encode(UUID.randomUUID().toString().getBytes()));
}
/**
* 获取token中的用户信息
*
* @param token 用户请求中的令牌
* @param publicKey 公钥
* @return 用户信息
*/
public static <T> Payload<T> getInfoFromToken(String token, PublicKey publicKey, Class<T> userType) {
Jws<Claims> claimsJws = parserToken(token, publicKey);
Claims body = claimsJws.getBody();
Payload<T> claims = new Payload<>();
claims.setId(body.getId());
claims.setInfo(JSONUtil.toBean(body.get(JWT_PAYLOAD_USER_KEY).toString(), userType));
claims.setExpiration(body.getExpiration());
return claims;
}
/**
* 获取token中的载荷信息
*
* @param token 用户请求中的令牌
* @param publicKey 公钥
* @return 用户信息
*/
public static <T> Payload<T> getInfoFromToken(String token, PublicKey publicKey) {
Jws<Claims> claimsJws = parserToken(token, publicKey);
Claims body = claimsJws.getBody();
Payload<T> claims = new Payload<>();
claims.setId(body.getId());
claims.setExpiration(body.getExpiration());
return claims;
}
}
创建RSA工具类
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
public class RsaUtils {
private static final int DEFAULT_KEY_SIZE = 2048;
/**
* 从文件中读取公钥
*
* @param filename 公钥保存路径,相对于classpath
* @return 公钥对象
* @throws Exception
*/
public static PublicKey getPublicKey(String filename) throws Exception {
byte[] bytes = readFile(filename);
return getPublicKey(bytes);
}
/**
* 从文件中读取密钥
*
* @param filename 私钥保存路径,相对于classpath
* @return 私钥对象
* @throws Exception
*/
public static PrivateKey getPrivateKey(String filename) throws Exception {
byte[] bytes = readFile(filename);
return getPrivateKey(bytes);
}
/**
* 获取公钥
*
* @param bytes 公钥的字节形式
* @return
* @throws Exception
*/
private static PublicKey getPublicKey(byte[] bytes) throws Exception {
bytes = Base64.getDecoder().decode(bytes);
X509EncodedKeySpec spec = new X509EncodedKeySpec(bytes);
KeyFactory factory = KeyFactory.getInstance("RSA");
return factory.generatePublic(spec);
}
/**
* 获取密钥
*
* @param bytes 私钥的字节形式
* @return
* @throws Exception
*/
private static PrivateKey getPrivateKey(byte[] bytes) throws NoSuchAlgorithmException, InvalidKeySpecException {
bytes = Base64.getDecoder().decode(bytes);
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(bytes);
KeyFactory factory = KeyFactory.getInstance("RSA");
return factory.generatePrivate(spec);
}
/**
* 根据密文,生存rsa公钥和私钥,并写入指定文件
*
* @param publicKeyFilename 公钥文件路径
* @param privateKeyFilename 私钥文件路径
* @param secret 生成密钥的密文
*/
public static void generateKey(String publicKeyFilename, String privateKeyFilename, String secret, int keySize) throws Exception {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
SecureRandom secureRandom = new SecureRandom(secret.getBytes());
keyPairGenerator.initialize(Math.max(keySize, DEFAULT_KEY_SIZE), secureRandom);
KeyPair keyPair = keyPairGenerator.genKeyPair();
// 获取公钥并写出
byte[] publicKeyBytes = keyPair.getPublic().getEncoded();
publicKeyBytes = Base64.getEncoder().encode(publicKeyBytes);
writeFile(publicKeyFilename, publicKeyBytes);
// 获取私钥并写出
byte[] privateKeyBytes = keyPair.getPrivate().getEncoded();
privateKeyBytes = Base64.getEncoder().encode(privateKeyBytes);
writeFile(privateKeyFilename, privateKeyBytes);
}
private static byte[] readFile(String fileName) throws Exception {
return Files.readAllBytes(new File(fileName).toPath());
}
private static void writeFile(String destPath, byte[] bytes) throws IOException {
File dest = new File(destPath);
if (!dest.exists()) {
dest.createNewFile();
}
Files.write(dest.toPath(), bytes);
}
}
创建PayLoad工具类
import java.util.Date;
public class Payload<T>{
private String id;
private T info;
private Date expiration;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public T getInfo() {
return info;
}
public void setInfo(T info) {
this.info = info;
}
public Date getExpiration() {
return expiration;
}
public void setExpiration(Date expiration) {
this.expiration = expiration;
}
}
创建测试类
import com.alibaba.fastjson.JSONObject;
public class Test {
private static final String localPath = "\\main\\java\\com\\coldwind\\xutils\\jwt\\";
public static void generateKey() {
String proPath = System.getProperty("user.dir") + "\\src";
proPath = proPath.replaceAll("\\\\", "\\\\\\\\");
String keyPath = proPath+ localPath;
String priKey = keyPath + "private.key";
String pubKey = keyPath + "public.key";
String secret = "123456";
try {
RsaUtils.generateKey(pubKey, priKey, secret, 2048);
} catch (Exception e) {
e.printStackTrace();
}
}
public static void test() throws Exception {
String proPath = System.getProperty("user.dir") + "\\src";
proPath = proPath.replaceAll("\\\\", "\\\\\\\\");
String keyPath = proPath+localPath;
UserInfo userInfo = new UserInfo();
userInfo.setUsername("admin");
userInfo.setId(1L);
String token = JwtUtils.generateTokenExpireInMinutes(JSONObject.toJSONString(userInfo), RsaUtils.getPrivateKey(keyPath+"private.key"), 10);
System.out.println(token);
Payload<UserInfo> userInfoPayload = JwtUtils.getInfoFromToken(token, RsaUtils.getPublicKey(keyPath+"public.key"),UserInfo.class);
System.out.println(JSONObject.toJSONString(userInfoPayload));
}
public static void main(String[] args) throws Exception {
test();
}
}
第一步:先在执行目录下生成公钥、私钥
第二步:模拟私钥加密
第三部:模拟公钥解密
执行效果
eyJhbGciOiJSUzI1NiJ9.eyJ1c2VyIjoie1wiaWRcIjoxLFwidXNlcm5hbWVcIjpcImFkbWluXCJ9IiwianRpIjoiTXprMVpEQmtOR010TWpZMFpTMDBPREl3TFRsaFpEZ3ROamRqTkdNelkyUTVNVEE1IiwiZXhwIjoxNzE3ODk3NTU2fQ.TrsJToMTUpkP6DbtyCL16-2d1WTTxPmOhb4BQyAFB6_nUg94xKu6q-lSfZNi3e7BmGxNrJYYNUyJZxaMO20ZQ88OJdeYbNfZ8ixxv2zw8QrmIFTGzHLqximXvl6kMZWEd_ArKRi2ba5NDQlTAvqJfQSv5hjiY-Rug_RX_Pd4ak81kVWOYOhJUMvsfoVlp6typoiFSWdjp024mkux4GtQfexZy0_CbLTX5KI4bVioSgxREPAhH68bmx-LcQQ9GEtcCidEZu0FsA6ubqnE0IWw5g6TJhOnuNdZOT2QWsv9WPaxhOJllV1mAqeXwnJufSZa-VUYgVjQhNnAWXbcNBSUqA
{"expiration":1717897556000,"id":"Mzk1ZDBkNGMtMjY0ZS00ODIwLTlhZDgtNjdjNGMzY2Q5MTA5","info":{"id":1,"username":"admin"}}