Java——令牌技术
目录
一、何为令牌
JWT令牌
介绍
JWT组成
二、JWT用于验证用户登录
三、JWT令牌生成和校验
简单用法
1.创建生成密钥的方法
2.接着添加过期时间,密钥,BASE64解码密钥的属性以及生成token的方法,合并上面生成密钥的方法,下面是完整代码。
3.当然也可以写解析token的方法进行验证,通过对称密钥KEY进行对token进行验证,得到载荷部分:
进阶用法
1.创建密钥库文件(JKS)
2.读取JKS密钥库并进行签名,生成token
3.验证Token
一、何为令牌
令牌其实就是一个用户身份的标识,其本质上就是一个字符串。
比如我们出行在外,会带着自己的身份证,需要验证身份时,就要身份证了,而身份证不能伪造,可以辨别真假。
服务器具备生成令牌和验证令牌的能力。
JWT令牌
令牌本质就是一个字符串,它的实现方式有很多种,我们采用一个JWT令牌来实现。
介绍
JWT全称:JSON Web Toekn
官网:https://jwt.io/
JSON Web Token(JWT)是一个开放的行业标准(RFC 7519),用于客户端和服务器之间传递安全可靠的信息。
其本质是一个token,是一种紧凑的URL安全方法。
JWT组成
JWT由三部分组成,使用(.)分隔,比如:aaaaa.bbbbb.cccc,这三部分为:
- Header(头部):头部包括令牌类型(即JWT)及使用的哈希算法(如HMAC SHA256或RSA)
- Payload(负载):负载部分是存放有效信息的地方,里面是一些自定义的内容。比如:{"userId":"123","userName":"zhangsan"},也可以存在jwt提供的现场字段,比如exp(过期时间戳)等 。注意:此部分不建议存放敏感信息,因为此部分能够被解码还原原始内容
- Signature(签名):此部分用于防止jwt内容被篡改,确保安全性。
防止被篡改,而不是防止被解析。
jwt之所以安全,就是因为最后的签名。jwt当中任何一个字符被篡改,整个令牌都会校验失效。这就好比我们的身份证,之所以能标识一个人的身份,是因为不能被篡改,而不是因为内容加密。
二、JWT用于验证用户登录
具体流程如下
1.用户登录(生成JWT)
(1)用户提交凭证
* 场景:用户输入用户名和密码,点击登录。
(2)认证服务器验证凭证
验证流程:
* 检查用户名和密码是否匹配数据库中的记录
* 确认用户状态(是否被禁用)
(3)生成JWT
JWT结构:
* Header:声明算法类型(如"alg": "RS256", "typ": "JWT"}
).
* Payload:包含用户身份和权限信息({"sub": "user123", "roles": ["user"], "exp": 1720000000}
)。
* Signature:使用私钥对头部和载荷签名 (如 RSA-SHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), privateKey)
)。
2.客户端存储并携带JWT
客户端把令牌存储起来,可以存储在Cookie中,也可以存储在其他的存储空间。
3.服务器验证JWT
(1)解析JWT结构
(2)验证签名
非对称加密流程:从认证服务器获取公钥,使用公钥重新计算签名,比对JWT中的签名是否一致。同时验证令牌是否有效,有效就说明执行了登录操作,否则就是未登录。
总结:
1.用户登录请求,经过负载均衡,把请求转给了第一台服务器,第一台服务器进行账号密码验证,验证成功后,生成一个令牌,并返回给客户端。
2.客户端收到令牌后,把令牌存储起来,可以存储在Cookie中,也可以存储在其他的存储空间。
3.用户登录成功后,携带令牌继续执行查询操作,比如查询博客列表,此时请求转发到了第二台机器 ,第二台机器会先进行权限验证操作。服务器验证令牌是否有效,就说明执行了登录操作,如果令牌是无效的,就说明用户之前未执行登录操作。
三、JWT令牌生成和校验
在使用之前先引入相关的依赖:
放在<dependencies> </dependencies>标签下:
<!-- 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>
简单用法
主要思想是利用生成的对称密钥对token进行签名(加密)和验证(解密)。
下面代码主要利用测试类进行讲解:
1.创建生成密钥的方法
@Testpublic void genKey(){//随机生成一个key,HS256算法生成 。H256是一种堆成密钥算法SecretKey secretKey = Keys.secretKeyFor(SignatureAlgorithm.HS256);//利用BASE64对密钥进行编码String key = Encoders.BASE64.encode(secretKey.getEncoded());System.out.println(key);}
运行后生成结果如下:
B2S6Xk0/9aDxArJCaVWEAa2moixi3RVx+O8oraRQ3cQ=
2.接着添加过期时间,密钥,BASE64解码密钥的属性以及生成token的方法,合并上面生成密钥的方法,下面是完整代码。
@SpringBootTest
public class JWTUtilTest {//过期毫秒时长10年public static final long Expiration =10 * 365 * 24 * 60 * 60 * 1000L;;//密钥,就是利用下面genKey()方法生成的密钥private static final String secretSrting =
"B2S6Xk0/9aDxArJCaVWEAa2moixi3RVx+O8oraRQ3cQ="; //生成安全密钥,BASE64解码private static final SecretKey KEY =Keys.hmacShaKeyFor(Decoders.BASE64.decode(secretSrting));//生成token@Testpublic void genToken(){//生成载荷部分Map<String,Object> claim = new HashMap<>();claim.put("id",1);claim.put("username","zhangsan");//自定义信息String jwt = Jwts.builder().setClaims(claim)//自定义内容(负载).setExpiration(new Date(System.currentTimeMillis()+Expiration))//设置过期时间.signWith(KEY) //密钥签名.compact();System.out.println(jwt);}//生成密钥@Testpublic void genKey(){//随机生成一个key,HS256算法生成SecretKey secretKey = Keys.secretKeyFor(SignatureAlgorithm.HS256);String key = Encoders.BASE64.encode(secretKey.getEncoded());System.out.println(key);}}
运行genToken()方法,得到token字符串。
eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwidXNlcm5hbWUiOiJ6aGFuZ3NhbiIsImV4cCI6MjA2MTAwMTY0OX0.
7eFIjaWuKEZTFnBlrVgdVtplVowslGnd_VHenj3qQpU
通过得到的token和密钥到官网去验证JSON Web Tokens - jwt.ioJSON Web Token (JWT) is a compact URL-safe means of representing claims to be transferred between two parties. The claims in a JWT are encoded as a JSON object that is digitally signed using JSON Web Signature (JWS).https://jwt.io/结果如下:
3.当然也可以写解析token的方法进行验证,通过对称密钥KEY进行对token进行验证,得到载荷部分:
//校验token信息@Testpublic void parseToken(){String token ="eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwidXNlcm5hbWUiOiJ6aGFuZ3NhbiIsImV4cCI6MjA2MTAwMTY0OX0.7eFIjaWuKEZTFnBlrVgdVtplVowslGnd_VHenj3qQpU";//利用对称密钥再进行验证tokenJwtParser bulid = Jwts.parserBuilder().setSigningKey(KEY).build();Claims body = bulid.parseClaimsJws(token).getBody();System.out.println(body);}
运行结果如下:
进阶用法
处于安全考虑,我们可以通过RSA算法生成非对称密钥,利用私钥签名,利用公钥进行验证,同时通过还需要这对对密钥进行管理,将其管理在密钥库中。
下面是实现步骤:
1.创建密钥库文件(JKS)
我们需要把生成这对对 公钥-私钥 ,并将其保存在密钥库中。可以使用keytool命令创建JKS文件(在idea中的终端运行)。
命令如下:
keytool -genkeypair -alias myalias -keyalg RSA -keysize 2048 -keystore mykeystore.jks -storepass keystorepass -validity 365
命令解析:
keytool -genkeypair \-alias myalias \ # 密钥条目别名(自定义标识)-keyalg RSA \ # 密钥算法(RSA 非对称加密)-keysize 2048 \ # 密钥长度(2048 位,安全推荐值)-keystore mykeystore.jks \# 密钥库文件名(保存路径)-storepass keystorepass \ # 密钥库密码(保护整个文件)-validity 365 # 证书有效期(365 天)
此命令用于生成一个java密钥库(JKS文件),并在其中创建一个RSA密钥(非对称)对。
运行后keytool会提示输入相关信息(学习阶段随便填即可):
您的名字与姓氏是什么?[Unknown]: localhost # 建议填域名(如服务器域名或本地测试填 localhost)
您的组织单位名称是什么?[Unknown]: MyOrg # 组织单位(可选)
您的组织名称是什么?[Unknown]: MyCompany # 组织名称(可选)
您所在的城市或区域名称是什么?[Unknown]: Beijing # 城市(可选)
您所在的省/市/自治区名称是什么?[Unknown]: Beijing # 省份(可选)
该单位的双字母国家/地区代码是什么?[Unknown]: CN # 国家代码(如 CN 表示中国)
CN=localhost, OU=MyOrg, O=MyCompany, L=Beijing, ST=Beijing, C=CN是否正确?[否]: 是 # 确认信息
输入完后,会提示输入口令,注意这个口令是和后面获取私钥的时候挂钩的,我们可以直接按回车和密钥库密码一样,同时也可以自定义。
输入完后,项目里面会生成jks文件,我们可以剪贴到resource目录下,后面需要该文件路径。
2.读取JKS密钥库并进行签名,生成token
下面是详细代码:
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;import java.io.FileInputStream;
import java.security.Key;
import java.security.KeyStore;
import java.util.Date;
import java.util.Map;public class JWTUtils {// EYSTORE_PASSWORD(密钥库密码):用于保护整个 JKS 密钥库文件。在加载密钥库时需要使用此密码。//配置密钥库的路径、别名。访问密码private static final String KETSTORE_PATH = "src/main/resources/mykeystore.jks";//密钥库密码private static final String KEYSTORE_PASSWORD = "keystorepass";//密钥别名private static final String KEY_ALIAS = "myalias";//访问私钥的密码(刚刚自己输入的口令)private static final String KEY_PASSWORD = "123456";//从jks文件中加载私钥private static Key getPrivateKey() throws Exception {KeyStore keyStore = KeyStore.getInstance("JKS");try (FileInputStream keyStoreStream = new FileInputStream(KETSTORE_PATH)){keyStore.load(keyStoreStream, KEYSTORE_PASSWORD.toCharArray());}return keyStore.getKey(KEY_ALIAS, KEY_PASSWORD.toCharArray());}//生成TOKENpublic static String generateToken(Map<String,Object> claim)throws Exception{Key privateKey = getPrivateKey();return Jwts.builder().setClaims(claim)//自定义内容(负载).setIssuedAt(new Date()).setExpiration(new Date(System.currentTimeMillis()+60*60*1000)) //过期时间1小时.signWith(privateKey, SignatureAlgorithm.RS256) //使用私钥签名,相当于用私钥加密.compact();}
}
3.验证Token
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;import java.io.FileInputStream;
import java.security.KeyStore;
import java.security.PublicKey;
import java.util.HashMap;
import java.util.Map;public class JwtValidator {//获取公钥,公钥无需密码去访问直接获取即可private static PublicKey getpublicKey()throws Exception{KeyStore keyStore = KeyStore.getInstance("JKS");try (FileInputStream keyStoreStream = new FileInputStream("src/main/resources/mykeystore.jks")) {keyStore.load(keyStoreStream,"keystorepass".toCharArray());}return keyStore.getCertificate("myalias").getPublicKey();}//验证JWT并解析public static Claims validateToken(String token)throws Exception{PublicKey publicKey = getpublicKey();return Jwts.parserBuilder().setSigningKey(publicKey) //使用公钥验证.build().parseClaimsJws(token)//解析jwt.getBody();}//运行main方法执行程序public static void main(String[] args) throws Exception {//写入相关参数Map<String,Object> claim = new HashMap<>();claim.put("id",1);claim.put("username","zhangsan");String token = JWTUtils.generateToken(claim);
// System.out.println(token);Claims claims = validateToken(token);System.out.println(claims);}
}
运行结果:
以上是JWT令牌的讲解,喜欢的支持一下,感谢观看!!!