SpringBoot使用JWT入门级示例

声明这只是一个入门级的JWT示例教学,欢迎各位朋友踊跃发言,一起探讨进步。

声明本文不介绍JWT相关概念,也不比较JWT的各个类库,对相关概念、JWT各个类库感兴趣的朋友可
           自行查阅相关资料;本文直接演示示例使用入门级JWT。


提示本人较懒,不想什么基本的东西都自己写,所以这里使用了JWT众多类库中的nimbus-jose-jwt类库。
           本文中涉及到的JwtUtil工具类,其实是本人对nimbus-jose-jwt提供的基本功能的一个简单封装。

软硬件环境说明Windows10、IntelliJ IDEA、SpringBoot 2.1.6.RELEASE。

准备工作:在pom.xml中映入依赖

SpringBoot使用JWT入门级示例_第1张图片

本人为了及快速开发、为了示例,还引入了其他的一些不是关键的依赖文件。给出完整pom.xml文字版:



    4.0.0
    
        org.springframework.boot
        spring-boot-starter-parent
        2.1.6.RELEASE
         
    
    com.szlzcl
    jwt-token
    0.0.1-SNAPSHOT
    jwt-token
    测试使用json web token

    
        1.8
    

    
        
            org.springframework.boot
            spring-boot-starter-web
        

        
            org.projectlombok
            lombok
            true
        

        
            org.springframework.boot
            spring-boot-starter-test
            test
        

        
        
            com.nimbusds
            nimbus-jose-jwt
            7.1
        

        
        
            com.alibaba
            fastjson
            1.2.56
        

    

    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
            
        
    


JWT简单使用示例

先给出本人的示例项目结构

SpringBoot使用JWT入门级示例_第2张图片

各个文件的具体内容

  • JwtUtil

import com.nimbusds.jose.*;
import com.nimbusds.jose.crypto.MACSigner;
import com.nimbusds.jose.crypto.MACVerifier;
import com.nimbusds.jose.crypto.RSASSASigner;
import com.nimbusds.jose.crypto.RSASSAVerifier;
import com.nimbusds.jose.jwk.RSAKey;

import java.text.ParseException;

/**
 * 对nimbus-jose-jwt类库提供的基本功能 再进行工具类封装
 *
 * @author JustryDeng
 * @date 2019/7/21 13:46
 */
@SuppressWarnings("unused")
public class JwtUtil {

    /**
     * 生成token -- 采用【对称加密】算法HS256验证签名
     *
     * 注:此方法生成的jwt的Header部分为默认的
     *    {
     *      "alg": "HS256",
     *      "typ": "JWT"
     *    }
     *
     * @param payloadJsonString
     *            有效负载JSON字符串
     *
     * @param secret
     *            对称加密/解密密钥
     *            注意:secret.getBytes().length 必须 >=32
     *
     * @return token
     * @throws JOSEException
     *             以下情况会抛出此异常:
     *                 1、密钥长度 secret.getBytes().length < 32时,会抛出此异常
     *                 2、JWS已签名
     *                 3、JWS无法使用指定的签名器
     * @date 2019/7/21 13:54
     */
    public static String generateToken(String payloadJsonString, String secret)
            throws JOSEException {

        // 创建Header(设置以JWSAlgorithm.HS256算法进行签名认证)
        JWSHeader jwsHeader = new JWSHeader(JWSAlgorithm.HS256);

        // 建立Payload
        Payload payload = new Payload(payloadJsonString);

        /// Signature相关
        // 根据Header以及Payload创建JSON Web Signature (JWS)对象
        JWSObject jwsObject = new JWSObject(jwsHeader, payload);
        // 使用给的对称加密密钥,创建一个加密器
        JWSSigner jwsSigner = new MACSigner(secret);
        // 将该签名器 与 JSON Web Signature (JWS)对象进行关联
        // 即: 指定JWS使用该签名器进行签名
        jwsObject.sign(jwsSigner);

        // 使用JWS生成JWT(即:使用JWS生成token)
        return jwsObject.serialize();
    }

    /**
     * 校验token是否被篡改,并返回有效负载JSON字符串 -- 采用【对称加密】算法HS256验证签名
     *
     * @param secret
     *            对称加密/解密密钥
     *            注意:secret.getBytes().length 必须 >=32
     *
     * @return  有效负载JSON字符串
     * @throws JOSEException,ParseException,JwtSignatureVerifyException 异常信息
     * @date 2019/7/21 14:08
     */
    public static String verifySignature(String token, String secret)
            throws JOSEException, ParseException, JwtSignatureVerifyException {
        // 解析token,将token转换为JWSObject对象
        JWSObject jwsObject = JWSObject.parse(token);

        // 创建一个JSON Web Signature (JWS) verifier.用于校验签名(即:校验token是否被篡改)
        JWSVerifier jwsVerifier = new MACVerifier(secret);
        // 如果校验到token被篡改(即:签名认证失败),那么抛出异常
        if(!jwsObject.verify(jwsVerifier)) {
            throw new JwtSignatureVerifyException("Signature verification result is fail!");
        }

        // 获取有效负载
        Payload payload = jwsObject.getPayload();

        // 返回 有效负载JSON字符串
        return payload.toString();
    }


    /**
     * 生成token -- 采用【非对称加密】算法RS256验证签名
     *
     * @param payloadJsonString
     *            有效负载JSON字符串
     *
     * @param rsaKey
     *            非对称加密密钥对
     *            提示:RSAKey示例,可以这么获得
     *            RSAKeyGenerator rsaKeyGenerator = new RSAKeyGenerator(1024 * 3);
     *            RSAKey rsaKey = rsaKeyGenerator.generate();
     *
     * @return token
     * @throws JOSEException 异常信息
     * @date 2019/7/21 13:54
     */
    public static String generateTokenByAsymmetric(String payloadJsonString, RSAKey rsaKey)
            throws JOSEException {

        // Header
        JWSHeader jwsHeader = new JWSHeader.Builder(JWSAlgorithm.RS256)
                                           .keyID(rsaKey.getKeyID())
                                           .build();
        // Payload
        Payload  payload= new Payload(payloadJsonString);

        /// Signature相关
        // 根据Header以及Payload创建JSON Web Signature (JWS)对象
        JWSObject jwsObject = new JWSObject(jwsHeader, payload);
        // 使用给的对称加密密钥,创建一个加密器
        JWSSigner signer = new RSASSASigner(rsaKey);
        // 将该签名器 与 JSON Web Signature (JWS)对象进行关联
        // 即: 指定JWS使用该签名器进行签名
        jwsObject.sign(signer);

        // 使用JWS生成JWT(即:使用JWS生成token)
        return jwsObject.serialize();
    }

    /**
     * 校验token是否被篡改,并返回有效负载JSON字符串 -- 采用【非对称加密】算法RS256验证签名
     *
     * @param rsaKey
     *          非对称加密密钥对
     *
     * @return  有效负载JSON字符串
     * @throws JOSEException,ParseException,JwtSignatureVerifyException 异常信息
     * @date 2019/7/21 14:08
     */
    public static String verifySignatureByAsymmetric(String token, RSAKey rsaKey)
            throws JOSEException, ParseException, JwtSignatureVerifyException {
        // 根据token获得JSON Web Signature (JWS)对象
        JWSObject jwsObject = JWSObject.parse(token);

        // 获取到公钥
        RSAKey publicRsaKey = rsaKey.toPublicJWK();
        // 根据公钥 获取 Signature验证器
        JWSVerifier jwsVerifier = new RSASSAVerifier(publicRsaKey);

        // 如果校验到token被篡改(即:签名认证失败),那么抛出异常
        if(!jwsObject.verify(jwsVerifier)) {
            throw new JwtSignatureVerifyException("Signature verification result is fail!");
        }

        // 获取有效负载
        Payload payload = jwsObject.getPayload();

        // 返回 有效负载JSON字符串
        return payload.toString();
    }

//    /**
//     *  main方法测试
//     *
//     *  --------------------------------------下面的为测试代码--------------------------------------
//     *
//     */
//    public static void main(String[] args)
//            throws JOSEException, ParseException, JwtSignatureVerifyException {
//        // 测试对称加密算法 验证签名的JWT
//        testSymmetric();
//        // 测试非对称加密算法 验证签名的JWT
//        testAsymmetric();
//    }
//
//    /**
//     * 测试对称加密算法的token生成与 签名检验
//     */
//    private static void testSymmetric()
//            throws JOSEException, ParseException, JwtSignatureVerifyException {
//        String secret = "adsgfiaughofashdofhjasodhfoasdafisd";
//        String token = JwtUtil.generateToken("{\"name\":\"张三\"}", secret);
//        System.out.println(token);
//        String payloadJsonString = JwtUtil.verifySignature(token, secret);
//        System.out.println(payloadJsonString);
//    }
//
//    /**
//     * 测试对称加密算法的token生成与 签名检验
//     */
//    private static void testAsymmetric()
//            throws JOSEException, ParseException, JwtSignatureVerifyException {
//        RSAKeyGenerator rsaKeyGenerator = new RSAKeyGenerator(1024 * 3);
//        RSAKey rsaKey = rsaKeyGenerator.generate();
//        String token =
//                JwtUtil.generateTokenByAsymmetric("{\"name\":\"张三\"}", rsaKey);
//        System.out.println(token);
//        String payloadJsonString = JwtUtil.verifySignatureByAsymmetric(token, rsaKey);
//        System.out.println(payloadJsonString);
//    }
}
  • JwtSignatureVerifyException:

/**
 * 签名校验失败异常
 * 即:token被篡改异常
 *
 * @author JustryDeng
 * @date 2019/7/21 14:23
 */
@SuppressWarnings("unused")
public class JwtSignatureVerifyException extends Exception {

    private static final long serialVersionUID = -861994790728930634L;

    /**
     * Creates a new JwtSignatureVerifyException with the specified message.
     *
     * @param message The exception message.
     */
    public JwtSignatureVerifyException(String message) {
        super(message);
    }

    /**
     * Creates a new JwtSignatureVerifyException with the specified message and cause.
     *
     * @param message The exception message.
     * @param cause   The exception cause.
     */
    public JwtSignatureVerifyException(String message, Throwable cause) {
        super(message, cause);
    }
}
  • PayloadDTO

import lombok.*;

import java.io.Serializable;

/**
 * JWT中间部分 有效负载 数据模型
 *
 * @author JustryDeng
 * @date 2019/7/21 15:31
 */
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class PayloadDTO implements Serializable {

    private static final long serialVersionUID = 5988619597830511341L;

    /**
     * ------------------------------------这部分为 注册声明------------------------------------
     * 这个jwt的身份id
     */
    private String jti;

    /** 签发人 */
    private String iss;

    /** 过期时长(单位ms) */
    private Long exp;

    /** 主题 */
    private String sub;

    /** 受众 */
    private String aud;

    /** 生效时间(1970年1月1日到现在的偏移量) */
    private Long nbf;

    /** 签发时间(1970年1月1日到现在的偏移量) */
    private Long iat;

    /**
     * ------------------------------------这部分为 公开声明------------------------------------
     * 姓名
     */
    private String name;

    /**
     * 性别
     */
    private String gender;

    /**
     * 出生日期
     */
    private String birthday;

    /**
     * ------------------------------------这部分为 私有声明------------------------------------
     * 是否是管理员
     */
    private Boolean isAdmin;
}
  • application.properties

# 验证签名的(对称加密)算法的密钥
my.jwt.signature.algorithm.secret=abcdefghijklmnopqrstuvwxyz123456789
# 单位ms
my.jwt.expiration.time=60000
  • DemoController

import com.alibaba.fastjson.JSON;
import com.nimbusds.jose.JOSEException;
import com.szlzcl.jwttoken.model.PayloadDTO;
import com.szlzcl.jwttoken.util.JwtSignatureVerifyException;
import com.szlzcl.jwttoken.util.JwtUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.text.ParseException;

/**
 * controller
 *
 * @author JustryDeng
 * @date 2019/7/21 11:03
 */
@Slf4j
@RestController
public class DemoController {

    @Value("${my.jwt.signature.algorithm.secret}")
    private String signatureAlgorithmSecret;

    @Value("${my.jwt.expiration.time}")
    private Long tokenExpirationTime;

    /**
     * 用户登录, 并返回token信息
     *
     * 注:真正使用时,往往除了token,还会返回一些其他的相关信息。
     * 注:真正使用时,token往往放在响应头里面进行返回。
     * 注:真正使用时,用户登录不应用get方法,而是用post方法。
     *
     * @param name 用户名
     * @param password mima
     *
     * @return  返回jwt
     * @date 2019/7/21 11:05
     */
    @GetMapping("/login")
    @SuppressWarnings("all")
    public String login(@RequestParam("name") String name,
                        @RequestParam("password")  String password){
        // 模拟 用户名密码 均正确
        if("邓沙利文".equals(name) && "123xyz".equals(password)) {
            /*
             * 模拟获得有效负载信息
             * 注:根据自己业务的不同,往往这一步会有非常大的不同
             */
            PayloadDTO payloadDTO  = getPayload(name);
            // 生成token
            String payloadJsonString = JSON.toJSONString(payloadDTO);
            String token;
            try {
                token = JwtUtil.generateToken(payloadJsonString, signatureAlgorithmSecret);
            } catch (JOSEException e) {
                log.error("生成token失败!", e);
                return "生成token失败";
            }
            return token;
        }
        return "用户名密码有误!";
    }

    /**
     * 验证请求中的token是否被篡改、是否过期(是否有效)。
     * 如果有效那么返回响应的业务信息。
     *
     * 注:真正使用时,token的验证往往在Filter或AOP中进行。
     * 注:真正使用时,一般将token放在请求头中,以Authorization作为key,以Bearer 作为值。
     *
     * @param token json web token(JWT)
     *
     * @return  返回相应信息
     * @date 2019/7/21 11:05
     */
    @GetMapping("/test")
    public String logis(@RequestHeader("Authorization") String token){
        String payloadJsonString;
        // 验证token是否被篡改
        try {
            payloadJsonString = JwtUtil.verifySignature(token, signatureAlgorithmSecret);
        } catch (JwtSignatureVerifyException e) {
            log.error("token签名验证失败, token已经被篡改!", e);
            return "token签名验证失败, token已经被篡改!";
        } catch (JOSEException | ParseException e) {
            log.error("验证token签名时,系统异常!", e);
            return "验证token签名时,系统异常!";
        }

        /// 验证 有效负载中的其他信息 (如:过期时间、权限信息 等等)
        PayloadDTO payloadDTO =JSON.parseObject(payloadJsonString, PayloadDTO.class);
        log.info("token中存放的用户信息是 -> {}", payloadDTO);
        // token生效时间
        long nbf = payloadDTO.getNbf();
        log.info("token的生效时间是 -> {}", nbf);
        // token有效时长
        long exp = payloadDTO.getExp();
        log.info("token的生效时长是 -> {}", exp);
        // 当前时间
        long nowTime = System.currentTimeMillis();
        log.info("当前时间是 -> {}", nowTime);

        boolean isAuthorized = (nowTime - nbf) >= 0 && exp >= (nowTime - nbf);
        log.info("token -> 【{}】是否有效? {}", token, isAuthorized);

        if (isAuthorized) {
            return "token认证通过!";
        }
        return "token已过期,请重新登录";

    }

    /**
     * 模拟生成 有效负载信息
     *
     * 注:根据自己业务情况的不同,可能会往有效负载中放入不同的信息;
     *    此步骤的逻辑也可能会非常复杂。
     *
     * @date 2019/7/21 16:00
     */
    private PayloadDTO getPayload(String name) {
        return PayloadDTO.builder()
                // 放置过期时长
                .exp(tokenExpirationTime)
                // 放置生效时间
                .nbf(System.currentTimeMillis())
                // 放置用户信息
                .name(name)
                .birthday("1994-02-05")
                .isAdmin(true)
                .build();
    }
}

测试一下

第一步:启动项目,并访问localhost:8080/login?name=邓沙利文&password=123xyz

SpringBoot使用JWT入门级示例_第3张图片

 

第二步:以Authorization为key,以将第一步拿到的token为value,放入请求Header里面,并访问localhost:8080/test

SpringBoot使用JWT入门级示例_第4张图片

 

此时,我们不妨观察一下IDEA的控制台输出:

token中存放的用户信息是 -> PayloadDTO(jti=null, iss=null, exp=60000, sub=null, aud=null, nbf=1563700723248, iat=null, name=邓沙利文, gender=null, birthday=1994-02-05, isAdmin=true)
token的生效时间是 -> 1563700723248
token的生效时长是 -> 60000
当前时间是 -> 1563700766988
token -> 【eyJhbGciOiJIUzI1NiJ9.eyJiaXJ0aGRheSI6IjE5OTQtMDItMDUiLCJleHAiOjYwMDAwLCJpc0FkbWluIjp0cnVlLCJuYW1lIjoi6YKT5rKZ5Yip5paHIiwibmJmIjoxNTYzNzAwNzIzMjQ4fQ.0nCrMAC8QO6r-dVnvQkzsSDlIWu3dxNwxOLnVs74saU】是否有效? true

 

第三步:等一分钟,让时间超过我们设置的有效时长(本人设置的是60000毫秒),再按照第二步的方式进行访问:

SpringBoot使用JWT入门级示例_第5张图片

此时,我们不妨再观察一下IDEA的控制台输出:

token中存放的用户信息是 -> PayloadDTO(jti=null, iss=null, exp=60000, sub=null, aud=null, nbf=1563700723248, iat=null, name=邓沙利文, gender=null, birthday=1994-02-05, isAdmin=true)
token的生效时间是 -> 1563700723248
token的生效时长是 -> 60000
当前时间是 -> 1563701012872
token -> 【eyJhbGciOiJIUzI1NiJ9.eyJiaXJ0aGRheSI6IjE5OTQtMDItMDUiLCJleHAiOjYwMDAwLCJpc0FkbWluIjp0cnVlLCJuYW1lIjoi6YKT5rKZ5Yip5paHIiwibmJmIjoxNTYzNzAwNzIzMjQ4fQ.0nCrMAC8QO6r-dVnvQkzsSDlIWu3dxNwxOLnVs74saU】是否有效? false

 

由此可见,入门级JWT使用并示例成功!

 

^_^ 如有不当之处,欢迎指正

^_^ 参考链接
     
         http://andaily.com/blog/?p=956
               https://www.jianshu.com/p/75208a68c3b9
               https://www.sohu.com/a/250972011_575744

^_^ 测试代码托管链接
           
   https://github.com/JustryDeng...er/Abc_JwtToken_Demo

^_^ 本文已经被收录进《程序员成长笔记(五)》,笔者JustryDeng

你可能感兴趣的:(Java知识大杂烩,JWT,JSON,Web,Token,JWT使用示例,无状态token之JWT,token)