SpringBoot整合JWT

目录

1、JWT简介

1.1、Session+Cookie认证

1.2、Token认证

1.3、对比

2、JWT组成

头部(Header)

载荷(Payload)

签名(Signature)

3、执行流程


1、JWT简介

JWT是JSON Web Token的缩写,是一个开放标准(RFC 7519),它定义了一种紧凑和自包含的方式,用于作为JSON对象在各方之间安全地传输信息。JWT可以用于身份验证和授权,因为它是数字签名的。

github: https://github.com/jwtk/jjwt

1.1、Session+Cookie认证

http 协议本身是无状态的协议,就意味着当有用户向系统使用账户名称和密码进行用户认证之后,下一次请求还要再一次用户认证才行。因为我们不能通过 http 协议知道是哪个用户发出的请求,所以如果要知道是哪个用户发出的请求,那就需要在服务器保存一份用户信息 ,这就需session。Session认证是一种基于服务器端的认证方式,它是通过在服务器端保存用户会话信息来实现的。当用户第一次访问服务器时,服务器会生成一个Session ID并将其存储在Cookie中。用户在后续的请求中将会带上这个Cookie,服务器可以通过这个Cookie来识别用户并获取用户的会话信息。

1.2、Token认证

token认证方式跟 session 的方式流程差不多,不同的地方在于保存的是一个 token 值到 redis,token 一般是一串随机的字符(比如UUID),value 一般是用户ID,并且设置一个过期时间。每次请求服务的时候带上 token 在请求头,后端接收到token 则根据 token 查一下 redis 是否存在,如果存在则表示用户已认证,如果 token 不存在则跳到登录界面让用户重新登录,登录成功后返回一个 token 值给客户端。

1.3、对比
  • 在传统的用户登录认证中,因为http是无状态的,所以都是采用session方式。用户登录成功,服务端会保证一个session,当然会给客户端一个sessionId,客户端会把sessionId保存在cookie中,每次请求都会携带这个sessionId。cookie+session这种模式通常是保存在内存中,而且服务从单服务到多服务会面临的session共享问题,随着用户量的增多,开销就会越大。而JWT不是这样的,只需要服务端生成token,客户端保存这个token,每次请求携带这个token,服务端认证解析就可。
  • JWT方式校验方式更加简单便捷化,无需通过redis缓存,而是直接根据token取出保存的用户信息,以及对token可用性校验,单点登录,验证token更为简单

2、JWT组成

JWT 由三部分组成:头部(Header)、**载荷(Payload)**和 签名(Signature),从官方的图解也可以看到如下:(JWT官网:JSON Web Tokens - jwt.io

头部(Header)

头部(Header): JWT头部通常由两部分组成,第一部分是声明类型,例如JWT,第二部分是声明所使用的算法,例如HMAC SHA256或者RSA等。 最后使用 Base64 URL 算法将上述 JSON 对象转换为字符串保存 。

复制代码
{
    "alg": "HS256", // 签名算法
    "typ": "JWT" // 令牌类型
}
载荷(Payload)

携带一些用户信息 和默认字段; 默认情况下 JWT 是未加密的,任何人都可以解读其内容,因此一些敏感信息不要存放于此,以防信息泄露。 JSON 对象也使用 Base64 URL 算法转换为字符串后保存,可以反编码。

复制代码
/**
ss (issuer):签发人/发行人
sub (subject):主题
aud (audience):用户
exp (expiration time):过期时间
nbf (Not Before):生效时间,在此之前是无效的
iat (Issued At):签发时间
jti (JWT ID):用于标识该 JWT
**/
// 自定义
{
    //默认字段
    "sub":"主题123",
    //自定义字段
    "name":"gcxy",
    "isAdmin":"true",
    "loginTime":"2023-8-22 10:00:03"
}
签名(Signature)

防止Token被篡改、确保安全性 ,签名过程:

1.将头部和载荷按照顺序拼接成一个字符串;

2.使用私钥对拼接后的字符串进行加密,得到一个签名;

3.将签名添加到头部中。

3、执行流程

jwt执行流程大致如下:

代码实例:

导入依赖:

复制代码
<!-- JWT依赖 -->
<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>3.4.0</version>
</dependency>

封装JWT工具类

复制代码
package com.gcxy.demo3.utils;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import org.springframework.stereotype.Component;

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



@Component
public class JwtUtil {

    private static String secret = "1234";


    /**
     * 生成token
     * @param subject
     * @return
     */
    public static String createToken(Map<String, String> subject){
        // 过期时间
        Date date = new Date(new Date().getTime() + 3600 * 1000);
        JWTCreator.Builder builder = JWT.create();
        subject.forEach((k, v) -> {
            builder.withClaim(k, v);
        });
        // 设置过期时间
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.DATE, 1); // 设置默认过期时间
        builder.withExpiresAt(calendar.getTime());  // 指定过期时间
        return builder.sign(Algorithm.HMAC256(secret));
    }

    /**
     * 校验token
     * @param token
     * @return
     */
    public static DecodedJWT verifyToken(String token) {
        return JWT.require(Algorithm.HMAC256(secret)).build().verify(token);
    }

    /**
     * 获取token信息
     * @param token
     * @return
     */
    public static Map<String, Claim> getTokenInfo(String token) {
        return JWT.require(Algorithm.HMAC256(secret)).build().verify(token).getClaims();
    }
}

创建拦截 器:

复制代码
package com.gcxy.demo3.interceptor;

import com.auth0.jwt.interfaces.DecodedJWT;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.gcxy.demo3.utils.JwtUtil;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

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


@Component
public class TokenInterceptor extends HandlerInterceptorAdapter {

    @Resource
    private JwtUtil jwtConfig;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        HashMap<String, String> map = new HashMap<>();
        // Token 验证
        String token = request.getHeader("Authorization");
        try {
            // 校验成功放行请求
            DecodedJWT verifyToken = jwtConfig.verifyToken(token);
            return true;
        } catch (Exception e) {
            map.put("msg", "token验证失败: " + e);
        }
       // 校验失败返回失败信息
        String json = new ObjectMapper().writeValueAsString(map);
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().write(json);
        return false;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    }

    @Override
    public void afterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    }
}

注册拦截器:

复制代码
package com.gcxy.demo3.config;

import com.gcxy.demo3.interceptor.TokenInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;


@Configuration
public class InterceptorConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 拦截所有请求,通过判断是否有 @LoginRequired 注解 决定是否需要登录
        registry.addInterceptor(new TokenInterceptor())
                .addPathPatterns("/**")
                .excludePathPatterns("/login")
        ;
    }
}

创建测试controller

复制代码
package com.gcxy.demo3.controller;

import com.auth0.jwt.interfaces.DecodedJWT;
import com.gcxy.demo3.utils.JwtUtil;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

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


@RestController
public class UserController {
    @PostMapping("/login")
    public Map<String, Object> login(String username, String password) {
        Map<String, Object> map = new HashMap<>();
        try{
            // 生成token
            Map<String, String> payload = new HashMap<>();
            payload.put("username", "zhangsan");
            payload.put("password", "123456");
            String token = JwtUtil.createToken(payload);
            map.put("code", 200);
            map.put("msg", "登录成功");
            map.put("token", token);
        }catch (Exception e){
            System.out.println(e.getMessage());
            map.put("code", 500);
            map.put("msg", "登录失败");
        }
      return map;
    }

    @GetMapping("/getUserInfo")
    public Map<String, Object> getUserInfo(HttpServletRequest request) {
        Map<String, Object> map = new HashMap<>();
        String token = request.getHeader("Authorization");
        DecodedJWT tokenInfo = JwtUtil.verifyToken(token);
        String username = tokenInfo.getClaim("username").asString();
        String password = tokenInfo.getClaim("password").asString();
        System.out.println("username = " + username);
        System.out.println("password = " + password);
        map.put("code", 200);
        map.put("msg", "请求成功");
        return map;
    }
}

测试结果:

练习拓展:

结合Redis实现token验证

相关推荐
追逐时光者4 分钟前
MongoDB从入门到实战之MongoDB简介
后端·mongodb
格子先生Lab13 分钟前
Java反射机制深度解析与应用案例
java·开发语言·python·反射
Huazie1 小时前
在WSL2 Ubuntu中部署FastDFS服务的完整指南
服务器·后端·ubuntu
Java知识库1 小时前
Java BIO、NIO、AIO、Netty面试题(已整理全套PDF版本)
java·开发语言·jvm·面试·程序员
西瓜本瓜@1 小时前
在 Android 中实现通话录音
android·java·开发语言·学习·github·android-studio
行者无疆xcc2 小时前
【Django】设置让局域网内的人访问
后端·python·django
嘵奇2 小时前
基于Spring Boot实现文件秒传的完整方案
java·spring boot·后端
Value_Think_Power2 小时前
azure 一个 pod 内有多个 container ,这些container 可以 共享一块磁盘吗
后端
李菠菜2 小时前
优化Centos关闭SELinux/Swap及资源限制调整
linux·后端·centos
V功夫兔2 小时前
Spring_MVC 高级特性详解与实战应用
java·经验分享·笔记·spring