当前位置: 首页 > news >正文

SpringBoot基于JWT的token做登录认证

背景

我们在基于Session做登录认证的时候,会有一些问题,因为Session存储到服务器端,然后通过客户端的Cookie进行匹配,如果正确,则通过认证,否则不通过认证。这在简单的系统中可以这么使用,并且难度是最低的,但是如果在大型分布式项目中,如果还是基于Session做登录认证的话,就不可行了。这个时候我们可以基于token做登录认证。token其实就是一个字符串,生成token的实现方案有很多种,可以使用uuid作为token,也可以使用jwt作为token,其中使用jwt实现的方案是最流行的,那么下面将会讲如何在SpringBoot中基于jwt实现token登录认证。

1. 引入依赖

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

2. 自定义注解

自定义一个注解,在需要认证的方法上添加该注解

@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Auth {boolean require() default true;
}

3. 编写拦截器

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

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

public class LoginInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {if (handler instanceof HandlerMethod) {HandlerMethod handlerMethod = (HandlerMethod) handler;if (handlerMethod.getBean() instanceof BasicErrorController) {return true;}Auth auth = handlerMethod.getMethod().getAnnotation(Auth.class);if (auth != null && auth.require()) {String token = request.getHeader("token");if (StringUtils.isNotBlank(token)) {if (TokenUtil.verifyToken(token)) { // 校验 token 是否正确return true;} else {request.getRequestDispatcher("/error/tokenError").forward(request, response); // 这里你也可以直接抛出自定义异常,然后在全局异常处理器中处理}} else {request.getRequestDispatcher("/error/token").forward(request, response); // 这里你也可以直接抛出自定义异常,然后在全局异常处理器中处理}} else {return true;}} else {return true;}return false;}
}

4. 定义跨域拦截器

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

public class CrossInterceptorHandler implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {response.setHeader("Access-Control-Allow-Origin", "*");response.setHeader("Access-Control-Allow-Credentials", "true");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");return true;}
}

5. 定义全局异常处理器

这里没有用到全局异常处理器,不过为了项目的完整性,我还是选择把这些常规的内容写上去。

@RestControllerAdvice
public class GlobalException {public final Logger logger = LoggerFactory.getLogger(this.getClass());@ExceptionHandler(TokenExpiredException.class)public R<?> handleTokenExpiredException(TokenExpiredException e) {logger.error("token 已过期");logger.error(e.getMessage());return R.error(ResponseEnum.TOKEN_EX);}
}

6. 定义工具类

6.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;}
}

6.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;}
}

6.3 Token工具类

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

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;/*** @author Luke Ewin* @date 2024/2/19 16:59* @blog blog.lukeewin.top*/
public class TokenUtil {private final static String ENCRYPT_KEY = "abc123"; // 加密的密钥private final static int EXPIRE_TIME = 1; // token 过期时间,单位分钟private static final String ISSUER = "zhangsan";/*** 生成 token** @param json 要封装到 token 的内容,如果要传递多个参数内容,可以定义为 JSON 或者 Map* @return 返回 token*/public static String createToken(JSONObject json) {return JWT.create().withSubject(json.toString()) // 不要把密码封装进去,不安全.withIssuer(ISSUER) // 设置发布者.withExpiresAt(DateUtil.offsetMinute(new Date(), EXPIRE_TIME)) // 设置过期时间.withClaim("test", "123") // 这里是随便设置的内容,类似 Map.sign(Algorithm.HMAC256(ENCRYPT_KEY)); // 加密}/*** 验证 token** @param token* @return*/public static boolean verifyToken(String token) {try {JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(ENCRYPT_KEY)).withIssuer(ISSUER).build();jwtVerifier.verify(token);return true;} catch (Exception e) { // 如果 token 过期会报错 TokenExpiredExceptione.printStackTrace();return false;}}
}

7. 编写实体类

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

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

8. 定义控制器

8.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);}
}

8.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);}
}

8.3 定义测试控制器

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

9. 配置类

最后别忘了定义一个配置类,把我们自定义的两个拦截器注册进去。

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new CrossInterceptorHandler()).addPathPatterns(new String[] {"/**"});registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/**").excludePathPatterns("/user/login", "/error/**");}
}

10. 最终的效果

访问登录接口,通过提交表单方式提交请求,通过token验证后会返回一个token,然后我们请求添加了@Auth注解的接口都需要在请求头添加token字段和对应的值。

image-20240220174407236

如果请求头中没有填写token或者填写的不对,在请求需求登录后才能访问的接口时都会报错。比如这里的/test/hello是需要登录后才能访问的接口,如果没有正确填写token,那么会报错,如下图所示。

image-20240220175045471 image-20240220174939307

如果正确填写了token,那么效果如下。

image-20240220175214721

有一个test/hi接口没有@Auth注解,可以不用登录就能访问,如下图所示。

image-20240220175428818

以上就是本篇文章所分享的内容,如果对你有用,记得收藏哦!

更多Java干货,欢迎关注我的博客。

代码已经开源到github中,如需要下载源代码,可点击这里。

相关文章:

SpringBoot基于JWT的token做登录认证

背景 我们在基于Session做登录认证的时候&#xff0c;会有一些问题&#xff0c;因为Session存储到服务器端&#xff0c;然后通过客户端的Cookie进行匹配&#xff0c;如果正确&#xff0c;则通过认证&#xff0c;否则不通过认证。这在简单的系统中可以这么使用&#xff0c;并且…...

[ 2024春节 Flink打卡 ] -- Paimon

2024&#xff0c;游子未归乡。工作需要&#xff0c;flink coding。觉知此事要躬行&#xff0c;未休&#xff0c;特记 Flink 社区希望能够将 Flink 的 Streaming 实时计算能力和 Lakehouse 新架构优势进一步结合&#xff0c;推出新一代的 Streaming Lakehouse 技术&#xff0c;…...

计算机网络——14CDN

CDN 视频流化服务和CDN&#xff1a;上下文 视频流量&#xff1a;占据着互连网大部分的带宽 Netflix&#xff0c;YouTube&#xff1a;占据37%&#xff0c;16%的下行流量 挑战&#xff1a;规模性-如何服务~1B用户&#xff1f; 单个超级服务器无法提供服务&#xff08;为什么&am…...

Docker技术仓库

数据卷 为什么用数据卷&#xff1f; 宿主机无法直接访问容器中的文件容器中的文件没有持久化&#xff0c;导致容器删除后&#xff0c;文件数据也随之消失容器之间也无法直接访问互相的文件 为解决这些问题&#xff0c;docker加入了数据卷机制&#xff0c;能很好解决上面问题…...

Kotlin学习 6

1.接口 interface Movable {var maxSpeed: Intvar wheels: Intfun move(movable: Movable): String}class Car(var name: String, override var wheels: Int 4, _maxSpeed: Int) : Movable {override var maxSpeed: Int _maxSpeedget() fieldset(value) {field value}overr…...

⭐北邮复试刷题LCR 052. 递增顺序搜索树__DFS (力扣119经典题变种挑战)

LCR 052. 递增顺序搜索树 给你一棵二叉搜索树&#xff0c;请 按中序遍历 将其重新排列为一棵递增顺序搜索树&#xff0c;使树中最左边的节点成为树的根节点&#xff0c;并且每个节点没有左子节点&#xff0c;只有一个右子节点。 示例 1&#xff1a; 输入&#xff1a;root [5,…...

获取discord上自己创建的服务器的服务器ID、频道ID以及discord的登录token(用于第三方登录)

在服务器图标上右键点击-》复制服务器ID 在频道上右键点击-》复制频道ID F12->手机模式-》application-》local storage-》填写过滤条件【token】 我开发的chatgpt网站&#xff1a; https://chat.xutongbao.top...

图纸透明加密:保护机械图纸安全的新方法

随着信息技术的不断发展&#xff0c;机械制造行业对于图纸安全的需求越来越高。机械图纸是企业的核心竞争力之一&#xff0c;泄露可能导致严重的商业损失和技术风险。为了解决这一问题&#xff0c;图纸透明加密成为了一种新的保护机械图纸安全的方法。本文将介绍图纸透明加密的…...

基于springboot + vue实现的前后端分离-酒店管理系统

项目介绍 基于springboot vue实现的酒店管理系统一共有酒店管理员和用户这两种角色。 管理员功能 登录&#xff1a;管理员可以通过登录功能进入系统&#xff0c;确保只有授权人员可以访问系统。用户管理&#xff1a;管理员可以添加、编辑和删除酒店的用户&#xff0c;包括前…...

79.SpringBoot的核心注解

一、SpringBoot的核心注解 SpringBootApplication注解&#xff1a;这个注解标识了一个SpringBoot工程&#xff0c;它实际上是另外三个注解的组合&#xff0c;这三个注解是&#xff1a;SpringBootConfiguration&#xff1a;这个注解实际就是一个Configuration&#xff0c;表示启…...

MATLAB 导出可编辑的eps格式图像

任务描述&#xff1a;部分期刊要求提交可编辑的eps格式图像&#xff0c;方便美工编辑对图像进行美化 我试了直接print或者在figure窗口导出&#xff0c;发现导出的文件放到Adobe AI中并不能编辑&#xff0c;经Google找到解决办法&#xff1a; %EPS exportgraphics(gcf,myVect…...

四问带你搞懂 I3C

大家都知道 I2C &#xff0c;它的全称是 Inter Integrated Circuit &#xff0c;那 I3C 又是什么&#xff1f; I3C 是 MIPI &#xff08;Mobile Industry Processor Interface&#xff09;移动产业处理器接口联盟推出的&#xff0c;全称是 Improved Inter Integrated Circuit &…...

fastjson解析自定义get方法导致空指针问题

背景 为了在日志中把出入参打印出来&#xff0c;以便验证链路和排查问题&#xff0c;在日志中将入参用fastjson格式化成字符串输出&#xff0c;结果遇到了NPE。 问题复现 示例代码 public static void main(String[] args) {OrganizationId orgId new OrganizationId();N…...

github新手用法详解

GitHub是一个非常强大的版本控制工具&#xff0c;它为程序员提供了一个便捷的方式来管理代码、协作开发和参与开源项目。但对于新手来说&#xff0c;可能会觉得GitHub的使用有些复杂。因此&#xff0c;本篇文章将详细介绍GitHub的基本用法&#xff0c;帮助新手快速上手并充分利…...

MAC电脑系统清理空间免费版软件CleanMyMac X2024

大家好&#xff0c;我是那个总是被苹果电脑“内存已满”提示搞得焦头烂额的专业博主。如果你也像我一样&#xff0c;在使用Mac时经常遭遇卡顿、慢吞吞的情况&#xff0c;那么今天的Mac清理空间妙招分享绝对适合你&#xff01; CleanMyMac X全新版下载如下: https://wm.makedi…...

notepad++运行python闪一下就没啦

问题&#xff1a;Notepad直接快捷键运行Python代码,出现闪一下就没了 解决措施&#xff1a; ①点击菜单运行(Run) --> 运行(Run)弹出的对话框 ②把 cmd /k python "$(FULL_CURRENT_PATH)" & ECHO. & PAUSE & EXIT 粘贴进入这个对话框内 ③点击保存&a…...

基于springboot+vue的课程答疑系统(前后端分离)

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、阿里云专家博主、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战&#xff0c;欢迎高校老师\讲师\同行交流合作 ​主要内容&#xff1a;毕业设计(Javaweb项目|小程序|Pyt…...

【工具类】非 sudo 运行 docker

非 root 运行 docker 命令 sudo groupadd docker sudo usermod -aG docker $USER newgrp docker sudo chown root:docker /var/run/docker.sock sudo chown "$USER":"$USER" /home/"$USER"/.docker -R sudo chmod grwx "$HOME/.docker&quo…...

力扣49.字母异位词分组

题目描述&#xff1a; 49. 字母异位词分组 难度 中等 给你一个字符串数组&#xff0c;请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。 字母异位词 是由重新排列源单词的所有字母得到的一个新单词。 示例 1: 输入: strs ["eat", "tea"…...

git操作--->在远程删除了某个分支,但本地使用git branch -r的时候还是会显示某个分支存在是什么原因

&#x1f495;又迷糊了哈哈&#xff0c;以为自己命令执行错了&#xff0c;结果可能是缓存的原因&#xff1a;&#x1f495; &#x1f602;如果你发现使用 git branch -r 命令显示了一个远程没有的分支&#xff0c;这可能是由以下几个原因造成的&#xff1a;&#x1f602; 缓存…...

合并Windows电脑的不同分区(不同的盘)的方法

本文介绍在Windows操作系统的电脑中&#xff0c;将磁盘上的不同分区&#xff08;例如E盘与F盘&#xff09;加以合并的方法。 最近&#xff0c;想着将新电脑的2个分区加以合并&#xff1b;如下图所示&#xff0c;希望将E盘与F盘合并为一个分区。本文就介绍一下实现这一需求的具体…...

web前端安全性——iframe安全问题

1、概念 iframe安全问题可称作界面劫持&#xff0c;像点击劫持、拖放劫持、触屏劫持。就是我们的点击&#xff0c;拖放&#xff0c;触屏操作被劫持了&#xff0c;而去操作了其它的透明隐藏的界面。 **原理是利用透明层iframe,使用了CSS中的opacity或z-index等属性&#xff0c;…...

从零开始学习Netty - 学习笔记 - NIO基础 - 网络编程: Selector

4.网络编程 4.1.非阻塞 VS 阻塞 在网络编程中&#xff0c;**阻塞&#xff08;Blocking&#xff09;和非阻塞&#xff08;Non-blocking&#xff09;**是两种不同的编程模型&#xff0c;描述了程序在进行网络通信时的行为方式。 阻塞&#xff08;Blocking&#xff09;&#xff1…...

useRef有什么用?

看一下官网定义 useRef是一个React Hook&#xff0c;它能帮助引用一个不需要渲染的值 这句话透露出一个信息&#xff0c;不需要渲染的值可以用useRef引用&#xff0c;那需要渲染的值用什么引用呢&#xff1f;当然是useState了&#xff0c;需要渲染的值指的就是状态嘛&#xff0…...

vue3中,ref()、reactive()、computed()、watch() 和 watchEffect()的区别

ref()、reactive()、computed()、watch() 和 watchEffect() 是 Vue 3 中常用的响应式处理函数&#xff0c;它们的主要区别如下&#xff1a; ref()&#xff1a;ref() 函数用于将一个普通的 JavaScript 值转化为响应式对象。它返回一个具有 value 属性的对象&#xff0c;我们可以…...

Java基于SpringBoot的校园轻博客系统,附源码

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…...

webstorm光标变成方块解决办法_webstorm光标变粗不能换行

webstorms光标变了 键盘上的insert是切换的快捷键&#xff0c;敲insert就可以来回切换了...

从计网的角度讲明白什么是网关

网关(Gateway)又称网间连接器、协议转换器。网关在传输层上以实现网络互连&#xff0c;是最复杂的网络互连设备&#xff0c;仅用于两个高层协议不同的网络互连。网关的结构也和路由器类似&#xff0c;不同的是互连层。网关既可以用于广域网互连&#xff0c;也可以用于局域网互连…...

如何选择最适合的图纸加密软件?安秉网盾软件用户体验及性价比

安秉网盾图纸加密软件是一款功能强大的图纸加密工具&#xff0c;具有以下特点和优势&#xff1a; 全盘加密&#xff1a;安秉网盾采用先进的加密算法&#xff0c;能对文件、文件夹、磁盘等数据进行全面加密&#xff0c;确保数据在存储和传输过程中的安全性。 监控与审计&#x…...

Spring Security学习(六)——配置多个Provider(存在两种认证规则)

前言 《Spring Security学习&#xff08;五&#xff09;——账号密码的存取》一文已经能满足一般应用的情况。但实际商业应用也会存在如下的情况&#xff1a;用户提交的账号密码&#xff0c;能在本地的保存的账号密码匹配上&#xff0c;或者能在远端服务认证中匹配上&#xff…...