JWT

JSON Web Token(JWT)是一个非常轻巧的规范。这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息。

一个JWT实际上就是一个字符串,它由三部分组成,头部、载荷与签名。

头部用于描述关于该JWT的最基本的信息

例如其类型以及签名所用的算法等。这也可以被表示成一个JSON对象。

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

在头部指明了签名算法是HS256算法。 我们进行BASE64编码http://base64.xpcha.com/,编码后的字符串如下:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9

载荷(playload)

载荷就是存放有效信息的地方。这个名字像是特指飞机上承载的货品,这些有效信息包含三个部分

1)标准中注册的声明(建议但不强制使用) 

iss: jwt签发者
sub: jwt所面向的用户
aud: 接收jwt的一方
exp: jwt的过期时间,这个过期时间必须要大于签发时间
nbf: 定义在什么时间之前,该jwt都是不可用的.
iat: jwt的签发时间
jti: jwt的唯一身份标识,主要用来作为一次性token。

(2)公共的声明

公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密.

(3)私有的声明

私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。

这个指的就是自定义的claim。比如前面那个结构举例中的admin和name都属于自定的claim。这些claim跟JWT标准规定的claim区别在于:JWT规定的claim,JWT的接收方在拿到JWT之后,都知道怎么对这些标准的claim进行验证(还不知道是否能够验证);而private claims不会验证,除非明确告诉接收方要对这些claim进行验证以及规则才行。

定义一个payload:

 

{"sub":"1234567890","name":"John Doe","admin":true}

然后将其进行base64加密,得到Jwt的第二部分。

 

eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9

签证(signature)

jwt的第三部分是一个签证信息,这个签证信息由三部分组成:

header (base64后的)

payload (base64后的)

secret

这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。

 

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

注意:secret是保存在服务器端的,jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和jwt的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。

JJWT签发与验证token

JJWT是一个提供端到端的JWT创建和验证的Java库。永远免费和开源(Apache License,版本2.0),JJWT很容易使用和理解。它被设计成一个以建筑为中心的流畅界面,隐藏了它的大部分复杂性。

官方文档:

https://github.com/jwtk/jjwt

创建token

(1)新建项目中的pom.xml中添加依赖:

<dependency>
 <groupId>io.jsonwebtoken</groupId>
 <artifactId>jjwt</artifactId>
 <version>0.9.0</version>
</dependency>

(2)创建测试类,代码如下

/* *
     * @Description  json web token 签发
     * @param id 令牌ID
     * @param subject 用户标识
     * @param userId
     * @param issuer 签发人
     * @param period 有效时间(秒)
     * @param roles 访问主张-角色
     * @param permissions 访问主张-权限
     * @param algorithm 加密算法
     * @Return java.lang.String
     */
    public static String issueJWT(String id,String subject, String userId, String issuer, Long period,
                                  String roles, String permissions, SignatureAlgorithm algorithm) {
        // 当前时间戳
        Long currentTimeMillis = System.currentTimeMillis();
        // 秘钥
        byte[] secreKeyBytes = DatatypeConverter.parseBase64Binary(SECRET_KEY);
        JwtBuilder jwtBuilder = Jwts.builder();
        if (StringUtil.isNotBlank(id)) {
            jwtBuilder.setId(id);
        }
        if (StringUtil.isNotBlank(subject)) {
            jwtBuilder.setSubject(subject);
        }
        if (StringUtil.isNotBlank(issuer)) {
            jwtBuilder.setIssuer(issuer);
        }
        // 设置签发时间
        jwtBuilder.setIssuedAt(new Date(currentTimeMillis));
        // 设置到期时间
        if (null != period) {
            jwtBuilder.setExpiration(new Date(currentTimeMillis + period*1000));
        }

        if (StringUtil.isNotBlank(userId)) {
            jwtBuilder.claim("userId",userId);
        }
        if (StringUtil.isNotBlank(roles)) {
            jwtBuilder.claim("roles",roles);
        }
        if (StringUtil.isNotBlank(permissions)) {
            jwtBuilder.claim("perms",permissions);
        }
        // 压缩,可选GZIP
        jwtBuilder.compressWith(CompressionCodecs.DEFLATE);
        // 加密设置
        jwtBuilder.signWith(algorithm,secreKeyBytes);

        return jwtBuilder.compact();
    }

运行打印结果:

jwt:eyJhbGciOiJIUzUxMiIsInppcCI6IkRFRiJ9.eNosyz0LwjAUheH_cucGbpqbj3YTdNBBoQ6KiyRpqlW02rQiiP_dFFzf85wPXIYWSvDeSNTomMqFYCRqx4xUhiFpawuNWnEBGcTRJdykJUdvDXcWKSdPyJ2qmxCkREU2wTbGBIfuGu4shv4V-inaAUoujTSoSRcZhPfjHzipKYzJLuv07J7V8aRWtNC2343nYr8x6_ktbA9UzeD7AwAA__8.764bh6E-PDmLLerSrveC2uwsSWYnGKArzwy-5F0LBfSCWfNj5z6lCvKFUMLItxygHONkz1qNDNh6QPC-qMibmg

再次运行,会发现每次运行的结果是不一样的,因为我们的载荷中包含了时间。

解析token

我们刚才已经创建了token ,在web应用中这个操作是由服务端进行然后发给客户端,客户端在下次向服务端发送请求时需要携带这个token(这就好像是拿着一张门票一样),那服务端接到这个token 应该解析出token中的信息(例如用户id),根据这些信息查询数据库返回相应的结果。

/**
     * 验签JWT
     *
     * @param jwt json web token
     */
    public static Token parseJwt(String jwt, String appKey) throws ExpiredJwtException, UnsupportedJwtException,
            MalformedJwtException, SignatureException, IllegalArgumentException {
        Claims claims = Jwts.parser()
                .setSigningKey(DatatypeConverter.parseBase64Binary(appKey))
                .parseClaimsJws(jwt)
                .getBody();
        Token jwtAccount = new Token();
        //令牌ID
        jwtAccount.setTokenId(claims.getId());
        //客户标识
        String subject = claims.getSubject();
        jwtAccount.setSubject(subject);
        //userId
        jwtAccount.setUserId(claims.get("userId", String.class));
        //签发者
        jwtAccount.setIssuer(claims.getIssuer());
        //签发时间
        jwtAccount.setIssuedAt(claims.getIssuedAt());
        //接收方
        jwtAccount.setAudience(claims.getAudience());
        //访问主张-角色
        jwtAccount.setRoles(claims.get("roles", String.class));
        //访问主张-权限
        jwtAccount.setPerms(claims.get("perms", String.class));
        return jwtAccount;
    }

试着将token或签名秘钥篡改一下,会发现运行时就会报错,所以解析token也就是验证token.。

设置过期时间

有很多时候,我们并不希望签发的token是永久生效的,所以我们可以为token添加一个过期时间。

(1)创建token 并设置过期时间

long now=System.currentTimeMillis();
long exp=now+1000*30;//30秒过期
JwtBuilder jwtBuilder = Jwts.builder().setId( "888" )
 .setSubject( "小白" )
 .setIssuedAt( new Date() )//签发时间
 .setExpiration( new Date( exp ) )//过期时间
 .signWith( SignatureAlgorithm.HS256, "hahaha" );
String token = jwtBuilder.compact();
System.out.println(token);

自定义claims

我们刚才的例子只是存储了id和subject两个信息,如果你想存储更多的信息(例如角色)可以定义自定义claims。

long now=System.currentTimeMillis();
long exp=now+1000*30;//30秒过期
JwtBuilder jwtBuilder = Jwts.builder().setId( "888" )
 .setSubject( "小白" )
 .setIssuedAt( new Date() )//签发时间
 .setExpiration( new Date( exp ) )//过期时间
 .claim( "roles","admin" )
 .signWith( SignatureAlgorithm.HS256, "hahaha" );
String token = jwtBuilder.compact();
System.out.println(token);

运行打印效果:

eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODgiLCJzdWIiOiLlsI_nmb0iLCJpYXQiOjE1NTc5MDU4MDIsImV4cCI6MTU1NzkwNjgwMiwicm9sZXMiOiJhZG1pbiJ9.AS5Y2fNCwUzQQxXh_QQWMpaB75YqfuK-2P7VZiCXEJI

解析TOKEN:

String token ="eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODgiLCJzdWIiOiLlsI_nmb0iLCJpYXQiOjE1NjIyNTM3NTQsImV4cCI6MTU2MjI1Mzc4Mywicm9sZXMiOiJhZG1pbiJ9.CY6CMembCi3mAkBHS3ivzB5w9uvtZim1HkizRu2gWaI";
Claims claims = Jwts.parser().setSigningKey( "hahaha" ).parseClaimsJws( token ).getBody();
System.out.println(claims);
System.out.println(claims.get( "roles" ));

附录完整的JWT工具类:

package com.inspur.tax.rest.token;

import java.io.IOException;
import java.security.SignatureException;
import java.util.Date;
import java.util.Map;
import java.util.UUID;

import javax.xml.bind.DatatypeConverter;

import org.springframework.util.Assert;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.inspur.tax.api.common.entity.TransEntity;
import com.inspur.tax.common.Const;
import com.inspur.tax.common.Result;
import com.inspur.tax.exception.BaseException;
import com.inspur.tax.rest.token.entity.Token;
import com.inspur.tax.utils.StringUtil;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.CompressionCodec;
import io.jsonwebtoken.CompressionCodecResolver;
import io.jsonwebtoken.CompressionCodecs;
import io.jsonwebtoken.CompressionException;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Header;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.UnsupportedJwtException;
import io.jsonwebtoken.impl.DefaultHeader;
import io.jsonwebtoken.impl.DefaultJwsHeader;
import io.jsonwebtoken.impl.TextCodec;
import io.jsonwebtoken.impl.compression.DefaultCompressionCodecResolver;

public class JwtUtil {
    //秘钥
    public static final String SECRET_KEY = Const.TOKEN_SECRET;
    private static final ObjectMapper MAPPER = new ObjectMapper();
    private static CompressionCodecResolver codecResolver = new DefaultCompressionCodecResolver();

    //私有化构造
    private JwtUtil() {
    }

    /* *
     * @Description  json web token 签发
     * @param id 令牌ID
     * @param subject 用户标识
     * @param userId
     * @param issuer 签发人
     * @param period 有效时间(秒)
     * @param roles 访问主张-角色
     * @param permissions 访问主张-权限
     * @param algorithm 加密算法
     * @Return java.lang.String
     */
    public static String issueJWT(String id,String subject, String userId, String issuer, Long period,
                                  String roles, String permissions, SignatureAlgorithm algorithm) {
        // 当前时间戳
        Long currentTimeMillis = System.currentTimeMillis();
        // 秘钥
        byte[] secreKeyBytes = DatatypeConverter.parseBase64Binary(SECRET_KEY);
        JwtBuilder jwtBuilder = Jwts.builder();
        if (StringUtil.isNotBlank(id)) {
            jwtBuilder.setId(id);
        }
        if (StringUtil.isNotBlank(subject)) {
            jwtBuilder.setSubject(subject);
        }
        if (StringUtil.isNotBlank(issuer)) {
            jwtBuilder.setIssuer(issuer);
        }
        // 设置签发时间
        jwtBuilder.setIssuedAt(new Date(currentTimeMillis));
        // 设置到期时间
        if (null != period) {
            jwtBuilder.setExpiration(new Date(currentTimeMillis + period*1000));
        }

        if (StringUtil.isNotBlank(userId)) {
            jwtBuilder.claim("userId",userId);
        }
        if (StringUtil.isNotBlank(roles)) {
            jwtBuilder.claim("roles",roles);
        }
        if (StringUtil.isNotBlank(permissions)) {
            jwtBuilder.claim("perms",permissions);
        }
        // 压缩,可选GZIP
        jwtBuilder.compressWith(CompressionCodecs.DEFLATE);
        // 加密设置
        jwtBuilder.signWith(algorithm,secreKeyBytes);

        return jwtBuilder.compact();
    }

    /* *
     * @Description  json web token 签发
     * @param subject 用户标识
     * @param userId
     * @param period 有效时间(秒)
     * @Return java.lang.String
     */
    public static String issueJWT(String subject, String userId,Long period) {
        return issueJWT(UUID.randomUUID().toString(), subject, userId, "token-server", period, "", null, SignatureAlgorithm.HS512);
    }

    /**
     * 解析JWT的Payload
     */
    public static String parseJwtPayload(String jwt){
        Assert.hasText(jwt, "JWT String argument cannot be null or empty.");
        String base64UrlEncodedHeader = null;
        String base64UrlEncodedPayload = null;
        String base64UrlEncodedDigest = null;
        int delimiterCount = 0;
        StringBuilder sb = new StringBuilder(128);
        for (char c : jwt.toCharArray()) {
            if (c == '.') {
                CharSequence tokenSeq = io.jsonwebtoken.lang.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);
            }
        }
        if (delimiterCount != 2) {
            String msg = "JWT strings must contain exactly 2 period characters. Found: " + delimiterCount;
            throw new MalformedJwtException(msg);
        }
        if (sb.length() > 0) {
            base64UrlEncodedDigest = sb.toString();
        }
        if (base64UrlEncodedPayload == null) {
            throw new MalformedJwtException("JWT string '" + jwt + "' is missing a body/payload.");
        }
        // =============== Header =================
        Header header = null;
        CompressionCodec compressionCodec = null;
        if (base64UrlEncodedHeader != null) {
            String origValue = TextCodec.BASE64URL.decodeToString(base64UrlEncodedHeader);
            Map<String, Object> m = readValue(origValue);
            if (base64UrlEncodedDigest != null) {
                header = new DefaultJwsHeader(m);
            } else {
                header = new DefaultHeader(m);
            }
            compressionCodec = codecResolver.resolveCompressionCodec(header);
        }
        // =============== Body =================
        String payload;
        if (compressionCodec != null) {
            byte[] decompressed = compressionCodec.decompress(TextCodec.BASE64URL.decode(base64UrlEncodedPayload));
            payload = new String(decompressed, io.jsonwebtoken.lang.Strings.UTF_8);
        } else {
            payload = TextCodec.BASE64URL.decodeToString(base64UrlEncodedPayload);
        }
        return payload;
    }

    /**
     * 验签JWT
     *
     * @param jwt json web token
     */
    public static Token parseJwt(String jwt, String appKey) throws ExpiredJwtException, UnsupportedJwtException,
            MalformedJwtException, SignatureException, IllegalArgumentException {
        Claims claims = Jwts.parser()
                .setSigningKey(DatatypeConverter.parseBase64Binary(appKey))
                .parseClaimsJws(jwt)
                .getBody();
        Token jwtAccount = new Token();
        //令牌ID
        jwtAccount.setTokenId(claims.getId());
        //客户标识
        String subject = claims.getSubject();
        jwtAccount.setSubject(subject);
        //userId
        jwtAccount.setUserId(claims.get("userId", String.class));
        //签发者
        jwtAccount.setIssuer(claims.getIssuer());
        //签发时间
        jwtAccount.setIssuedAt(claims.getIssuedAt());
        //接收方
        jwtAccount.setAudience(claims.getAudience());
        //访问主张-角色
        jwtAccount.setRoles(claims.get("roles", String.class));
        //访问主张-权限
        jwtAccount.setPerms(claims.get("perms", String.class));
        return jwtAccount;
    }

    public static Map<String, Object> readValue(String val) {
        try {
            return MAPPER.readValue(val, Map.class);
        } catch (IOException e) {
            throw new MalformedJwtException("Unable to userpager JSON value: " + val, e);
        }
    }
    
    /**
     * @Title auth 
     * @Description token验证 
     * @param @param trans
     * @param @return   
     * @return Token
      */
     public static Token auth(TransEntity<?> trans) {
         Token token = null;
         try {
             token = JwtUtil.parseJwt(trans.getComment().getToken(), JwtUtil.SECRET_KEY);
         } catch (NullPointerException e) {
             throw new BaseException(Result.COMMON_AUTH_EMPTY_ERROR);
         } catch (CompressionException | UnsupportedJwtException | MalformedJwtException | IllegalArgumentException e) {
             throw new BaseException(Result.COMMON_AUTH_ERROR);
         } catch (ExpiredJwtException e) {
             throw new BaseException(Result.COMMON_AUTH_EXPIRE_ERROR);
         } catch (SignatureException e) {
             throw new BaseException(Result.COMMON_AUTH_SIGN_ERROR);
         }

         if (null == token) {
            throw new BaseException(Result.COMMON_AUTH_ERROR);
         }
         
         return token;
     }

    public static void main(String[] args) throws Exception{
        String subject = "10115117000067865480";

        String jwt = JwtUtil.issueJWT("f56820ca81ba0424c401b6dfee55064a", "oqR_g6J4E7arWuh9XO8NDmeSZ4RA",7200L);
        System.out.println("jwt:"+jwt);
        System.out.println("----------------------------------------");
//        Thread.sleep(1000);
        String ss = JwtUtil.parseJwtPayload(jwt);
        System.out.println("ss:"+ss);

        Token ooo = JwtUtil.parseJwt(jwt,SECRET_KEY);
        System.out.println("ooo:"+ooo);
    }
}

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

李晓LOVE向阳

你的鼓励是我持续的不断动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值