JWT介绍以及在spring boot中使用总结

1 JWT 介绍

1.1 JWT组成

JWT 是JSON Web Token的缩写。 是由3部分组成的,他们之间使用英文句号  .  分割。

大概长得下面这样子:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1ODkzODE5Mzg4MTAsInBheWxvYWQiOiJcImFiY1wiIn0.EZNHd_YcnXqhEd0Ol-ilPaY_c2c8115DRGJJIUCDrAA

3部分分别是:

  • header(头部)
  • payload(负载)
  • signature(签名)

所以JWT中具体的表现形式就是:Base64url(header) +  '.' + Base64url(payloay) +  '.' + signature

1.1.1 Header

Header 是一个JSON格式的字符串,标准定义中包括2个部分:

  • agl: 消息认证的算法,也就是第三部分signature使用的算法。通常使用的是HS256( HMAC with SHA-256 对称加密算法)或者RS256(RSA signature with SHA-256 非对称加密算法)。
  • typ: 令牌的类型,既然是JWT,那这部分基本都是JTW了

所以JWT Header长得如下:

{
 "alg" : "HS256",
 "typ" : "JWT"
}

1.1.2 Payload

Payload是一个JSON格式的字符串,标准定义中包括7个部分:

codenamedescription
issIssuer发布JWT的主体,也就是签发者,发布者
subSubjectJWT的主题
audAudienceJWT要发送给的收件人
expExpiration Time过期时间戳
nbfNot BeforeJWT 生效时间戳
iatIssued at签发时间戳
jtiJWT IDJWT ID编号

官方标准中的字段不强求一定使用,甚至可以不定义其中任何字段,除了标准定义中的7个字段,当然可以自定义字段。例如添加用户的一些信息"clientId":"xxxx"

所以Payload长得如下:

{
  "iss":"abc.com"
  "sub": "xxxx",
  "clientName": "Allen",
  "clientId": "11233"
}

需要注意的是:Header 和payload都仅仅是Base64url编码,而不是加密,所以基本是不允许放置敏感信息在其中的。因为编码是随意都可以解码成为明文的。

1.1.3 Signature

签名是通过使用Base64编码对Header和Payload进行编码,并使用句点分隔符将两者连接在一起来计算的。然后,该字符串通过Header中指定的加密算法运行。这里说到加密,就肯定涉及到秘钥。所以signature包含3部分的信息

  • Base64url(header)
  • Base64url(payload)
  • secret

使用在Header中指定的算法,用secret对base64(header) +  '.' + base64(payloay),进行加密操作。 这个过程可以理解为签名。

signature经常使用的方式长得如下:HS256 接收两个参数(encryp string, secret)

HMAC-SHA256(
 base64urlEncoding(header) + '.' + base64urlEncoding(payload),
 secret
)

算出签名之后,把三部分使用句点分隔符连接起来就可以返回给用户了。长得就和开篇展示的那样。

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJsb2dnZWRJbkFzIjoiYWRtaW4iLCJpYXQiOjE0MjI3Nzk2Mzh9.gzSraSYS8EXBxLN_oWnFSRgCzcmJmMjLiuyu5CSpyHI

1.2 JWT在微服务(springboot)的运用

在微服务中,我们会使用JWT来作为权限校验的一个工具。通常会在HTTP HEADER 中设置一个Authorization 的字段信息,通常对于springboot 项目,长得如下:

Authorization: Bearer <JWT string>

具体流程大概如下:

  1. 首次登录APP或者首页时,访问后端getAuth 接口,带上用户相应信息(假设已经是注册的用户)
  2. 后端系统返回JWT,对于微服务来说,这些工作一般都在网关服务中进行操作。
  3. 每次请求任何API都会在HTTP HEADER的Authorization字段带上这个JWT
  4. 后端系统获取到JWT后,先做签名校验,使用指定的算法,用secret对base64urlEncoding(header) + '.' + base64urlEncoding(payload),签名如果相等,就表示JWT Header 和Payload没有被篡改过;然后Payload如果定义了exp,那么就要校验exp是否过期,如果过期,API 请求是吧;否则,请求成功。如果没有定义exp 字段,则可以正常访问API逻辑,可以获取API数据
  5. 一般作为API校验的都不会使用exp 来作为JWT 是否过期的标识,这样可以避免正常的访问需要频繁更新JWT。一般会试用一些辅助的方式来校验请求的合理性和合法性。例如在Payload中定义clientId,然后使用缓存工具(redis,memcache)来保存clientId的相关信息,如果在缓存中正常获取到的clientId对应的信息,则是用户的合法请求,这时候可以对缓存的信息进行续期;否则则说明用户信息已经失效,需要重新登录操作才能对API 进行请求操作。

简单的请求流程如下:

附上JWT 使用的主要方法:


import com.alibaba.fastjson.JSON;
import com.auth0.jwt.JWTSigner;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.JWTVerifyException;

import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SignatureException;
import java.util.HashMap;
import java.util.Map;


public class JWT {

	private static final String SECRET = "123)sdfose";

	private static final String EXP = "exp";

	private static final String PAYLOAD = "payload";

	/**
	 * 将对象签名为JWT的token字符串,(指定有效时间)
	 * 
	 * @param payloadObj
	 * @param maxAge
	 * @return the jwt token
	 */
	public static <T> String sign(T payloadObj, long maxAge) {
		try {
			assert payloadObj != null;
			final JWTSigner signer = new JWTSigner(SECRET);
			final Map<String, Object> claims = new HashMap<>(2);
			claims.put(EXP, System.currentTimeMillis() + maxAge);
			claims.put(PAYLOAD, JSON.toJSONString(payloadObj));
			return signer.sign(claims);
		} catch (Exception e) {
			return null;
		}
	}

	/**
	 * valid the JWT
	 *
	 * @param jwtToken
	 * @return
	 */
	public static boolean validate(String jwtToken){
		final JWTVerifier verifier = new JWTVerifier(SECRET);
		Map<String, Object> claims;
		try {
			claims = verifier.verify(jwtToken);
			if(claims == null || claims.isEmpty()){
				return false;
			}
			if (claims.containsKey(EXP) && claims.containsKey(PAYLOAD)){
				long exp = (Long) claims.get(EXP);
				long currentTimeMillis = System.currentTimeMillis();
				if (exp > currentTimeMillis) {
					//有效!
					return true;
				}else{
					//过期了!
					return false;
				}
			}
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		} catch (IllegalStateException e) {
			e.printStackTrace();
		} catch (SignatureException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (JWTVerifyException e) {
			e.printStackTrace();
		} catch (InvalidKeyException e) {
			e.printStackTrace();
		}
		return false;
	}
	
	/**
	 * 从jwt token签名中获取加密前的对象。<br>
	 * 注意,如果过了有效期或者解密密钥有错误,本方法会吃掉异常,返回值为null~!
	 * 
	 * @param jwt
	 *            token
	 * @return POJO object 解密后的对象或者null
	 */
	public static <T> T unsign(String jwt, Class<T> clazz) {
		final JWTVerifier verifier = new JWTVerifier(SECRET);
		try {
			final Map<String, Object> claims = verifier.verify(jwt);
			if (claims.containsKey(EXP) && claims.containsKey(PAYLOAD)) {
				long exp = (Long) claims.get(EXP);
				long currentTimeMillis = System.currentTimeMillis();
				if (exp > currentTimeMillis) {
					return JSON.parseObject(claims.get(PAYLOAD).toString(), clazz);
				}
			}
			return null;
		} catch (Exception e) {
			return null;
		}
	}


	/**
	 * test
	 * @param args
	 */
	public static void main(String[] args) {
		String abc = sign("abc", 10000);
		System.out.println(abc);
		String unsign = unsign(abc, String.class);
		System.out.println(unsign);
	}
}

参考资料:

维基百科:https://en.wikipedia.org/wiki/JSON_Web_Token

JSON Web Token 入门教程:https://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值