网上关于jwt创建token的资料很多,有讲原理的,也有深度解析的,也存在很多的示例,但大部份示例,把简单的方式搞的复杂化,因此稍做整理提供一个可用的工具类;
JSON Web Token(缩写 JWT)是目前最流行的跨域认证解决方案。 JWT 的三个部分依次如下 Header(头部):是一个 JSON 对象,描述 JWT 的元数据{ "alg": "HS256", typ": "JWT" } Payload(负载):也是一个 JSON 对象,用来存放实际需要传递的数据,JWT 规定了7个官方字段: iss (issuer):签发人 exp (expiration time):过期时间 sub (subject):主题 aud (audience):受众 nbf (Not Before):生效时间 iat (Issued At):签发时间 jti (JWT ID):编号 Signature(签名):对前两部分的签名,防止数据篡改 1.JWT中Header头和Payload有效载荷序列化的算法都用到了Base64URL,签名哈希部分是对Header与Payload两部分数据签名 2.客户端接收服务器返回的JWT,将其存储在Cookie或localStorage中,客户端将在与服务器交互中都会带JWT,将它放入HTTP请求的Header Authorization字段中 3.JWT的最大缺点是服务器不保存会话状态,所以在使用期间不可能取消令牌或更改令牌的权限 4.JWT本身包含认证信息,因此一旦信息泄露,任何人都可以获得令牌的所有权限 5.JWT不建议使用HTTP协议来传输代码,而是使用加密的HTTPS协议进行传输
pom.xml引入依赖包
<!-- JWT Token验证机制 --> <dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.8.1</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.0</version> </dependency>
工具类
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.Claim;
import org.apache.commons.lang3.time.DateUtils;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* @Description JWT跨域管理token工具类
* @Author JL
* @Date 2019/08/08
* @Version V1.0
*/
public class JwtTokenUtils {
/**
* 加密密钥
*/
private static final String SECRET = "wNmx01w27MQnPc3BtUQkty_23P0pVlAdj86o5XznUrE";
/**
* jwt创建token,考虑安全性,token中不因该放入太多信息(勿放密码之类的敏感信息),只放入关键字段值即可,如用户ID
* @param sub 主题(可以放入关键数据,如:userid, 用户唯一值等)
* @param timeout 过期时长(秒)
* @return
*/
public static String createToken(String sub, int timeout) {
JWTCreator.Builder builder = JWT.create();
builder.withSubject(sub);//主题
builder.withIssuer("pro-server");
builder.withExpiresAt(DateUtils.addSeconds(new Date(), timeout));//过期时间,30秒后过期
String jwtToken = builder.sign(Algorithm.HMAC256(SECRET));
return jwtToken;
}
/**
* 对jwt创建的token进行验签与解析,返回Subject(主题)中存放的内容
* @param token
* @return
* @throws TokenExpiredException 会话超时异常
* @throws SignatureVerificationException 验签无效异常
*/
public static String parseToken(String token) throws TokenExpiredException, SignatureVerificationException {
return JWT.require(Algorithm.HMAC256(SECRET)).build().verify(token).getSubject();
}
/**
* jwt创建token,考虑安全性,token中不因该放入太多信息(勿放密码之类的敏感信息)
* @param loadMap 数据集合
* @param timeout 过期时长(秒)
* @return
*/
public static String createToken(Map<String, Object> loadMap, int timeout) {
JWTCreator.Builder builder = JWT.create();
loadMap.forEach((k, v) -> {
if (v instanceof String) {
builder.withClaim(k, (String) v);
} else if (v instanceof Date) {
builder.withClaim(k, (Date) v);
} else if (v instanceof Long) {
builder.withClaim(k, (Long) v);
} else if (v instanceof Integer) {
builder.withClaim(k, (Integer) v);
} else if (v instanceof Boolean) {
builder.withClaim(k, (Boolean) v);
}
});
builder.withIssuer("pro-server");
builder.withExpiresAt(DateUtils.addSeconds(new Date(), timeout));//过期时间,30秒后过期
String jwtToken = builder.sign(Algorithm.HMAC256(SECRET));
return jwtToken;
}
/**
* 对jwt创建的token进行验签与解析,返回集合
* @param token
* @return
* @throws TokenExpiredException 会话超时异常
* @throws SignatureVerificationException 验签无效异常
*/
public static Map<String, Object> parseTokenToMap(String token) throws TokenExpiredException, SignatureVerificationException {
Map<String, Claim> claimMap = JWT.require(Algorithm.HMAC256(SECRET)).build().verify(token).getClaims();
if (claimMap == null){
return null;
}
Map<String, Object> loadMap = new HashMap<>();
claimMap.forEach((k, v) -> {
Object obj = null;
if (v.asString() != null) {
obj = v.asString();
} else if (v.asBoolean() != null) {
obj = v.asBoolean();
} else if (v.asDate() != null || v.asLong() != null) {//Date类型按Long方式来处理
obj = v.asLong();
} else if (v.asInt() != null) {
obj = v.asInt();
}
loadMap.put(k, obj);
});
return loadMap;
}
//测试方法
//登录成功后,将用户的id放入到JwtTokenUtils.createToken(userid, 60 * 30);
//每次请求在拦截器或过滤器中,获取请求中的token调用JwtTokenUtils.parseToken(jwtToken)验证是否有效,或从token中获取userid进行业务逻辑操作
public static void main(String[] args) {
//创建token和解析token
String subject = "userid_001";
System.out.println("新建subject = " + subject);
String jwtToken = JwtTokenUtils.createToken(subject, 60);
System.out.println("生成token = " + jwtToken);
try {
subject = JwtTokenUtils.parseToken(jwtToken);
System.out.println("解析subject = " + subject);
} catch(TokenExpiredException tee){
throw new TokenExpiredException("token已过有效期,请重新申请:" + tee.getMessage());
} catch(SignatureVerificationException sve){
//验证签名不通过(数据被篡改过)
throw sve;
}
//过期测试
// String jwtToken = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c2VyaWRfMDAxIiwiaXNzIjoicHJvLXNlcnZlciIsImV4cCI6MTU2NTI0OTMxMH0.0cSnHLHTqDx-FXoL08yk6AtIwobiWcMNRofyE4dunGY";
// subject = JwtTokenUtils.parseToken(jwtToken);
//无效验鉴(将最后一位更改为z)
// String jwtToken = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c2VyaWRfMDAxIiwiaXNzIjoicHJvLXNlcnZlciIsImV4cCI6MTU2NTI0OTMxMH0.0cSnHLHTqDx-FXoL08yk6AtIwobiWcMNRofyE4dunGz";
// subject = JwtTokenUtils.parseToken(jwtToken);
//将多种数据放入集合中,通过jwt创建token
/*
Map<String, Object> loadMap = new HashMap<>();
loadMap.put("userId", (Long) 1000000L);
loadMap.put("userName", "test");
loadMap.put("isLogin", true);
String jwtToken = JwtTokenUtils.createToken(loadMap, 60);
System.out.println(jwtToken);
Map<String, Object> loadMap2 = JwtTokenUtils.parseTokenToMap(jwtToken);
for (Map.Entry<String, Object> entry : loadMap2.entrySet()) {
System.out.println(entry.getKey() + "=" + entry.getValue());
}
*/
}
}
请求中集成使用
1.登录成功后,将用户的id放入到JwtTokenUtils.createToken(userid, 60 * 30);
2.对需要鉴权的每次URL请求在拦截器或过滤器中处理,获取请求中的token(一般放在请求头部,每次请求必带)调用JwtTokenUtils.parseToken(jwtToken)验证是否有效,或从token中获取userid进行业务逻辑操作
说明:
做过项目的人都知道,很多写过的可重复利用的代码块或有用的工具类没有怎么整理,当需要的时候,又得打开项目查找一翻,虽然功能开发不难,但是又得花时间成本去写去测试,这样的重复造轮子的事情太多次了;因此不如把轮子保留,供大家一起使用;
1.这个轮子可以有:需要使用的时候确实还不存在这个组件。
2.我需要的时候轮子不在:每一种技术或工具产生都有它的项目背景,当代码写在项目里的时候,我知道有这个东西,当换了一个项目或公司后,没有备份也没有记录,这个时候你不在了,又得花时间手打一遍;
3.我不知道是不是造轮子:大多数情况下初学者很难分清楚自己是不是在重复造轮子,事实上造轮子不是我目的。我的目的是完成工作任务,任务完成的速度越快越好,质量越高越好。而不是去判断自己在不在造轮子。
4.不想重复花时间造轮子:有时候还会碰到一些并不困难但是很占时间的东西,当然有现成的轮子是花时间最少的;
5.我就是想学习轮子:初学者的并不是在重复造轮子,而是学习后以提高为自己的知识与技能。
轮子有过测试,但难免有失误,如有错误处,还敬请指出;