Springboot + JWT 的 Token 登录验证

news2024/11/15 11:05:26

目录

        项目结构

一、 引入依赖

二、自定义Auth认证注解

三、 编写登录拦截器

四、定义跨域拦截器

五、 定义全局异常处理器

六、定义工具类

1. 统一错误状态码

2.统一响应类

3.Token工具类

七、 编写实体类

八、 定义控制器

1.定义登录控制器类

2 定义报错处理器

3 定义测试控制器

九、 配置类


项目结构

一、 引入依赖

         <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>4.4.0</version>
        </dependency>
​
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.7.6</version>
        </dependency>
​
        <dependency>
            <groupId>commons-lang</groupId>
            <artifactId>commons-lang</artifactId>
            <version>2.6</version>
        </dependency>
​
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>16.0.1</version>
        </dependency>
​
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.1.0</version>
        </dependency>
​

二、自定义Auth认证注解

在需要认证的方法上添加该注解

package com.dev.jwt;
​
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
​
/**
 * 定义一个名为Auth的注解,用于标注需要授权的方法或类型。
 *
 * @Target 指定该注解可以应用于方法或类型的元素上。
 * @Retention 指定该注解在运行时是可见的。
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Auth {
​
    /**
     * 是否需要授权的属性。
     * 默认值为true,表示该方法或类型需要授权才能访问。
     * 设置为false时表示不需要授权。
     */
    boolean require() default true;
}

三、 编写登录拦截器

  • 通过识别是否在接口上添加@Auth注解来确定是否需要登录才能访问。

  • 同时这里需要注意只拦截HandlerMethod类型,同时还要考虑放行BasicErrorController,因为基本的报错在这个控制器中,如果不放行,那么会看不到报错信息。

package com.dev.jwt;
​
​
import org.apache.commons.lang.StringUtils;
import org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
​
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
​
/**
 * 登录拦截器类,实现 HandlerInterceptor 接口,用于拦截请求并进行登录验证。
 */
public class LoginInterceptor implements HandlerInterceptor {
​
    /**
     * Spring MVC 的 HandlerInterceptor 接口的实现类,用于处理请求的前置拦截。
     * 主要功能是进行权限验证,如果请求需要认证,且认证失败,则重定向到错误页面。
     *
     * @param request  HTTP 请求对象
     * @param response HTTP 响应对象
     * @param handler  处理请求的处理器对象,可能是 Controller 方法或其他类型的处理器
     * @return 是否继续处理请求,如果返回 false,则不会继续执行该请求的处理器方法
     * @throws Exception 如果处理过程中抛出异常
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 判断处理器是否为 HandlerMethod 类型,即是否为一个方法处理器
        if (handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            // 检查处理器方法所属的 bean 是否为 BasicErrorController 类型,如果是,则直接放行
            if (handlerMethod.getBean() instanceof BasicErrorController) {
                return true;
            }
            // 获取处理器方法上的 Auth 注解,用于判断该方法是否需要认证
            Auth auth = handlerMethod.getMethod().getAnnotation(Auth.class);
            // 如果存在 Auth 注解且要求认证
            if (auth != null && auth.require()) {
                // 从请求头中获取 token,用于认证
                String token = request.getHeader("token");
                // 如果 token 不为空且通过验证
                if (StringUtils.isNotBlank(token)) {
                    if (TokenUtil.verifyToken(token)) {
                        // 认证成功,继续处理请求
                        return true;
                    } else {
                        // 认证失败,重定向到 token 错误页面
                        request.getRequestDispatcher("/error/tokenError").forward(request, response);
                    }
                } else {
                    // 未提供 token,重定向到 token 缺失页面
                    request.getRequestDispatcher("/error/token").forward(request, response);
                }
            } else {
                // 不需要认证,继续处理请求
                return true;
            }
        } else {
            // 非 HandlerMethod 类型的处理器,直接放行
            return true;
        }
        // 如果执行到此处,表示拦截处理失败,不应继续处理请求
        return false;
    }
​
}

四、定义跨域拦截器

这里是做前后端分离需要做的步骤,解决跨域的方式有好几种,这里使用拦截器的方式解决跨域问题。

package com.dev.jwt;
​
import org.springframework.web.servlet.HandlerInterceptor;
​
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
​
/**
 * 跨域拦截器类,用于处理跨域请求。
 * 实现HandlerInterceptor接口,提供预处理请求的能力。
 */
public class CrossInterceptorHandler implements HandlerInterceptor {
    /**
     * 在请求处理之前进行预处理。
     * 主要用于设置响应头,以允许来自特定源的跨域请求。
     *
     * @param request  HttpServletRequest对象,代表客户端的请求。
     * @param response HttpServletResponse对象,用于向客户端发送响应。
     * @param handler  将要处理请求的具体处理器对象。
     * @return 返回true表示继续处理请求,返回false表示中断请求处理。
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        // 设置响应头,允许来自https://example.com的跨域请求
        response.setHeader("Access-Control-Allow-Origin", "https://example.com");
        // 允许浏览器发送cookie
        response.setHeader("Access-Control-Allow-Credentials", "true");
        // 允许的HTTP方法
        response.setHeader("Access-Control-Allow-Methods", "POST, GET , PUT , OPTIONS");
        // 预检请求的缓存时间
        response.setHeader("Access-Control-Max-Age", "3600");
        // 允许的请求头
        response.setHeader("Access-Control-Allow-Headers", "x-requested-with,accept,authorization,content-type");
        // 返回true,表示继续后续的处理流程
        return true;
    }
}
​

五、 定义全局异常处理器

为了项目的完整性,将这些常规的内容写上去。

package com.dev.jwt.handler;

import com.auth0.jwt.exceptions.TokenExpiredException;
import com.dev.jwt.model.emum.ResponseEnum;
import com.dev.jwt.utils.R;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

/**
 * 全局异常处理器,用于捕获并处理应用程序中抛出的特定异常。
 * 使用@RestControllerAdvice注解,标识这个类是一个处理全局异常的控制器顾问。
 */
@RestControllerAdvice
public class GlobalExceptionHandler {

    /**
     * 日志记录器,用于记录异常信息。
     */
    public final Logger logger = LoggerFactory.getLogger(this.getClass());

    /**
     * 处理Token过期异常。
     * 当用户持有的Token过期时,此异常会被抛出。
     *
     * @param e Token过期异常的具体实例,包含详细的错误信息。
     * @return 返回一个封装了错误信息的响应对象。
     */
    @ExceptionHandler(TokenExpiredException.class)
    public R<?> handleTokenExpiredException(TokenExpiredException e) {
        // 记录token过期的错误信息
        logger.error("token 已过期");
        logger.error(e.getMessage());
        // 返回一个表示Token过期的错误响应
        return R.error(ResponseEnum.TOKEN_EX);
    }
}

六、定义工具类

1. 统一错误状态码

编写一个枚举类,统一项目的报错状态码

@AllArgsConstructor
@Getter
public enum ResponseEnum {
 
    SUCCESS(200, "操作成功"),
 
    FAIL(300,"获取数据失败"),
 
    USER_EX(301,"用户不存在,请重新登录"),
 
    ERROR(302,"错误请求"),
 
    USERNAME_PASSWORD_ERROR(303,"用户名或密码错误"),
 
    NO_TOKEN(400,"无token,请重新登录"),
 
    TOKEN_VERIFY_ERROR(401,"token验证失败,请重新登录"),
 
    TOKEN_EX(402,"token已过期");
 
    private final Integer code;
 
    private final String msg;
 
    public static ResponseEnum getResultCode(Integer code){
        for (ResponseEnum value : ResponseEnum.values()) {
            if (code.equals(value.getCode())){
                return value;
            }
        }
        return ResponseEnum.ERROR;
    }
}

2.统一响应类

@Data
public class R<T> implements Serializable {
 
    private static final long serialVersionUID = 56665257244236049L;
 
    private Integer code;
 
    private String message;
 
    private T data;
 
    private R() {
    }
 
    public static <T> R<T> ok(T data) {
        R<T> response = new R<>();
        response.setCode(ResponseEnum.SUCCESS.getCode());
        response.setMessage(ResponseEnum.SUCCESS.getMsg());
        response.setData(data);
        return response;
    }
 
    public static <T> R<T> error(Integer errCode, String errMessage) {
        R<T> response = new R<>();
        response.setCode(errCode);
        response.setMessage(errMessage);
        return response;
    }
 
    public static <T> R<T> error(ResponseEnum responseEnum) {
        R<T> response = new R<>();
        response.setCode(responseEnum.getCode());
        response.setMessage(responseEnum.getMsg());
        return response;
    }
}
​

3.Token工具类

通过TokenUtil可以生成token和验证token是否正确。

package com.dev.jwt;
​
import cn.hutool.core.date.DateUtil;
import cn.hutool.json.JSONObject;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
 
import java.util.Date;
 
​
/**
 * Token工具类,用于生成和验证JWT令牌。
 */
public class TokenUtil {
​
    /**
     * JWT加密的密钥。
     */
    private final static String ENCRYPT_KEY = "abc123";
​
    /**
     * JWT令牌的过期时间,单位为分钟。
     */
    private final static int EXPIRE_TIME = 1;
​
    /**
     * JWT令牌的发行者。
     */
    private static final String ISSUER = "zhangsan";
​
    /**
     * 生成JWT令牌。
     *
     * @param json 令牌中承载的信息,以JSONObject形式提供。
     * @return 生成的JWT令牌字符串。
     */
    public static String createToken(JSONObject json) {
        // 使用JWT创建一个令牌
        return JWT.create()
                // 设置令牌的主题,即json对象转换后的字符串 不要把密码封装进去,不安全
                .withSubject(json.toString())
                // 设置令牌的发行者
                .withIssuer(ISSUER)
                // 设置令牌的过期时间,以当前时间为基础加上设定的过期时间
                .withExpiresAt(DateUtil.offsetMinute(new Date(), EXPIRE_TIME))
                // 设置自定义的声明,这里以"test"为键,"123"为值
                .withClaim("test", "123")
                // 使用HMAC256算法对令牌进行签名加密
                .sign(Algorithm.HMAC256(ENCRYPT_KEY));
    }
​
    /**
     * 验证JWT令牌的有效性。
     *
     * @param token 待验证的JWT令牌字符串。
     * @return 如果令牌有效,则返回true;否则返回false。
     */
    public static boolean verifyToken(String token) {
        try {
            // 创建一个 JWT 验证器,使用 HMAC256 算法,密钥为 ENCRYPT_KEY,发行者为 ISSUER
            JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(ENCRYPT_KEY))
                    .withIssuer(ISSUER)
                    .build();
​
            // 对令牌进行验证
            jwtVerifier.verify(token);
            // 如果验证成功,返回 true
            return true;
        } catch (Exception e) {
            // 验证失败,打印异常信息,并返回false
            e.printStackTrace();
            return false;
        }
    }
}

七、 编写实体类

这里为了简单,并没有与数据库交互。

@Data
public class User {
    private String userName;
    private String password;
    private String token;
}

八、 定义控制器

1.定义登录控制器类

@RestController
@RequestMapping("/user")
public class LoginController {
    @PostMapping("/login")
    public R<User> login(String userName, String password) {
        if (StringUtils.isNotBlank(userName) && StringUtils.isNotBlank(password)) {
            if ("张三".equals(userName) && "123456".equals(password)) {
                User user = new User();
                JSONObject json = JSONUtil.createObj()
                        .put("name", "zhangsan");
                String token = TokenUtil.createToken(json);
                user.setToken(token);
                return R.ok(user);
            }
        }
        return R.error(ResponseEnum.USERNAME_PASSWORD_ERROR);
    }
}

2 定义报错处理器

 
@RestController
@RequestMapping("/error")
public class ErrorController {
 
    @PostMapping("/token")
    public R<?> token() {
        return R.error(ResponseEnum.NO_TOKEN);
    }
 
    @PostMapping("/tokenError")
    public R<?> tokenError() {
        return R.error(ResponseEnum.TOKEN_VERIFY_ERROR);
    }
}

3 定义测试控制器


@RestController
@RequestMapping("/test")
public class TestController {
 
    @Auth
    @PostMapping("/hello")
    public R<?> hello() {
        return R.ok("登录成功");
    }
 
    @PostMapping("/hi")
    public R<?> hi() {
        return R.ok("登录成功");
    }
}

九、 配置类

将自定义的两个拦截器注册进去。

package com.dev.jwt.config;

import com.dev.jwt.interceptor.CrossInterceptorHandler;
import com.dev.jwt.interceptor.LoginInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * WebMvcConfigurer的实现类,用于自定义Spring MVC的配置,例如拦截器的设置。
 */
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    /**
     * 添加拦截器到应用中。
     *
     * @param registry 拦截器注册表,用于注册和管理拦截器。
     *
     * 本方法中,首先添加了一个处理跨域请求的拦截器CrossInterceptorHandler,应用到所有路径。
     * 接着添加了一个登录拦截器LoginInterceptor,应用到所有路径,但排除了/user/login和/error/**路径。
     * 这样配置是为了确保登录页面和错误页面不受登录拦截器的影响。
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new CrossInterceptorHandler()).addPathPatterns(new String[] {"/**"});
        registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/**").excludePathPatterns("/user/login", "/error/**");
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1926890.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

vscode编译环境配置-golang

1. 支持跳转 如果单测函数上方不显示run test | debug test&#xff0c;需要安装Code Debugger&#xff08;因为以前的go Test Explorer不再被维护了&#xff09; 2. 单测 指定单个用例测试 go test -v run TestXXXdlv 调试 需要安装匹配的go版本和delve版本&#xff08;如…

6.S081的Lab学习——Lab11: Network

文章目录 前言Network提示&#xff1a;实现e1000_transmit的一些提示&#xff1a;实现e1000_recv的一些提示&#xff1a; 解析 总结 前言 一个本硕双非的小菜鸡&#xff0c;备战24年秋招。打算尝试6.S081&#xff0c;将它的Lab逐一实现&#xff0c;并记录期间心酸历程。 代码下…

Go-知识测试-性能测试分析工具-benchstat

Go-知识测试-性能测试分析工具-benchstat benchmark 结果benchstat确认 benchstat 已安装确认 GOPATH 和 GOBIN将 $GOPATH/bin 添加到 PATH验证安装检查安装路径 使用 传送门&#xff1a;Go-知识测试-性能测试 benchmark 结果 benchmark 测试是实际项目中经常使用的测试方法&a…

飞凌全志T527开发板U-Boot添加自定义菜单

昨日&#xff0c;终于收到了心心念念的飞凌OK-T527开发板&#xff0c;板子很漂亮&#xff0c;外设丰富&#xff0c;性能强悍&#xff0c;T527创新性地使用了RISC-V架构的协处理器&#xff0c;后期值得研究一下异核的使用&#xff1a; 有趣的是&#xff0c;板子上电&#xff0c;…

智能制造 v3.13.16 发布,ERP、MES 更新

智能制造一体化管理系统 [SpringBoot2 - 快速开发平台]&#xff0c;适用于制造业、建筑业、汽车行业、互联网、教育、政府机关等机构的管理。包含文件在线操作、工作日志、多班次考勤、CRM、ERP 进销存、项目管理、EHR、拖拽式生成问卷、日程、笔记、工作计划、行政办公、薪资模…

0.单片机工作原理

文章目录 最小系统 单片机芯片 时钟电路 复位电路 电源 最小系统 单片机芯片 本次51单片机的芯片为&#xff1a;STC89C52 Flash(闪存)程序存储器&#xff1a;存储程序的空间 SRAM&#xff1a;数据存储器&#xff0c;可用于存放程序执行的中间结果和过程数据 DPTR&#xff1a;…

某客户报表系统Oracle数据库挂起问题分析处理

某客户报表系统Oracle数据库挂起问题分析处理 一、概要 某客户报表系统Oracle数据库在3月5号、6号均出现一节点实例短暂挂起现象&#xff0c;挂起现象有两种&#xff0c;第一是普通用户不能登录数据库&#xff0c;第二是sys用户可以登录数据库&#xff0c;但是做简单的select查…

C判断一个点在三角形上

背景 鼠标操作时&#xff0c;经常要判断是否命中显示控件&#xff0c;特开发此算法快速判断。 原理 三角形三等分点定理是指在任意三角形ABC中&#xff0c;可以找到三个点D、E和F&#xff0c;使得线段AD、BE和CF均等分三角形ABC。 这意味着三个等分点分别位于三个边界上&…

数据湖表格式 Hudi/Iceberg/DeltaLake/Paimon TPCDS 性能对比(Spark 引擎)

当前&#xff0c;业界流行的集中数据湖表格式 Hudi/Iceberg/DeltaLake&#xff0c;和最近出现并且在国内比较火的 Paimon。我们现在看到的很多是针对流处理场景的读写性能测试&#xff0c;那么本篇文章我们将回归到大数据最基础的场景&#xff0c;对海量数据的批处理查询。本文…

具身大模型研究综述

源自&#xff1a;哈工大SCIR 作者&#xff1a;陈一帆&#xff0c;张宇驰&#xff0c;孙楚芮&#xff0c;冯怀绪&#xff0c;宋浩&#xff0c;王寄哲 指导老师&#xff1a;张伟男 注&#xff1a;若出现无法显示完全的情况&#xff0c;可 V 搜索“人工智能技术与咨询”查看完整…

什么叫图像的双边滤波,并附利用OpenCV和MATLB实现双边滤波的代码

双边滤波&#xff08;Bilateral Filtering&#xff09;是一种在图像处理中常用的非线性滤波技术&#xff0c;主要用于去噪和保边。它在空间域和像素值域上同时进行加权&#xff0c;既考虑了像素之间的空间距离&#xff0c;也考虑了像素值之间的相似度&#xff0c;从而能够有效地…

赛氪网荣获2024年中国高校计算机教育大会合作伙伴荣誉

2024年7月13日&#xff0c;在黑龙江哈尔滨召开的“2024年中国高校计算机教育大会&#xff08;CCEC2024&#xff09;”&#xff0c;环球赛乐&#xff08;北京&#xff09;科技有限公司(以下简称”赛氪网“)凭借其在高等教育与科技创新领域的卓越贡献&#xff0c;荣幸地获得了本次…

SpringBoot详细解析

1.什么是springboot springboot也是spring公司开发的一款框架。为了简化spring项目的初始化搭建的。那么spring对应springboot有什么缺点呢&#xff1f; spring项目搭建的缺点: 配置麻烦依赖tomcat启动慢 2.springboot的特点 自动配置 Spring Boot的自动配置是一个运行时&…

Docker之在外执行docker内部命令(十一)

简介: CSDN博客专家,专注Android/Linux系统,分享多mic语音方案、音视频、编解码等技术,与大家一起成长! 优质专栏:Audio工程师进阶系列【原创干货持续更新中……】🚀 优质专栏:多媒体系统工程师系列【原创干货持续更新中……】🚀 优质视频课程:AAOS车载系统+AOSP…

react + redux 状态管理操作

目录 1 概念2 Redux 安装3 创建子模块并导入4 中间件为 react 注入 store5 在组件中使用 store 数据6 修改 store 数据7 提交 action 传参8 异步状态操作9 redux 调试工具 1 概念 Redux 是一个全局状态管理的 JS 库 2 Redux 安装 在react中使用redux&#xff0c;官方要求安…

「网络通信」HTTP 协议

HTTP &#x1f349;简介&#x1f349;抓包工具&#x1f349;报文结构&#x1f34c;请求&#x1f34c;响应&#x1f34c;URL&#x1f95d;URL encode &#x1f34c;方法&#x1f34c;报文字段&#x1f95d;Host&#x1f95d;Content-Length & Content-Type&#x1f95d;User…

Ubuntu使用K3S一分钟快速搭建K8S集群

快速入门指南 | Rancher文档 准备3台服务器 Master节点安装脚本# K3s 提供了一个安装脚本&#xff0c;可以方便的在 systemd 或 openrc 的系统上将其作为服务安装。这个脚本可以在 https://get.k3s.io 获得。要使用这种方法安装 K3s&#xff0c;只需运行以下命令&#xff1a;…

[超级详细系列]ubuntu22.04配置深度学习环境(显卡驱动+CUDA+cuDNN+Pytorch)--[3]安装cuDNN与Pytorch

本次配置过程的三篇博文分享分别为为&#xff1a; [超级详细系列]ubuntu22.04配置深度学习环境(显卡驱动CUDAcuDNNPytorch)--[1]安装显卡驱动 [超级详细系列]ubuntu22.04配置深度学习环境(显卡驱动CUDAcuDNNPytorch)--[2]安装Anaconda与CUDA [超级详细系列]ubuntu22.04配置深…

sql server 练习题5

课后作业 在homework库下执行 作业1&#xff1a; 案例&#xff1a;根据用户分数划分等级。小于60分为不及格&#xff0c;[60,80)为及格&#xff0c;[80,90)为良好&#xff0c;大于等于90分以上为优秀。 建表语句&#xff1a; CREATE TABLE Grades ( ID INT PRIMARY KEY, Name V…

数电基础 - 时序逻辑电路

目录 一. 简介 二. 分析方法 三. 常用的时序逻辑电路 四. 冒险现象 五. 总结 一. 简介 时序逻辑电路是数字电路的重要组成部分&#xff0c;与组合逻辑电路不同&#xff0c;它在任何时刻的输出不仅取决于当时的输入信号&#xff0c;还与电路原来的状态有关。 时序逻辑电路…