JWT(JSON WEB TOKEN)

1 什么是JWT

      JWT是基于json制作的一个web token的一套规范,这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息,它属于一种无状态的数据,它主要有两大使用场景:认证和数据传递。

2 JWT 的内容

        JWT的内容如下:

        eyJhbGciOiJIUzUxMiJ9.eyJwYXNzd29yZCI6IjQ1NiIsImV4cCI6MTY2MDUyOTI0MCwiaWF0IjoxNjYwNTI3NDQwLCJ1c2VybmFtZSI6InpoYW5nc2FuIn0.LbtbH4DRn7W8PAzre1Cflpfg3W9grzPQIs7Z_86q5J9Gm8YiRxvBjZ7gPmils6LBzvmFKmvcgywwHELLPlCQKQ

        由上面的JWT的内容我们根据“.”这个字符把内容隔开看,内容一共分为三段,如下
       eyJhbGciOiJIUzUxMiJ9
       eyJwYXNzd29yZCI6IjQ1NiIsImV4cCI6MTY2MDUyOTI0MCwiaWF0IjoxNjYwNTI3NDQwLCJ1c2VybmFtZSI6InpoYW5nc2FuIn0
       LbtbH4DRn7W8PAzre1Cflpfg3W9grzPQIs7Z_86q5J9Gm8YiRxvBjZ7gPmils6LBzvmFKmvcgywwHELLPlCQKQ
         这三段内容也就是jwt的主要组成部分,看到这三段内容,我们肯定会不由自主的想到,这三段内容到底是什么呢?有什么作用呢?安全起见为什么一定是三段呢?

      2.1 第一段名为Header

        header里面存放的是一个内容包含了算法名称已经类型的json对象,然后以base64处理之后的数据。

      2.2 第二段名为Payload 

        payLoad 里面存放的是user的数据的json对象,然后以base64处理之后的数据。

      2.3 第三段名为 Signature

        signature 里面存放的是前面两个的数据组合起来,然后加上一个加密盐通过第一段设置的加密方式加密起来,然后以base64处理的数据。 

3  JWT的demo

        在 pom.xml导入依赖 如下
                

        JWT的生成token以及token的解析代码如下:

package com.zw.jwtdemo.util;

import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

public class JwtUtil {
	
	private String keyScret ="123456";
	private int expiryTime = 30;
	
	public String getToken(Map claims) {
		if(claims == null || claims.size() == 0) {
			return null;
		}
		Date createTime = new Date();
		Calendar calander = Calendar.getInstance();
		calander.setTime(createTime);
		calander.add(Calendar.MINUTE, expiryTime);
		
		return Jwts.builder()
				//payload 
				//设置携带的内容即数据信息。
				.setClaims(claims)
				//设置过期时间
				.setExpiration(calander.getTime())
				//设置创建时间
				.setIssuedAt(createTime)
				//设置加密盐和签名算法
				.signWith(SignatureAlgorithm.HS512, keyScret)
				//数据压缩得到一个字符串
				.compact();
	}
	
	public Map parseToken(String token) {
		
		Claims claims = Jwts.parser()
				//设置加密盐
				.setSigningKey(keyScret)
				//通过压缩的jwt 
				//字符串解析出数据信息
				.parseClaimsJws(token)
				//返回数据信息;
				//返回的内容是一个hashMap
				.getBody();
		return claims;
	}
	
	public static void main(String[] args) {
		
		Map map = new HashMap(); JwtUtil jwtUtil = new
		JwtUtil(); map.put("username", "zhangsan"); map.put("password", "456");
		 
		String token =jwtUtil.getToken(map); System.out.println(token);
		Map map2 = jwtUtil.parseToken(token); Set keys =
		map2.keySet(); for(String key: keys) { System.out.println(key + "=" +
		String.valueOf(map2.get(key))); }
		
		
	}

}



//输出结果
eyJhbGciOiJIUzUxMiJ9.eyJwYXNzd29yZCI6IjQ1NiIsImV4cCI6MTY2MTE1MjkyOCwiaWF0IjoxNjYxMTUxMTI4LCJ1c2VybmFtZSI6InpoYW5nc2FuIn0.BC-M5R9kThhW1H7DF0c5OLczQyV6xSOc6Sj3kS2bU67dpjGUmCcMOyFF9H2x0mK7z6J4zqzsB0Z0uJ48CGtDqw
password=456
exp=1661152928
iat=1661151128
username=zhangsan

      上面的代码段便是JWT的代码段了,代码比较简单生成的JWT也是通过“.”隔开的三段字符串拼接而成。接下来我没还是带着我之前提出的三个问题,分析下源码。

4 JWT的源码分析

        4.1 JWT加密的源码 

                JWT加密的源码主要就是compact方法,我们进入到compact方法看看,这个方法里面到底有啥内容。

@Override
    public String compact() {
		//payload和claims二者必须要有一个
        if (payload == null && Collections.isEmpty(claims)) {
            throw new IllegalStateException("Either 'payload' or 'claims' must be specified.");
        }
        //payload和claims二者只能有一个
        if (payload != null && !Collections.isEmpty(claims)) {
            throw new IllegalStateException("Both 'payload' and 'claims' cannot both be specified. Choose either one.");
        }
        
        //加密盐的key和字符数组只能有一个,防止两个不同的key同时用来加密一个JWT
        if (key != null && keyBytes != null) {
            throw new IllegalStateException("A key object and key bytes cannot both be specified. Choose either one.");
        }
        
        //设置JWT header
        Header header = ensureHeader();
        //设置加密key
        Key key = this.key;
        
        if (key == null && !Objects.isEmpty(keyBytes)) {
            key = new SecretKeySpec(keyBytes, algorithm.getJcaName());
        }
        
        
        JwsHeader jwsHeader;

        if (header instanceof JwsHeader) {
            jwsHeader = (JwsHeader)header;
        } else {
            jwsHeader = new DefaultJwsHeader(header);
        }
        
        
        //设置加密算法 存放在头里面
        if (key != null) {
            jwsHeader.setAlgorithm(algorithm.getValue());
        } else {
            //no signature - plaintext JWT:
            jwsHeader.setAlgorithm(SignatureAlgorithm.NONE.getValue());
        }

        //设置JWT的压缩方式
        if (compressionCodec != null) {
            jwsHeader.setCompressionAlgorithm(compressionCodec.getAlgorithmName());
        }

        //把header里面的内容通过base64的方式压缩得到header的字符串
        String base64UrlEncodedHeader = base64UrlEncode(jwsHeader, "Unable to serialize header to json.");

        
        String base64UrlEncodedBody;

        //如果压缩方式不为空
        if (compressionCodec != null) {

            byte[] bytes;
            try {
            	//讲数据信息体转成json的字符串,然后再将字符串转成转成字符数组。
                bytes = this.payload != null ? payload.getBytes(Strings.UTF_8) : toJson(claims);
            } catch (JsonProcessingException e) {
                throw new IllegalArgumentException("Unable to serialize claims object to json.");
            }
            //将字符数组通过指定的压缩方式进行压缩 然后进行base64处理的带一个payload的字符串数据体
            base64UrlEncodedBody = TextCodec.BASE64URL.encode(compressionCodec.compress(bytes));

        } else {
        	//如果没有指定压缩方式,就会直接把数据信息体转成json字符串然后转成字符数据,直接通过base64处理得到一个payload的字符串
            base64UrlEncodedBody = this.payload != null ?
                    TextCodec.BASE64URL.encode(this.payload) :
                    base64UrlEncode(claims, "Unable to serialize claims object to json.");
        }

        //再将头字符串和payload字符串通过“.”拼接起来得到一个初步的JWT字符串
        String jwt = base64UrlEncodedHeader + JwtParser.SEPARATOR_CHAR + base64UrlEncodedBody;

        if (key != null) { //jwt must be signed:
        	
        	//通过设置的加密算法和加密盐设置数字签名
            JwtSigner signer = createSigner(algorithm, key);

            //通过加密签名把base64处理之后的头和base64处理之后的payload以及“.”组合起来的字符串进行加密。得到一个加密的字符串
            String base64UrlSignature = signer.sign(jwt);

            //把base64处理之后的头和base64处理之后的payload以及“.”组合起来的字符串再拼接上“.”以及数字签名之后得到的base64处理过的字符串。整个一起构成了一个字符串即是完成的JWT
            jwt += JwtParser.SEPARATOR_CHAR + base64UrlSignature;
        } else {
            // no signature (plaintext), but must terminate w/ a period, see
            // https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-25#section-6.1
        	//如果不需要后面的数字签名部分的话,就只是返回前面两端的内容并且后面拼上一个“.”
            jwt += JwtParser.SEPARATOR_CHAR;
        }

        return jwt;
    }

       4.2 JWT解析部分的源码  

                

@Override
    public Jwt parse(String jwt) throws ExpiredJwtException, MalformedJwtException, SignatureException {
		//判断传过来的JWT的数据不能为空
        Assert.hasText(jwt, "JWT String argument cannot be null or empty.");
        //JWT的Header的base64的字符串
        String base64UrlEncodedHeader = null;
        //JWT的payload的base64的字符串
        String base64UrlEncodedPayload = null;
        //JWT的数字签名的base64的字符串
        String base64UrlEncodedDigest = null;

        int delimiterCount = 0;

        StringBuilder sb = new StringBuilder(128);

        for (char c : jwt.toCharArray()) {
        	//以“.”作为分割线,来分割得到JWT的3个部署的字符串
            if (c == SEPARATOR_CHAR) {

                CharSequence tokenSeq = Strings.clean(sb);
                String token = tokenSeq!=null?tokenSeq.toString():null;

                if (delimiterCount == 0) {
                    base64UrlEncodedHeader = token;
                } else if (delimiterCount == 1) {
                    base64UrlEncodedPayload = token;
                }

                delimiterCount++;
                sb.setLength(0);
            } else {
                sb.append(c);
            }
        }

        //如果JWT的字符串里面没有两个“.”表示不是规范的JWT的内容,抛出异常
        if (delimiterCount != 2) {
            String msg = "JWT strings must contain exactly 2 period characters. Found: " + delimiterCount;
            throw new MalformedJwtException(msg);
        }
        //如果有第三段,设置数字签名部分的base64的字符串
        if (sb.length() > 0) {
            base64UrlEncodedDigest = sb.toString();
        }

        //如果payload的字符串为空则表示payload的数据被删除了,抛出异常
        if (base64UrlEncodedPayload == null) {
            throw new MalformedJwtException("JWT string '" + jwt + "' is missing a body/payload.");
        }

        // =============== Header =================
        Header header = null;
        //设置压缩方式,一般没有设置
        CompressionCodec compressionCodec = null;
        //解压得到JWT Header部分。
        if (base64UrlEncodedHeader != null) {
            String origValue = TextCodec.BASE64URL.decodeToString(base64UrlEncodedHeader);
            Map m = readValue(origValue);

            if (base64UrlEncodedDigest != null) {
                header = new DefaultJwsHeader(m);
            } else {
                header = new DefaultHeader(m);
            }

            compressionCodec = compressionCodecResolver.resolveCompressionCodec(header);
        }

        // =============== Body =================
        //获取到payload部分
        String payload;
        if (compressionCodec != null) {
            byte[] decompressed = compressionCodec.decompress(TextCodec.BASE64URL.decode(base64UrlEncodedPayload));
            payload = new String(decompressed, Strings.UTF_8);
        } else {
            payload = TextCodec.BASE64URL.decodeToString(base64UrlEncodedPayload);
        }

        Claims claims = null;
        //将payload由json字符串转成一个hashMap
        if (payload.charAt(0) == '{' && payload.charAt(payload.length() - 1) == '}') { //likely to be json, parse it:
            Map claimsMap = readValue(payload);
            claims = new DefaultClaims(claimsMap);
        }

        // =============== Signature =================
        //如果数字签名部位空
        if (base64UrlEncodedDigest != null) { //it is signed - validate the signature
        	
            JwsHeader jwsHeader = (JwsHeader) header;

            SignatureAlgorithm algorithm = null;

            if (header != null) {
                String alg = jwsHeader.getAlgorithm();
                if (Strings.hasText(alg)) {
                	//获取到签名算法
                    algorithm = SignatureAlgorithm.forName(alg);
                }
            }

            if (algorithm == null || algorithm == SignatureAlgorithm.NONE) {
                //it is plaintext, but it has a signature.  This is invalid:
                String msg = "JWT string has a digest/signature, but the header does not reference a valid signature " +
                             "algorithm.";
                throw new MalformedJwtException(msg);
            }

            if (key != null && keyBytes != null) {
                throw new IllegalStateException("A key object and key bytes cannot both be specified. Choose either.");
            } else if ((key != null || keyBytes != null) && signingKeyResolver != null) {
                String object = key != null ? "a key object" : "key bytes";
                throw new IllegalStateException("A signing key resolver and " + object + " cannot both be specified. Choose either.");
            }

            //digitally signed, let's assert the signature:
            Key key = this.key;
            //获取加密盐
            if (key == null) { //fall back to keyBytes

                byte[] keyBytes = this.keyBytes;

                if (Objects.isEmpty(keyBytes) && signingKeyResolver != null) { //use the signingKeyResolver
                    if (claims != null) {
                        key = signingKeyResolver.resolveSigningKey(jwsHeader, claims);
                    } else {
                        key = signingKeyResolver.resolveSigningKey(jwsHeader, payload);
                    }
                }

                if (!Objects.isEmpty(keyBytes)) {

                    Assert.isTrue(algorithm.isHmac(),
                                  "Key bytes can only be specified for HMAC signatures. Please specify a PublicKey or PrivateKey instance.");

                    key = new SecretKeySpec(keyBytes, algorithm.getJcaName());
                }
            }

            Assert.notNull(key, "A signing key must be specified if the specified JWT is digitally signed.");
            
            //加密的数据内容
            //re-create the jwt part without the signature.  This is what needs to be signed for verification:
            String jwtWithoutSignature = base64UrlEncodedHeader + SEPARATOR_CHAR + base64UrlEncodedPayload;

            JwtSignatureValidator validator;
            try {
            	//通过加密算法给加密盐获取到JWT的数字签名验证器
                validator = createSignatureValidator(algorithm, key);
            } catch (IllegalArgumentException e) {
                String algName = algorithm.getValue();
                String msg = "The parsed JWT indicates it was signed with the " +  algName + " signature " +
                             "algorithm, but the specified signing key of type " + key.getClass().getName() +
                             " may not be used to validate " + algName + " signatures.  Because the specified " +
                             "signing key reflects a specific and expected algorithm, and the JWT does not reflect " +
                             "this algorithm, it is likely that the JWT was not expected and therefore should not be " +
                             "trusted.  Another possibility is that the parser was configured with the incorrect " +
                             "signing key, but this cannot be assumed for security reasons.";
                throw new UnsupportedJwtException(msg, e);
            }
            
            //通过验证器来验证JWT是否有效
            if (!validator.isValid(jwtWithoutSignature, base64UrlEncodedDigest)) {
                String msg = "JWT signature does not match locally computed signature. JWT validity cannot be " +
                             "asserted and should not be trusted.";
                throw new SignatureException(msg);
            }
        }

        final boolean allowSkew = this.allowedClockSkewMillis > 0;

        //since 0.3:
        if (claims != null) {

            SimpleDateFormat sdf;

            final Date now = this.clock.now();
            long nowTime = now.getTime();

            //https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-30#section-4.1.4
            //token MUST NOT be accepted on or after any specified exp time:
            Date exp = claims.getExpiration();
            //验证JWT是否过期
            if (exp != null) {

                long maxTime = nowTime - this.allowedClockSkewMillis;
                Date max = allowSkew ? new Date(maxTime) : now;
                if (max.after(exp)) {
                    sdf = new SimpleDateFormat(ISO_8601_FORMAT);
                    String expVal = sdf.format(exp);
                    String nowVal = sdf.format(now);

                    long differenceMillis = maxTime - exp.getTime();

                    String msg = "JWT expired at " + expVal + ". Current time: " + nowVal + ", a difference of " +
                        differenceMillis + " milliseconds.  Allowed clock skew: " +
                        this.allowedClockSkewMillis + " milliseconds.";
                    throw new ExpiredJwtException(header, claims, msg);
                }
            }

            //https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-30#section-4.1.5
            //token MUST NOT be accepted before any specified nbf time:
            Date nbf = claims.getNotBefore();
            if (nbf != null) {

                long minTime = nowTime + this.allowedClockSkewMillis;
                Date min = allowSkew ? new Date(minTime) : now;
                if (min.before(nbf)) {
                    sdf = new SimpleDateFormat(ISO_8601_FORMAT);
                    String nbfVal = sdf.format(nbf);
                    String nowVal = sdf.format(now);

                    long differenceMillis = nbf.getTime() - minTime;

                    String msg = "JWT must not be accepted before " + nbfVal + ". Current time: " + nowVal +
                        ", a difference of " +
                        differenceMillis + " milliseconds.  Allowed clock skew: " +
                        this.allowedClockSkewMillis + " milliseconds.";
                    throw new PrematureJwtException(header, claims, msg);
                }
            }

            validateExpectedClaims(header, claims);
        }

        Object body = claims != null ? claims : payload;
        //返回JWT对象
        if (base64UrlEncodedDigest != null) {
            return new DefaultJws((JwsHeader) header, body, base64UrlEncodedDigest);
        } else {
            return new DefaultJwt(header, body);
        }
    }

5 总结  

     下面我们来回答上面的3个问题作为一个JWT的总结内容。

这三段内容到底是什么呢?有什么作用呢?安全起见为什么一定是三段呢?

     1  这三段内容分别是加密算法,数据信息体,以及数字签名

     2  第一段的作用是表明算法,第二段是数据信息的传递,第三段是第一段和第二段加密之后的数据

     3  因为在解析的时候防止别人拦截了JWT,防止篡改JWT中的数据,所以添加了第三段,需要通过第一段和第二段正确的信息进行验证是否正确。第三段的加密算法一般使用不可逆的加密算法比较好,安全系数比较高。
        

你可能感兴趣的:(JWT,java)