天天看点

JWT详解JWT详解

JWT详解

1. 介绍

  • JWT

    简称

    JSON Web Token

    ,也就是通过**

    JSON形式作为Web应用中的令牌

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

2. 流程图

JWT详解JWT详解

2.1 认证流程

  • 首先,前端通过Web表单将自己的用户名和密码发送到后端的接口。这一过程一般是一个HTTP POST请求。建议的方式是通过SSL加密的传输(https协议),从而避免敏感信息被嗅探。
  • 后端核对用户名和密码成功后,将用户的id等其他信息作为 JWT Payload(负载),将其与头部分别进行Base64编码拼接后签名,形成一个JWT(Token)。形成的JWT就是一个形同lll.mmm.sss的字符串。

    token = head.payload.singurater

  • 后端将JWT字符串作为登录成功的返回结果返回给前端。前端可以将返回的结果保存在localStorage或sessionStorage上,退出登录时前端删除保存的JWT即可。
  • 前端在每次请求时将JWT放入HTTP Header中的Authorization位。(解决XSS和XSRF问题)
  • 后端检查是否存在,如存在验证JWT的有效性。例如,检查签名是否正确;检查Token是否过期;检查Token的接收方是否是自己(可选)。
  • 验证通过后后端使用JWT中包含的用户信息进行其他逻辑操作,返回相应结果。

2.2 jwt优势

  • 简洁(Compact): 可以通过URL,POST参数或者在HTTP header发送,因为数据量小,传输速度也很快
  • 自包含(Self-contained):负载中包含了所有用户所需要的信息,避免了多次查询数据库
  • 因为Token是以JSON加密的形式保存在客户端的,所以JWT是跨语言的,原则上任何web形式都支持。
  • 不需要在服务端保存会话信息,特别适用于分布式微服务。

3. JWT

3.1 header(表头)

  • 标头通常由两部分组成:令牌的类型(即JWT)和所使用的签名算法,例如HMAC SHA256(默认)或RSA。它会使用Base64编码组成JWT结构的第一部分。
  • 注意:Base64是一种编码,也就是说,它是可以被翻译回原来的样子的,它并不是一种加密过程。

3.2 Payload(载荷)

  • 将能用到的用户信息放在 Payload中。官方建议不要放特别敏感的信息,例如密码。

3.3 Signature(签名信息)

  • 签证由三部分组成,header和payload分别经base64Url(一种在base64上做了一点改变的编码)编码后由’.’连接,服务器生成秘钥(secret),连接之后的字符串在经header中声明的加密方式和秘钥加密,再用’.'和加密前的字符串连接。服务器验证token时只会验证第三部分。
  • header (base64Url) . payload (base64Url) . secret(header (base64Url)+payload (base64Url))

4. Session方式

  • session方法存储会有很大的缺点。略
@RestController
@RequestMapping("test")
public class SessionController {
    @PostMapping("login")
    public String login(String userName, String password, HttpServletRequest request){
        // 将用户名和密码拼接后设置在session作用域中
        request.getSession().setAttribute("user", userName + password);
        return "Login Success!";
    }
}
           

5. Jwt实现

5.1 依赖

<!--	jwt的依赖包	-->
<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>3.4.0</version>
</dependency>
           

5.2 工具类

package com.liu.jwt.util;

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

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

/**
 * @author lms
 * @date 2021-10-02 - 8:46
 *
 * jwt工具类
 */
public class JwtUtils {

    // sign
    private static final String SIGN = "[email protected]";


    // 生成token,header.payload.sign
    public static String getToken(Map<String, String> map){

        // 创建jwt builder对象
        JWTCreator.Builder builder = JWT.create();

        // 添加payload
        map.forEach((k, v) -> {
            builder.withClaim(k, v);
        });

        // 设置过期时间
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.DATE, 7);
        // 设置过期时间和添加签名信息
        String token = builder.withExpiresAt(calendar.getTime())
                .sign(Algorithm.HMAC256(SIGN));
        return token;
    }

    // 验证token的合法性
    public static void verify(String token){
        // 如果验证失败会直接抛出异常信息
        JWT.require(Algorithm.HMAC256(SIGN)).build().verify(token);
    }

    // 获取token中的信息(也可以和上面部分的代码直接合并)
    public static DecodedJWT getTokenInfo(String token){
        DecodedJWT verify = JWT.require(Algorithm.HMAC256(SIGN)).build().verify(token);
        return verify;
    }
}
           

5.3 拦截器

package com.liu.jwt.interceptor;

import com.auth0.jwt.exceptions.AlgorithmMismatchException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.liu.jwt.util.JwtUtils;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;

/**
 * @author lms
 * @date 2021-10-02 - 10:21
 * jwt拦截器,执行token的验证工作(集的配置拦截器)
 */
public class JwtInterceptor implements HandlerInterceptor {

    /**
     * 在目标方法执行之前执行
     * @param request
     * @param response
     * @param handler 目标方法
     * @return true,代表放行, false,表示禁止放行
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        HashMap<String, Object> map = new HashMap<>();
        // 因为token存放在请求头中,
        String token = request.getHeader("token");

        // 验证token
        try {
            // 直接验证token是否正确,如果token不正确,会直接抛出异常信息被全局异常捕获
            JwtUtils.verify(token);
            // 验证通过,直接放行请求
            return true;

        } catch (SignatureVerificationException e){
            map.put("msg", "无效签名");
        } catch (TokenExpiredException e){
            map.put("msg", "token已过期");
        } catch (AlgorithmMismatchException e){
            map.put("msg", "token算法不一致");
        } catch (Exception e) {
            map.put("msg", "token无效");
        }
        map.put("code", "500");
        // 将map转为json数据信息
        // 因为response底层也封装了json,也可以使用fastJson
        String json = new ObjectMapper().writeValueAsString(map);
        response.setContentType("application/json;charset=utf-8");
        response.getWriter().println(json);
        // 禁止放行请求
        return false;
    }
}
           
  • 配置拦截器
package com.liu.jwt.config;

import com.liu.jwt.interceptor.JwtInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * @author lms
 * @date 2021-10-02 - 10:29
 */
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {

    // 配置拦截器
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new JwtInterceptor())
                // 拦截所有的请求信息
                .addPathPatterns("/**")
                // 只放行/login登录请求,
                .excludePathPatterns("/login");
    }
}
           

5.4 Controller

package com.liu.jwt.controller;

import com.auth0.jwt.exceptions.AlgorithmMismatchException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.liu.jwt.entity.User;
import com.liu.jwt.service.UserService;
import com.liu.jwt.util.JwtUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;

/**
 * @author lms
 * @date 2021-10-02 - 9:24
 */
@RestController
public class UserController {

    @Resource
    private UserService userService;

    /**
     * 用户登录
     * @param user
     * @return
     */
    @PostMapping("login")
    public Map<String, Object> login(User user){
        HashMap<String, Object> map = new HashMap<>();
        System.out.println("user.getUsername() = " + user.getUsername());
        System.out.println("user.getPassword() = " + user.getPassword());

        try {
            User userLogin = userService.login(user);
            // 用户存放负载信息,从而调用jwt生成相应的token信息
            HashMap<String, String> payload = new HashMap<>();
            payload.put("id", userLogin.getId());
            payload.put("username", userLogin.getUsername());

            // 生成token信息
            String token = JwtUtils.getToken(payload);

            map.put("code", "200");
            map.put("msg","登录成功");
            map.put("token", token);
        } catch (Exception e) {
            map.put("code", "500");
            map.put("msg", e.getMessage());
        }
        return map;
    }


    // 方式1:未使用拦截器,代码冗余
    // 登录之后执行相应的业务逻辑
	//    @PostMapping("test")
    public Map<String, Object> verify(String token){
        HashMap<String, Object> map = new HashMap<>();
        System.out.println("token = " + token);

        // 验证token
        try {
            JwtUtils.verify(token);
            // 获取token的返回值信息,类型为DecodedJWT
            DecodedJWT tokenInfo = JwtUtils.getTokenInfo(token);
            map.put("code", "200");
            map.put("msg","请求成功");
            return map;
        } catch (SignatureVerificationException e){
            map.put("msg", "无效签名");
        } catch (TokenExpiredException e){
            map.put("msg", "token已过期");
        } catch (AlgorithmMismatchException e){
            map.put("msg", "token算法不一致");
        } catch (Exception e) {
            map.put("msg", "token无效");
        }
        map.put("code", "500");
        return map;
    }


    // 方式2:配置了拦截器(只有登录之后才能进行使用该方法)
    @PostMapping("test")
    public Map<String, Object> test(HttpServletRequest request){
        HashMap<String, Object> map = new HashMap<>();
        // 从请求头中获取token信息
        String token = request.getHeader("token");
        DecodedJWT tokenInfo = JwtUtils.getTokenInfo(token);

        // 处理自己的业务逻辑
        map.put("code", "200");
        map.put("msg","请求成功");

        String username = tokenInfo.getClaim("username").asString();
        String id = tokenInfo.getClaim("id").asString();
        map.put("username", username);
        map.put("id", id);

        return map;
    }
}
           
  • 笔记整理来源于B站Up主: 编程不良人