目录
一、简介
二、应用场景
三、组成结构
四、特点
五、代码实现【java】
一、简介
JSON Web Token(JWT)是为了在网络应用环境间传递声明的一种基于JSON的开放标准(RCF 7519),它定义了一种紧凑(Compact)且自包含(Self-contained)的方式,用于在各方之间以JSON安全地传输信息。由于这些信息是经过数字前面的,因此可以被验证和信任。可以使用密钥(使用HMAC算法)或使用RSA的公钥/私钥对对JWT进行签名。JWT的声明一般被用来传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加额外的其他业务处理所必须的声明信息,该token可直接被用于认证,也可被加密。是目前最流行的跨域认证解决方案。
- compact(紧凑):由于它们内容较小,JWT可以通过URL,POST参数或HTTP的头信息内发送,另外,内容越小意味着传输速度越快。
- Self-contained(自包含):有效载荷(Playload)包含有关用户的所有必需信息,避免了多次查询数据库。
二、应用场景
Authentication(授权) :JWT最常见的作用就是授权。一旦用户登录,每个后续请求将包含JWT,从而允许用户访问该token允许的路由,服务和资源。
Information Exchange(信息交换):JWT是在各方之间安全地传输信息的方案。因为可以对JWT进行签名(例如,使用公私密钥对),所以您可以确认发件人是否是真正的发件人,另外,由于会对头部(header)和载荷(payload)进行数字签名,因此您还可以验证内容是否未被篡改。
三、组成结构
JWT由以下三个部分组成,中间用( . )分隔
- Header(头部)
- Payload(载荷)
- Signature(签名)
写成一行,就是下面的样子
Header.Payload.Signature
1、Header
Header部分是一个JSON对象,描述JWT的元数据,通常是下面这个样子的
{
"alg": "HS256",
"typ": "JWT"
}
上面代码中 alg属性表示签名的算法,默认是HMACSHA256(写成HS256);
typ属性表示这个令牌(token)的type,JWT令牌统一写JWT
最后,将上面的JSON对象使用Base64URL算法转成字符串
2、Payload
Payload部分也是一个JSON对象,用来存放实际需要传递的数据。也需要使用Base64URL编码
JWT规定了7个官方字段,供选用
- iss(issuer):jwt签发者
- sub(subject):jwt所面向的用户
- aud(audience):接收jwt的一方,受众
- exp(expiration time):jwt的过期时间,这个过期时间必须大于签发时间
- nbf(Not Before):生效时间,定义在什么时间之前
- iat(Issued At):jwt的签发时间
- jti(JWT ID):jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击
除了官方字段,你还可以在这个部分自定义字段,下面就是一个例子
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
注意:JWT中的payload是不加密的,只是Base64URL编码一下,任何人拿到都可以进行解码,所以不要把敏感信息放在这个部分
3、Signature
Signature 部分是对前两部分的签名,防止数据篡改。
首先,需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。然后,使用 Header 里面指定的签名算法(默认是 HMAC SHA256),按照下面的公式产生签名。
var header = Base64URL({ "alg": "HS256", "typ": "JWT"}); var payload = Base64URL( {"sub": "1234567890", "name": "John Doe", "iat": 1516239022}); var secret = "私钥";
#签名 var signature = HMACSHA256(header + "." + payload, secret); var jwt = header + "." + payload + "." + signature;
四、特点
1、因为json的通用性,所以JWT是可以进行跨语言支持的,像JAVA,JavaScript,NodeJS,PHP等很多语言都可以使用。
2、它不需要在服务端保存会话信息,所以它易于应用的扩展
3、JWT默认是不加密,但也是可以加密的。生成原始Token以后,可以用密钥再加密一次
4、JWT不加密的情况下,不能将秘密写入JWT
5、JWT不仅可以用于认证,也可以用于交换信息。有效使用JWT,可以降低服务器查询数据库的次数
6、JWT的最大缺点是,由于服务器不保存session状态,因此无法在使用过程中废止某个token,或者更改token的权限。也就是说,一旦JWT签发了,在到期之前就会始终有效。除非服务器部署额外的逻辑
7、JWT本身包含了认证信息,一旦泄露,任何人都可以获得该令牌(token)的所有权限。为了减少盗用,JWT的有效期应该设置得比较短。对于一些比较重要的权限,使用时应该再次对用户进行认证。
8、为了减少盗用,JWT不应该使用HTTP协议明码传输,要使用HTTPS协议传输
五、代码实现【java】
引入maven依赖
<dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency>
生成token ,解析token
package com.harara.jjwt.emp; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import io.jsonwebtoken.*; import org.springframework.util.StringUtils; import javax.xml.bind.DatatypeConverter; import java.util.Date; import java.util.Random; /** * @author : harara * @version : 2.0 * @date : 2020/6/16 17:15 */ public class TokenUtil { //默认密钥 public static final String DEFAULT_APPKEY = "harara"; //默认token有效期30分钟 public static final long DEFAULT_TOKEN_PERIOD = 30 * 60 *1000; //默认token刷新间隔 public static final long DEFAULT_TOKEN_INTERVAL= 5 * 60 *1000; //token 不合法 public static final String MSG_TOKEN_ERROR = "token 不合法"; //token 已过期 public static final String MSG_TOKEN_EXPIRED = "token 已过期"; //token 不合法 public static final String MSG_UNKNOWN_ERROR = "未知错误"; /** * 登录和token快过期时调用 * 生成token * @param id 令牌ID * @param subject 用户ID * @param user 登录用户信息 * @param period 有效时长,单位毫秒 * @param appKey 加密key * @param algorithm 加密算法 * @return */ public static String genToken(String id, String subject, User user, Long period, String appKey, SignatureAlgorithm algorithm){ //读取需要的密钥 if(StringUtils.isEmpty(appKey)){ appKey = DEFAULT_APPKEY; } //将密钥转换成字节形式 byte[] secreKeyBytes = DatatypeConverter.parseBase64Binary(appKey); //计算过期时间 Date nowDate = new Date(); Date endDate =new Date(nowDate.getTime()+period); JwtBuilder jwtBuilder = Jwts.builder(); jwtBuilder.setId(id) .setSubject(subject) .setIssuedAt(nowDate) .setExpiration(endDate); String userStr = JSONArray.toJSONString(user); //其他自定义信息 jwtBuilder.claim("user",userStr) .claim("period",period+""); //加签 jwtBuilder.signWith(algorithm,secreKeyBytes); return jwtBuilder.compact(); } public static Token parseToken(String token,String appkey){ //密钥 if(StringUtils.isEmpty(appkey)){ appkey = DEFAULT_APPKEY; } byte[] secretKeyBytes = DatatypeConverter.parseBase64Binary(appkey); Claims claims; try{ //检查jwt token是否合法 claims = Jwts.parser().setSigningKey(secretKeyBytes).parseClaimsJws(token).getBody(); }catch (ExpiredJwtException e){ throw new RuntimeException(MSG_TOKEN_EXPIRED); }catch (UnsupportedJwtException | MalformedJwtException | SignatureException | IllegalArgumentException e){ throw new RuntimeException(MSG_TOKEN_ERROR); }catch (Exception e){ throw new RuntimeException(MSG_UNKNOWN_ERROR); } //Token对象,保存token字符串中的一些信息,便于其他业务处理时从该对象中获取登录用户等信息 Token jwtToken = new Token(); //设置登录user信息到token对象中 User user= JSONObject.parseObject(claims.get("user", String.class), User.class); jwtToken.setUser(user); //检查是否需要刷新token //获取签发时间 long issueAttime = claims.getIssuedAt().getTime(); //获取当前时间 long nowTime = System.currentTimeMillis(); //获取有效时长 String periodTime = claims.get("period",String.class); long period; if(StringUtils.isEmpty(periodTime)){ period = DEFAULT_TOKEN_PERIOD; }else{ period = Long.valueOf(periodTime); } //如果已过期,获取已过去一半有效时长,则重新生成token if(nowTime > issueAttime + period || nowTime > issueAttime + DEFAULT_TOKEN_INTERVAL ){ String token_id = getRandomString(20); String tokenNew = genToken(token_id,user.getUserId()+"",user,period,appkey,SignatureAlgorithm.HS256); //设置新的token字符串 jwtToken.setToken(tokenNew); }else{ //设置原来token字符串 jwtToken.setToken(token); } //设置用户名 jwtToken.setUserId(claims.getSubject()); return jwtToken; } /** * 获取指定位数的随机数 * @param length * @return */ public static String getRandomString(int length) { String base = "abcdefghijklmnopqrstuvwxyz0123456789"; Random random = new Random(); StringBuilder sb = new StringBuilder(); for (int i = 0; i < length; i++) { int number = random.nextInt(base.length()); sb.append(base.charAt(number)); } return sb.toString(); } public static void main(String[] args) { //模拟登录的时候从数据库中拿到的登录用户信息,省略 User sysuser = new User(); sysuser.setUserId(1111L); String tokenId = getRandomString(20); String appkey = "1223232131"; //生成JWT token String jwttoken = genToken(tokenId,sysuser.getUserId()+"",sysuser,DEFAULT_TOKEN_PERIOD,appkey,SignatureAlgorithm.HS256); System.out.println(jwttoken); //解析token Token token = parseToken(jwttoken,appkey); System.out.println(token); } }
自定义Token类,保存token字符串中的一些信息,便于其他业务处理时从该对象中获取登录用户等信息
package com.chenly.jjwt.emp; import lombok.Data; @Data public class Token { private String token; //请求时携带的token private String userId; //用户ID private User user;//登录信息 }
用户类
package com.harara.jjwt.emp; import lombok.Data; /** * <p> * 操作员表 * </p> * * @author harara * @since 2020-06-16 */ @Data public class User { // private static final long serialVersionUID = 1L; private Long userId; private String userName; private Integer sex;
....
}
参考地址
jwt官方介绍 : https://jwt.io/introduction/
JSON Web Token 入门教程http://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html
Spring Boot集成JSON Web Token(JWT): https://www.cnblogs.com/cndarren/p/11518443.html