JWT
把书本读薄,简单介绍一下JWT所具备的特征,最好提前了解 “编码”、“摘要” 和 “加密” 的区别。
JWT简介
JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed. JWTs can be signed using a secret (with the HMAC algorithm) or a public/private key pair using RSA or ECDSA.
JWT是一种设计令牌的开放标准。
JWT结构
JWT分为3段,用“.”连接,其中:“头部”和“载荷”通过Base64进行编码的,“签名”是将前2段连接在一起,通过加密算法得出的校验码。
HEADER(头部).PAYLOAD(载荷).SIGNATURE(签名)
头部
Base64解码后是一段JSON,用于说明jwt所采用的算法
alg: HS256
typ: JWT
载荷
Base64解码后是一段JSON,用于存储jwt的详细信息,字段只是推荐,并没有强制要求这些字段必须存在
iss:发行人
exp:token的到期时间
sub:主题
aud:用户
nbf:token在此之前不可用
iat:发布时间
jti:JWT ID用于标识该JWT
签名
“头部”和“载荷”使用Base64编码,编码并不是加密,用户可以随时解码,查看里面的内容。
允许用户看token的内容,又不允许他们修改,这就是“签名”的作用——防止用户伪造token。
比如:token过期了,用户偷偷修改了token的有效期,这时候,服务器只要把数据再加密一遍,如果加密的结果不一致,就说明token被修改过。
DEMO
自带的加密算法有这么多:none、HS256、HS384、HS512、RS256、RS384、RS512、ES256、ES384、ES512、PS256、PS384、PS512。
挑2个展示效果,对称加密HS256,非对称加密RS256。
Maven依赖
<dependency> <groupId>com.auth0groupId> <artifactId>java-jwtartifactId> <version>3.17.0version> dependency> <dependency> <groupId>io.jsonwebtokengroupId> <artifactId>jjwtartifactId> <version>0.9.1version> dependency>
HS256
对称加密,加密和解密需要相同的密钥。
import io.jsonwebtoken.Claims; import io.jsonwebtoken.JwtBuilder; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import javax.crypto.SecretKey; import java.util.Base64; import java.util.Date; /** * @author Mr.css * @date 2022-01-05 15:03 */ public class JwtHS256 { // /** // * 生成密钥 // * // * @param key 长度必须是32的倍数 // * @return SecretKey // * @throws IllegalArgumentException - // */ // public static SecretKey generateSecretKey(byte[] key) { // if (key.length % 32 != 0) { // throw new IllegalArgumentException("key size must be a multiple of 32"); // } // return new SecretKeySpec(key, "AES"); // } /** * 密钥,更复杂地,应该将密钥交由数据库管理 */ private static SecretKey secretKey = AES.generateSecretKey(MD5.hexBit32("this is a secret key!").getBytes()); /** * 生成jwt * * @param id ID * @param subject 主题 * @param ttlMillis 超时时间 * @return jwt */ public static String encrypt(String id, String subject, long ttlMillis) { long nowMillis = System.currentTimeMillis(); Date now = new Date(nowMillis); JwtBuilder builder = Jwts.builder(); //ID builder.setId(id); //主题 builder.setSubject(subject); // 签发者 builder.setIssuer("system"); // 签发时间 builder.setIssuedAt(now); // 签名算法以及密匙 builder.signWith(SignatureAlgorithm.HS256, secretKey); // 设置超时时间 if (ttlMillis >= 0) { long expMillis = nowMillis + ttlMillis; Date expDate = new Date(expMillis); builder.setExpiration(expDate); } return builder.compact(); } /** * 解析JWT字符串 * * @param jwt jwt * @return Claims */ public static Claims decrypt(String jwt) { return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(jwt).getBody(); } public static void main(String[] args) throws InterruptedException { //解密 String jwt = encrypt("id", "sub", 3000); System.out.println(jwt); //解密 Claims claims = decrypt(jwt); System.out.println(claims); //解析头部 System.out.println( new String(Base64.getDecoder().decode("eyJhbGciOiJIUzI1NiJ9"))); //解析载荷 System.out.println( new String(Base64.getDecoder().decode("eyJqdGkiOiJpZCIsInN1YiI6InN1YiIsImlzcyI6InN5c3RlbSIsImlhdCI6MTY0MTM3NTc3MywiZXhwIjoxNjQxMzc1Nzc2fQ"))); //5秒之后token失效 Thread.sleep(5000); claims = decrypt(jwt); System.out.println(claims); } }
RS256
代码上与HS256基本一致。JWT限定了只能用私钥加密,公钥解密。
什么情况下使用RS256?
令牌由其它服务器发出,我们的服务器只负责调用,我们在使用令牌之前,需要验证令牌是可信任的。
(比如:微服务,一个服务器只负责造令牌,其它负责用,使用之前,需要保证令牌是自己人发出来的。)
import io.jsonwebtoken.Claims; import io.jsonwebtoken.JwtBuilder; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import java.security.*; import java.util.Date; /** * @author Mr.css * @date 2022-01-05 15:03 */ public class JwtRS256 { /** * 公钥/私钥一般交由数据库管理 */ private static PublicKey publicKey; private static PrivateKey privateKey; // /** // * 获取密钥对 // * // * @param initialize 长度,1024 // * @return 密钥对 // */ // public static KeyPair getKeyPair(int initialize) throws GeneralSecurityException { // KeyPairGenerator keyPairGen; // keyPairGen = KeyPairGenerator.getInstance("RSA"); // keyPairGen.initialize(initialize); // return keyPairGen.generateKeyPair(); // } static { try { KeyPair keyPair = RSA.getKeyPair(); publicKey = keyPair.getPublic(); privateKey = keyPair.getPrivate(); } catch (GeneralSecurityException e) { e.printStackTrace(); } } /** * 生成jwt * * @param id ID * @param subject 主题 * @param ttlMillis 超时时间 * @return jwt */ public static String encrypt(String id, String subject, long ttlMillis) { Date now = new Date(); JwtBuilder builder = Jwts.builder(); //ID builder.setId(id); //主题 builder.setSubject(subject); // 签发者 builder.setIssuer("system"); // 签发时间 builder.setIssuedAt(now); // 签名算法以及密匙 builder.signWith(SignatureAlgorithm.RS256, privateKey); // 设置超时时间 if (ttlMillis >= 0) { long expMillis = now.getTime() + ttlMillis; Date expDate = new Date(expMillis); builder.setExpiration(expDate); } return builder.compact(); } /** * 解析JWT字符串 * * @param jwt jwt * @return Claims */ public static Claims decrypt(String jwt) { return Jwts.parser().setSigningKey(publicKey).parseClaimsJws(jwt).getBody(); } public static void main(String[] args) throws InterruptedException { //加密 String jwt = encrypt("id", "sub", 3000); System.out.println(jwt); //解密 Claims claims = decrypt(jwt); System.out.println(claims); //暂停5秒再解密,超时报错 Thread.sleep(5000); claims = decrypt(jwt); System.out.println(claims); } }
公钥/私钥——字符串和对象的转换
/** * 获取公钥字符串 */ public static String getPublicKey(KeyPair keyPair) { return Base64.encodeString(keyPair.getPublic().getEncoded()); } /** * 获取私钥字符串 */ public static String getPrivateKey(KeyPair keyPair) { return Base64.encodeString(keyPair.getPrivate().getEncoded()); } /** * 获取公钥对象 * * @param publicKey 公钥字符串 * @return 公钥 * @throws GeneralSecurityException - */ public static PublicKey generatePublicKey(String publicKey) throws GeneralSecurityException { byte[] keyBytes = Base64.decodeString(publicKey); X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(keyBytes); KeyFactory factory = KeyFactory.getInstance("RSA"); return factory.generatePublic(x509EncodedKeySpec); } /** * 获取私钥对象 * * @param privateKey 私钥字符串 * @return 私钥 * @throws GeneralSecurityException - */ public static PrivateKey generatePrivateKey(String privateKey) throws GeneralSecurityException { byte[] keyBytes = Base64.decodeString(privateKey); PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(keyBytes); KeyFactory factory = KeyFactory.getInstance("RSA"); return factory.generatePrivate(pkcs8EncodedKeySpec); }