JWT学习笔记

Posted dingwen_blog

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JWT学习笔记相关的知识,希望对你有一定的参考价值。

一、简介

JWT是一个开放标准(rfc7519),它定义了一种紧凑的、自包含的方式,用于在各方之间以JSON对象安全地传输信息。此信息可以验证和信任,因为它是数字签名的。jwt可以使用秘密(使用HMAC算法)或使用RSA或ECDSA的公钥/私钥对进行签名。

通过JSON形式作为Web应用中的令牌,用于在各方之间安全地将信息作为JSON对象传输。在数据传输过程中还可以完成数据加密、签名等相关处理。

二、功能

2.1 授权

用户登录成功后生成JWT,每个后续请求将包括JWT,从而允允许的路由,服务和资源。单点登录是当今广泛使用JWT的一项功能,因为它的开销很小并且可以在不同的域中轻松使用。

2.2 信息交换

JSON Web Token是在各方之间安全地传输信息的好方法。因为可以对JWT进行签名(例如,使用公钥/私钥对),所以您可以确保发件人是他们所说的人。此外,由于签名是使用标头和有效负载计算的,因此您还可以验证内容是否遭到篡改

2.3与Session比较

jwt跟session不一样,jwt存储在客户端,session存储在服务器端,服务器断电后session就没了,而jwt因为存储在客户端,所以就不会被影响,只要jwt不过期,就可以继续使用。

三、基于Session的认证

3.1 方式

Http是无状态的协议,无法知道请求是哪一个用户发出的,这就意味着用户每次发起请求都需要进行认证。为了解决这个问题我们就只能将该用户的登录信息存储到服务器也就是session会话技术。让浏览器存储为cookie每次请求时携带。

3.2 流程

在这里插入图片描述

3.3带来的问题

  • 多个session存储在服务端,增加了服务器的消耗
  • 同一用户的session不在同一个服务器,在分布式系统中实现这样的功能需要实现session共享
  • 存储在cookie中的信息容易被获取存在CRSF跨站请求伪造攻击的风险

四、基于JWT的认证

4.1 流程

在这里插入图片描述

4.2 优势

  • 简洁(Compact): 可以通过URL,POST参数或者在HTTP header发送,因为数据量小,传输速度也很快

  • 自包含(Self-contained):负载中包含了所有用户所需要的信息,避免了多次查询数据库

  • 因为Token是以JSON加密的形式保存在客户端的,所以JWT是跨语言的,原则上任何web形式都支持

  • 不需要在服务器存储用户信息,减轻服务器压力,适用于分布式系统

五、JWT组成

String token = header.payload.singnature (Base64编码)
  • header(头)

    • 加密的算法
    • 令牌的类型
    {
        "alg": "HS256",
        "typ": "JWT"
    }
    
    
  • payload(负载)

用户信息的Base64编码
注意: Base64是可逆的,不应该在payload中加入敏感的信息,例如用户密码

  • Singnature(签名)

使用前两部分按照秘钥进行指定加密算法加密之后的结果

六、常见异常信息

  • SignatureVerificationException: 签名不一致的异常
  • TokenExpiredException : token过期异常
  • AlgorithmMismatchException: 算法不匹配异常
  • InvalidClaimException: 失效的负载异常

七、API

package com.dingwen.sprboojwtstu;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.dingwen.sprboojwtstu.entity.UserEntity;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.Arrays;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;
import java.util.function.BiConsumer;

//@SpringBootTest
@Slf4j
class SpringbootJwtStudyApplicationTests {

    public static final String SECRET_KEY = "!QAZ@WSX";

    @Test
    void contextLoads() {
    }


    /**
     * jwt创建测试
     */
    @Test
    void jwtCreateTest() {
        // 当前时间 add 60 秒
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.SECOND, 60);

        // 模拟用户
        UserEntity userEntity = UserEntity.builder()
                .username("dingwen")
                .password("123456")
                .permissionList(Arrays.asList("admin", "developer"))
                .build();

        Map<String, Object> payload = new HashMap<>();
        payload.put("username", userEntity.getUsername());
        payload.put("password", userEntity.getPassword());
        payload.put("permissionList", userEntity.getPermissionList());
        // 生成令牌
        String token = JWT.create()
//                .withHeader() 执行加密类型 & 令牌类型: 默认即可
                .withClaim("payload", payload) // 负载信息
                .withExpiresAt(calendar.getTime()) // 过期时间
                .sign(Algorithm.HMAC256(SECRET_KEY)); // 加密算法(签名)

        log.info("生成的token为:{}", token);

        jwtVerifierTest(token);
    }


    /**
     * jwt匹配测试
     */
    @Test
    void jwtVerifierTest(String token) {
        JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(SECRET_KEY)).build();
        DecodedJWT decodedJWT = jwtVerifier.verify(token);
        log.info("header:{}", decodedJWT.getHeader());
        log.info("payload:{}", decodedJWT.getPayload());
        log.info("expiresAt:{}",decodedJWT.getExpiresAt());

        Map<String, Object> payload = decodedJWT.getClaim("payload").asMap();
        BiConsumer<String, Object> biConsumerPrint = (k, v) -> log.info("{}:{}", k, v);
        payload.forEach(biConsumerPrint);


    }

}

八、案例

8.1编写JWT工具类

package com.dingwen.sprboojwtstu.util;

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;

import java.util.Calendar;
import java.util.Map;
import java.util.Optional;

/**
 * JWT 工具类
 *
 * @author dingwen
 * 2021.05.31 11:39
 */
public class JwtUtil {
    /**
     * 秘密密钥
     */
    private static final String SECRET_KEY = "!qaz@wsx";

    /**
     * 过期时间单位
     */
    public static final int unit = Calendar.MINUTE;

    /**
     * 过期时间量
     */
    public static final int amount = 30;

    /**
     * 创建jwt牌
     *
     * @param payload 有效载荷
     * @return {@link String}
     */
    public static String createJwtToken(Map<String, Object> payload) {
        // payload
        Optional.ofNullable(payload).orElseThrow(() -> new RuntimeException("jwt payload not null"));
        // 过期时间
        Calendar calendar = Calendar.getInstance();
        calendar.add(unit, amount);

        return JWT.create()
                .withClaim("payload", payload)
                .withExpiresAt(calendar.getTime())
                .sign(Algorithm.HMAC256(SECRET_KEY));
    }


    /**
     * 验证
     *
     * @param jwtToken jwt令牌
     * @return {@link DecodedJWT}
     */
    public static DecodedJWT verify(String jwtToken) {
        return JWT.require(Algorithm.HMAC256(SECRET_KEY)).build().verify(jwtToken);
    }

    /**
     * 得到有效载荷
     *
     * @param jwtToken jwt令牌
     * @return {@link Map<>}
     */
    public static Map<String, Object> getPayload(String jwtToken) {
        DecodedJWT decodedJWT = verify(jwtToken);
        return decodedJWT.getClaim("payload").asMap();
    }

}

8.2 登录接口

	package com.dingwen.sprboojwtstu.controller;

import com.dingwen.sprboojwtstu.util.JwtUtil;
import com.dingwen.sprboojwtstu.entity.UserEntity;
import com.dingwen.sprboojwtstu.result.Result;
import com.dingwen.sprboojwtstu.result.ResultGenerator;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

/**
 * login controller
 *
 * @author dingwen
 * 2021.05.31 11:26
 */
@Api(tags = "登录")
@RestController
public class LoginController {

    /**
     * 登录
     *
     * @param userEntity 用户实体
     * @return {@link Result}
     */
    @ApiOperation("登录")
    @ApiImplicitParam(value = "用户实体", required = true, type = "UserEntity")
    @PostMapping("/login")
    public Result login(@RequestBody UserEntity userEntity) {
        // 模拟登录失败
        if (userEntity.getUsername().equalsIgnoreCase("failure")) {
            return ResultGenerator.genFailureResult("登录失败").setCode(401);
        }
        // 模拟登录成功
        // 生成jwt token
        //构建payload
        Map<String, Object> payload = new HashMap<>();
        payload.put("username", userEntity.getUsername());
        payload.put("permissionList", userEntity.getPermissionList());

        // 生成jwt token
        String token = JwtUtil.createJwtToken(payload);

        return ResultGenerator.genOkResult(token).setMessage("登录成功");
    }
}

8.3 拦截器

package com.dingwen.sprboojwtstu.interceptor;

import com.auth0.jwt.exceptions.AlgorithmMismatchException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.dingwen.sprboojwtstu.exception.ServiceException;
import com.dingwen.sprboojwtstu.util.JwtUtil;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.ws.Service;
import java.util.Optional;

/**
 * JWT 令牌 校验 拦截器
 *
 * @author dingwen
 * 2021.05.31 13:45
 */
public class JwtInterceptor implements HandlerInterceptor {

    /**
     * 前处理
     *
     * @param request  请求
     * @param response 响应
     * @param handler  处理程序
     * @return boolean
     * @throws Exception 异常
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        try {
            String token = request.getHeader("token");
            Optional.ofNullable(token).orElseThrow(() -> new ServiceException("token不能为空"));
            JwtUtil.verify(token);
            return true;
        } catch (TokenExpiredException e) {
            throw new ServiceException("token已过期");
        } catch (SignatureVerificationException e) {
            throw new ServiceException("签名错误");
        } catch (AlgorithmMismatchException e) {
            throw new ServiceException("加密算法不匹配");
        } catch (ServiceException e) {
            throw new ServiceException("token不能为空");
        } catch (Exception e) {
            throw new ServiceException("无效token");
        }

    }
}

九、测试

9.1 登录失败

在这里插入图片描述

9.2 登录成功返回令牌

在这里插入图片描述

9.3不携带令牌请求

在这里插入图片描述

9.4携带错误令牌请求

在这里插入图片描述

9.5正确令牌请求

在这里插入图片描述

以上是关于JWT学习笔记的主要内容,如果未能解决你的问题,请参考以下文章

JWT学习笔记

[原创]java WEB学习笔记61:Struts2学习之路--通用标签 property,uri,param,set,push,if-else,itertor,sort,date,a标签等(代码片段

基于JWT的身份认证学习笔记

json web token 网上学习笔记

前后端分离学习笔记 ---[跨域问题,JWT,路由守卫,Axios设置请求拦截和响应拦截]

前后端分离学习笔记 ---[跨域问题,JWT,路由守卫,Axios设置请求拦截和响应拦截]