【登录认证】JWT令牌

发布于:2025-05-16 ⋅ 阅读:(18) ⋅ 点赞:(0)

一、概述

  • JWT全称:**JSON Web Token **(https://jwt.io/)
  • 定义了一种简洁的、自包含的格式,用于通信双方以json数据格式安全的传输信息。
  • 组成:
    ①第一部分:Header(头),记录令牌类型、签名算法等。例如: (“alg”:" HS256"," type":“JWT”)
    ②第二部分: Payload(有效载荷),携带一些自定义信息、默认信息等。例如: {“id”.“1”,“username”:“Tom”}
    ③第三部分: Signature(签名),防止Token被篡改、确保安全性。将header、payload,并加入指定秘钥,通过指定签名算法计算而来。

在这里插入图片描述

Base64:是一种基于64个可打印字符(A-Z a-z 0-9 +/)来表示二进制数据的编码方式。

二、令牌生成

1、引入坐标

    <!--java-jwt令牌-->
    <dependency>
      <groupId>com.auth0</groupId>
      <artifactId>java-jwt</artifactId>
      <version>4.4.0</version>
    </dependency>

2、调用API生成令牌

    @Test
    public void testGen(){
        HashMap<String, Object> claims = new HashMap<>();
        claims.put("id",1);
        claims.put("username","张三");
        //生成jwt的
        String token = JWT.create()
                .withClaim("user", claims)//通过添加载荷的方式将用户信息添加进去
                .withExpiresAt(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 12)) //添加过期时间 12h
                .sign(Algorithm.HMAC256("arata"));//指定加密算法 加密秘钥
        System.out.println(token);
    }

在这里插入图片描述

3、验证生成的令牌

    @Test
    public void testParse(){
        //定义字符串模拟生成的token令牌
        String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" +
                ".eyJ1c2VyIjp7ImlkIjoxLCJ1c2VybmFtZSI6IuW8oOS4iSJ9LCJleHAiOjE3NDcxNjc1MjN9" +
                ".KejB84CGQw5r602-wUk-Ew0YE2nLSaLvLHhY2S47GCw";
        JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("arata")).build();//申请JWt验证器(秘钥与算法和加密时保持一致)
        DecodedJWT decodedJWT = jwtVerifier.verify(token);//验证token,生成一个解析后的jwt对象
        Map<String, Claim> claims = decodedJWT.getClaims();//可以从中获取所有载荷
        System.out.println(claims.get("user")); //获取键名为user的载荷,获取成功的话代表认证成功
//如果篡改了头部和载荷部分的数据,那么验证失败工
//如果秘钥改了,验证失败
//token过期
    }

在这里插入图片描述

三、封装为JWT工具类

package org.example.utils;

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

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

public class JwtUtil {

    private static final String KEY = "itheima";
	
	//接收业务数据,生成token并返回
    public static String genToken(Map<String, Object> claims) {
        return JWT.create()
                .withClaim("claims", claims)
                .withExpiresAt(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 12))
                .sign(Algorithm.HMAC256(KEY));
    }

	//接收token,验证token,并返回业务数据
    public static Map<String, Object> parseToken(String token) {
        return JWT.require(Algorithm.HMAC256(KEY))
                .build()
                .verify(token)
                .getClaim("claims")
                .asMap();
    }

}

四、集成JWT

    @PostMapping("/login")
    public Result<String> login(@Pattern(regexp = "^\\S{5,16}$")String username,@Pattern(regexp = "^\\S{5,16}$")String password){
        //根据用户名查询user
        User user = userService.findByUserName(username);
        //判断是否查询到
        if (user == null){
            return Result.error("用户不存在");
        }

        //判断密码是否正确
        String md5String = Md5Util.getMD5String(password);
        if(md5String.equals(user.getPassword())){
            //登陆成功
            //生成token
            Map<String, Object> claims = new HashMap<>();
            claims.put("id",user.getId());
            claims.put("username",user.getUsername());
            String token = JwtUtil.genToken(claims);
            return Result.success(token);
        }
        return Result.error("密码错误");
    }

使用:

    @GetMapping("/list")
    public Result<String> list(@RequestHeader(name = "Authorization") String token, HttpServletResponse response){
        //在提供服务之前先验证token
        //用户登录成功后,系统会自动下发WT令牌,
        // 然后在后续的每次请求中,浏览器都需要在请求头header中携带到服务端,
        // 请求头的名称为Authorization,值为登录时下发的JWI令牌。
        try {
            Map<String, Object> claims = JwtUtil.parseToken(token);
            return Result.success("所有的文章数据列表");
        } catch (Exception e) {
            //没有验证成功的话会报错,所以这里我们直接抛出异常
            response.setStatus(401);
            throw new RuntimeException(e);
        }

    }

五、优化(拦截器)

在这里插入图片描述
在程序中会存在很多controller,而每个controller会有很多接口,都要校验吗?使用拦截器
在这里插入图片描述

1、重写方法编写拦截器

在这里插入图片描述

package org.example.interceptors;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.example.pojo.Result;
import org.example.utils.JwtUtil;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import java.util.Map;

@Component
public class LoginInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //拦截下来之后先进行令牌验证
        //获取token
        String token = request.getHeader("Authorization");
        //解析Token
        try {
            Map<String, Object> claims = JwtUtil.parseToken(token);
            //验证成功,放行
            return true;
        } catch (Exception e) {
            //没有验证成功的话会报错,所以这里我们直接抛出异常
            response.setStatus(401);
            //验证不成功,不放行
            return false;
        }
    }
}

2、在配置类中注册拦截器(添加拦截器使其生效)

在这里插入图片描述

package org.example.config;

import org.example.interceptors.LoginInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
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 WebConfig implements WebMvcConfigurer {
    @Autowired
    private LoginInterceptor loginInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //登录接口和注册接口不拦截
        registry.addInterceptor(loginInterceptor).excludePathPatterns("/user/login","/user/register");
    }
}


网站公告

今日签到

点亮在社区的每一天
去签到