Springboot + JWT 的 Token 登录验证

目录

        项目结构

一、 引入依赖

二、自定义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 如果处理过程中抛出异常*/@Overridepublic 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表示中断请求处理。*/@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {// 设置响应头,允许来自https://example.com的跨域请求response.setHeader("Access-Control-Allow-Origin", "https://example.com");// 允许浏览器发送cookieresponse.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,发行者为 ISSUERJWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(ENCRYPT_KEY)).withIssuer(ISSUER).build();
​// 对令牌进行验证jwtVerifier.verify(token);// 如果验证成功,返回 truereturn true;} catch (Exception e) {// 验证失败,打印异常信息,并返回falsee.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/**路径。* 这样配置是为了确保登录页面和错误页面不受登录拦截器的影响。*/@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new CrossInterceptorHandler()).addPathPatterns(new String[] {"/**"});registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/**").excludePathPatterns("/user/login", "/error/**");}
}

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

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

相关文章

【单片机毕业设计选题24067】-基于STM32的智能衣控制系统

系统功能: 系统上电后OLED显示“欢迎使用智能衣服系统请稍后”两秒钟后进入正常页面显示&#xff0c; 第一行显示系统状态信息 第二行显示获取到体温值 第三行显示设定的高体温阈值和低体温阈值 第四行显示获取到心率和血氧值 短按B3按键调高心率设定高阈值 短按B4按键调…

【学术会议征稿】2024年第三届信息学,网络与计算技术国际学术会议(ICINC2024)

2024年第三届信息学&#xff0c;网络与计算技术国际学术会议(ICINC2024) 2024 3rd International Conference on Informatics,Networking and Computing (ICINC 2024) 2024年第三届信息学&#xff0c;网络与计算技术国际学术会议(ICINC2024)将于2024年10月25-27日于中国郑州召…

Vue中使用mind-map实现在线思维导图

概述 在前面的文章Vue中实现在线画流程图实现中介绍了流程图的在线绘制&#xff0c;在本文&#xff0c;给大家分享一下基于mind-map实现在线的思维导图&#xff0c;并实现&#xff1a;1. 导图导出为图片&#xff1b;2. 打开xmind文件。 实现效果 实现 1. mind-map简介 simp…

使用Druid完成SpringBoot集成MySql、DM(达梦)数据库数据源操作

业务背景 一个方法里&#xff0c;对A数据源需要进行查询&#xff0c;B数据源进行插入(切面插入访问数据&#xff0c;日志)。 详细业务是查询业务数据&#xff0c;同时主数据库记录访问日志。 第一步依赖先行 <dependency><groupId>mysql</groupId><art…

正确选择指挥中心操作台厂家的因素

在当今数字化和信息化快速发展的时代&#xff0c;指挥中心操作台在各类机构和企业的运营中发挥着至关重要的作用。然而&#xff0c;选择一个合适的指挥中心操作台厂家并非易事。以下是一些选择指挥中心操作台厂家的考虑因素。 一、产品质量与工艺 要考察厂家的产品质量和工艺水…

C语言中的指针:掌握内存的钥匙

C语言中的指针&#xff1a;掌握内存的钥匙 引言 C语言是一种结构化编程语言&#xff0c;它提供了对硬件底层的直接访问&#xff0c;其中最强大的特性之一就是指针。指针允许程序员直接操作内存地址&#xff0c;这对于理解程序的内部工作原理以及优化代码性能至关重要。本文将深…

解决宝塔Spring Boot项目获取不到环境变量的问题

问题描述 在使用宝塔面板管理Spring Boot项目时&#xff0c;可能会遇到代码无法获取 /etc/profile 文件中设置的Linux环境变量的问题。虽然在SSH终端中可以正常获取&#xff0c;但在通过宝塔面板启动的Spring Boot项目中&#xff0c;环境变量却无法被读取。 解决方案&#xf…

量化交易研究报告#13

核心公式 通过对 HS300 相对强弱指标值变化规律的梳理&#xff0c;我们制定如下的交易法则&#xff1a; &#xff08;1&#xff09;计算指标的 20 日均值变化&#xff0c;以及 20 日标准差&#xff1b; &#xff08;2&#xff09;20日均值1倍标准差作为上阈值&#xff0c;20…

【Chapter 3: Creating Minimal API Applications】

Chapter 1: Foundations of Framework Learning and Practical Strategies Chapter 2: An Introduction to ASP.NET Core in Layman‘s Terms Chapter 3: Creating Minimal API Applications 1. Overview of Minimal APIs In the vast landscape of ASP.NET Core, “Minima…

Mysql表的三范式、事务和查询

数据表的三范式 在制定数据表,需要遵循的制表规范:第一范式(1NF),第二范式(2NF),第三范式(3NF) 第一范式 属性不可分割,每一个属性(每一个单元格)都是不可再分的原子,也就是说数据表中的每一个字段必须是单独一列的,不能出现还可以再拆分的情况,也可以说成是…

数电基础 - 半导体存储

目录 一. 简介 一. 只读存储器 二. 可编程只读存储器 三. 可擦除的可编程只读存储器 四. 随机存储器 五. 存储器容量的扩展 六. 总结 一. 简介 半导体存储是数字电路中用于存储数据的重要组成部分。 半导体存储器主要分为两大类&#xff1a;随机存取存储器&#xff0…

【人工智能】Transformers之Pipeline(二):自动语音识别(automatic-speech-recognition)

​​​​​​​ 目录 一、引言 二、自动语音识别&#xff08;automatic-speech-recognition&#xff09; 2.1 概述 2.2 技术原理 2.2.1 whisper模型 2.2.2 Wav2vec 2.0模型 2.3 pipeline参数 2.3.1 pipeline对象实例化参数​​​​​​​ 2.3.2 pipeline对象使用参数…

16001.WSL2 ubuntu20.04 编译安装 vsomeip

文章目录 1 vsomeip 编译安装1.1 vsomeip的安装1.2 编译提示错误1.3 编译hello_world示例1.4 运行服务器端 1 vsomeip 编译安装 1.1 vsomeip的安装 参考博文 https://blog.csdn.net/peterwanye/article/details/128386539 1.2 编译提示错误 ubuntu1-BJ-EE1000042:~/opt/vso…

Flask启动5000端口后关不掉了?

事情是这样的&#xff1a; 使用python app.py启动flask应用后&#xff0c;又启动了另一个flask测试应用&#xff0c;也能启动成功&#xff0c;也没有报设么端口冲突&#xff0c;关闭黑窗口后&#xff0c;访问还是有守护进程在运行&#xff0c; 为什么我知道5000还在运行&#…

深入理解缓冲区:提升程序性能的关键

深入理解缓冲区&#xff1a;提升程序性能的关键 1、什么是缓冲区&#xff1f;2、缓冲区的作用3、缓冲区在Java中的应用4、如何操作缓冲区5、总结 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; 缓冲区&#xff08;Buffer&#xff09;是一种…

2024Datawhale AI夏令营---基于术语词典干预的机器翻译挑战赛--学习笔记

#Datawhale #NLP 1.背景介绍&#xff1a; 机器翻译&#xff08;Machine Translation&#xff0c;简称MT&#xff09;是自然语言处理领域的一个重要分支&#xff0c;其目标是将一种语言的文本自动转换为另一种语言的文本。机器翻译的发展可以追溯到20世纪50年代&#xff0c;经历…

07:串口通信二

串口编程 1、与波特率之相关的寄存器2、PCON寄存器3、SCON寄存器4、配置的代码分析5、向PC发送一段字符串6、PC机向单片机发送字符控制LED1灯的亮灭 1、与波特率之相关的寄存器 如图&#xff0c;与串口通信相关的寄存器主要是SCON和PCON寄存器。 2、PCON寄存器 SMOD&#xff1…

信息素养大赛-2024-算法创意实践挑战复赛-小学组

文章目录 一、前言二、问题问题&#xff1a;玫瑰花地的面积问题&#xff1a;判断三角形问题&#xff1a;汤姆的日记问题&#xff1a;正方形的数量问题&#xff1a;字符操作问题&#xff1a;猴子摘桃 三、感谢 一、前言 本章节主要对2024年信息素养大赛算法创意实践挑战复赛小学…

通用图形处理器设计GPGPU基础与架构(三)

一、前言 前两篇已经介绍了 GPGPU 的背景 和 GPGPU 的编程模型相关的内容&#xff0c;本文将在 SIMT 计算模型的基础上&#xff0c;介绍 GPGPU 控制核心架构和微体系结构的设计。 二、CPU-GPGPU 异构计算系统 一个由 CPU 和 GPGPU 构成的异构计算平台如下图所示&#xff0c;GP…

开源AI智能名片S2B2C商城小程序:重塑营销一体化新生态,引领未来商业潮流!

在互联网时代&#xff0c;尤其是移动互联网的迅猛发展下&#xff0c;顾客的认知与购买行为发生了翻天覆地的变化。曾经&#xff0c;顾客的认知和购买是两个相对独立的过程&#xff0c;不仅时间不同步&#xff0c;空间上也存在明显的分离。但如今&#xff0c;微信、App、电子商务…