JWT(JSON Web Token)
JWT在web环境中通常作为一种用户凭证,它是一个以.分隔,由heaer.payload.signature组成的字符串,其中每个部分都经过Base64编码。header包含token采用的算法、类型(JWT)、KeyId等信息,payload中包含token的发行人iss、过期时间exp、业务信息等(比如用户ID、昵称等),signature是对(header.payload)的Base64组合串用密钥加密得到的摘要,代表header.payload的签名结果。
JWT生成/验证流程
生成:对header、payload分别进行Base64编码,得到两个base64串,以.拼接这两个串,将该串通过MD5/SHA/MAC等算法生成签名信息得到signature。再以.拼接header.payload.signature,得到token
验证:以.为分隔符拆分token串,得到三个子部分,将header.payload采用相同的算法生成新的签名,对比签名来判断token是否被修改、如果有设置过期时间,则进行过期检测(最终通常会从payload中提取信息存储到一个ThreadLocal对象中)。 (注意由于这些摘要算法是不可逆的,所以signature是不能解密的)
JWT代码实现
实际开发中你可以自己实现,也可以采用第三方库,但通常都会采用Mac算法(Message Authentication Codes消息认证码算法), Mac兼容了MD5及SHA的特性,并且添加了密钥(这里也可称为盐值),能更好的降低碰撞机率,防止暴力破解。
这里两份代码都贴一下,自己写的话,可以参考下面这份比较粗糙的代码,主要是为了说明流程。第三方库的底层实现流程也类似,通常也是采用javax.crypto.mac算法
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Map;
import java.util.Objects;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;
import com.auth0.jwt.exceptions.SignatureGenerationException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class JWTDemo {
// 密钥(或者叫盐值)
private String saltSecret = "saltSecret";
// 算法
private String algorithm = "HmacSHA256";
public String sign(Map<String,Object> headerMap,Map<String,Object> payloadMap) throws SignatureGenerationException, JsonProcessingException, InvalidKeyException, NoSuchAlgorithmException {
// 0、得到header.payload的Base64串
ObjectMapper objMapper = new ObjectMapper();
String headerJson = objMapper.writeValueAsString(headerMap);
String payloadJson = objMapper.writeValueAsString(payloadMap);
String headerB64 = Base64.encodeBase64URLSafeString(headerJson.getBytes(StandardCharsets.UTF_8));
String payloadB64 = Base64.encodeBase64URLSafeString(payloadJson.getBytes(StandardCharsets.UTF_8));
String content = String.format("%s.%s", headerB64, payloadB64);
// 1、生成摘要信息
byte[] signatureBytes = createSignature(algorithm,saltSecret.getBytes(StandardCharsets.UTF_8),content.getBytes(StandardCharsets.UTF_8));
String signature = Base64.encodeBase64URLSafeString((signatureBytes));
return String.format("%s.%s", content, signature);
}
private byte[] createSignature(String algorithm, byte[] secretBytes, byte[] contentBytes) throws NoSuchAlgorithmException, InvalidKeyException {
final Mac mac = Mac.getInstance(algorithm);
mac.init(new SecretKeySpec(secretBytes, algorithm));
return mac.doFinal(contentBytes);
}
@SuppressWarnings("unchecked")
public void verifySignature(String token) throws InvalidKeyException, NoSuchAlgorithmException, JsonMappingException, JsonProcessingException {
// 0、解析token,得到header.payload的Base64串
String[] parts = token.split("\\.");
String headerB64 = parts[0];
String payloadB64 = parts[1];
String signatureB64 = parts[2];
byte[] contentBytes = String.format("%s.%s", headerB64, payloadB64).getBytes(StandardCharsets.UTF_8);
// 1、生成摘要,进行摘要对比
byte[] signatureBytes = Base64.decodeBase64(signatureB64);
boolean validResult = this.verifySignature(algorithm, saltSecret.getBytes(StandardCharsets.UTF_8), contentBytes, signatureBytes);
if(!validResult) {
// ...验签失败
}
// 2、过期判断
ObjectMapper objMapper = new ObjectMapper();
String payloadJson = new String(Base64.decodeBase64(payloadB64),StandardCharsets.UTF_8);
Map<String,Object> payloadMap = objMapper.readValue(payloadJson, Map.class);
if(Objects.nonNull(payloadMap.get("exp"))){
long endTime = Long.parseLong(String.valueOf(payloadMap.get("exp")));
long currSecond = System.currentTimeMillis()/1000;
if(currSecond > endTime) {
// ...超时异常
}
}
}
private boolean verifySignature(String algorithm, byte[] secretBytes, byte[] contentBytes, byte[] signatureBytes) throws NoSuchAlgorithmException, InvalidKeyException {
// 生成新的签名,对比签名
return MessageDigest.isEqual(createSignature(algorithm, secretBytes, contentBytes), signatureBytes);
}
}
关于第三方库可以使用,可以使用Java中比较推荐的JJWT(java-jwt)
//gradle引入
implementation 'com.auth0:java-jwt:3.4.0'
// maven引入
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.0</version>
</dependency>
public class JWTMaker {
@Value("${xxxxx}")
private String tokenSalt;
private final String ISSUER_NAME = "xxxx";
private final Integer JWT_EXPIRE_TIME = 8;
public String sign(UserInfo user) {
String token = null;
try {
Date expireAt = Date.from(LocalDateTime.now().plusHours(JWT_EXPIRE_TIME).atZone(ZoneId.systemDefault()).toInstant());
token = JWT.create()
.withIssuer(ISSUER_NAME)
.withClaim("userId", user.getUserId())
.withClaim("name", user.getName())
.withExpiresAt(expireAt)
.sign(Algorithm.HMAC256(tokenSalt));
} catch (Exception e) {
log.error(e.getMessage(), e);
}
return token;
}
/**
* 签名验证
* @param token
* @return
*/
public boolean verify(String token) {
try {
// 0、验签
JWTVerifier verifier = JWT.require(Algorithm.HMAC256(tokenSalt)).withIssuer(ISSUER_NAME).build();
DecodedJWT jwt = verifier.verify(token);
// 1、提取信息
UserInfo user = new UserInfo();
user.setUserId(jwt.getClaim("userId").asString());
user.setName(jwt.getClaim("name").asString());
// 2、放入到ThreadLocal中,与当前线程绑定。
// ...
return true;
} catch (Exception e) {
log.error(e.getMessage(), e);
return false;
}
}
}