分布式环境集成JWT(Java Web Token)

目录

  • 一,说明:
  • 二,Token、Session和Cookie比较
  • 三,Spring Boot项目集成JWT
    • 1,引入依赖
    • 2,Token工具类
    • 3,定义拦截器
    • 4,注册拦截器
    • 5,编写登录代码
    • 6,测试
  • 四,说明

一,说明:

  • Token的引入:客户端向服务端请求数据时一般都会加入验证信息,比如客户端在请求的信息中携带用户名、密码,服务端会校验用户名和密码是否正确,校验通过响应该客户端请求。但是每次都携带用户名和密码无疑有些繁琐,而且也不安全,在这种背景下,Token便应运而生。Token在计算机身份认证中是令牌的意思。
  • Token的定义:Token是服务端生成的一串字符串,用来作为客户端请求的一个令牌。Token是客户端第一次登录时,由服务端生成并将其返回给客户端,以后客户端只需带上这个Token前来请求数据即可,无需带上用户名和密码。

二,Token、Session和Cookie比较

  • Session和Cookie区别:
    • 数据存放位置不同:Session数据是存在服务器中的,Cookie数据存放在浏览器当中;
    • 安全程度不同:Session存储在服务器中,比Cookie存储在浏览器中,安全程度要高;
    • 性能使用程度不同:Session存储在服务器上,数量过多会影响服务器性能;
    • 数据存储大小不同:单个Cookie保存的数据不能超过4K,Session存储在服务端,根据服务器大小决定。
  • Token和Session区别:
    • Token是开发定义的,Seesion是http协议规定的;
    • Token一般不存储,Session存储在服务器中,且在分布式环境中,Session会失效;

三,Spring Boot项目集成JWT

  • 我们实现如下功能:客户端登录获取Token,请求时携带Token,服务端对携带的Token进行验证,若是有效Token则放行,非法或是过期Token拦截,给出说明信息后直接返回。
  • 代码中抛出的异常,以及返回的实体类信息,是自定义异常,详细可参考该博客自定义异常。

1,引入依赖

    <!--hutool--><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId></dependency><!---jwt(java web token)--><dependency><groupId>com.auth0</groupId><artifactId>java-jwt</artifactId></dependency>

2,Token工具类

通过该工具类来生成Token,以及从请求头中获取token来获取当前用户的信息。

package com.tick.tack.utils;import cn.hutool.core.date.DateUtil;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.tick.tack.manager.entity.User;
import com.tick.tack.manager.service.IUserService;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;@Component
public class TokenUtils {// 日志类private static final Logger log= LoggerFactory.getLogger(TokenUtils.class);private static IUserService staticUserService;@Resourceprivate IUserService userService;@PostConstructpublic void setUserService() {//必须加@Component注解后才会执行该段代码,在spring容器中初始化staticUserService = userService;}public static String getToken(String userId, String password) {return JWT.create().withAudience(userId) //将userId保存到token里面,作为载荷.withExpiresAt(DateUtil.offsetHour(new Date(), 2))//2小时候过期.sign(Algorithm.HMAC256(password));//以password作为token的密钥}/*** 获取当前登录的用户信息*/public static User getCurrentUser() {try {HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();// 从请求头中获取token信息String token = request.getHeader("token");if (StringUtils.isNotBlank(token)) {String userAccount = JWT.decode(token).getAudience().get(0);return staticUserService.queryUserByAccount(userAccount);}} catch (Exception e) {e.printStackTrace();}return null;}
}

3,定义拦截器

AuthAccess是一个自定义的注解,在拦截器中判断如果方法上有加入该注解,则放行,不校验token

package com.tick.tack.common.interceptor;import cn.hutool.jwt.JWTException;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.tick.tack.common.Constants;
import com.tick.tack.config.AuthAccess;
import com.tick.tack.exception.ServiceException;
import com.tick.tack.manager.entity.User;
import com.tick.tack.manager.service.IUserService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;public class JWTInterceptor implements HandlerInterceptor {@Autowiredprivate IUserService userService;@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {String token = request.getHeader("token");//如果不是映射到方法直接通过if (!(handler instanceof HandlerMethod)) {return true;} else {// 判断是否为自定义注解AuthAccess,如果是,就不校验了,直接放行HandlerMethod h = (HandlerMethod) handler;AuthAccess authAccess = h.getMethodAnnotation(AuthAccess.class);if (authAccess != null) {return true;}}//执行认证if (StringUtils.isBlank(token)) {throw new ServiceException(Constants.CODE_401, "无token,请重新登录");}//获取token中的user id,验证是否合法String userAccount;try {userAccount = JWT.decode(token).getAudience().get(0);} catch (JWTException jwt) {throw new ServiceException(Constants.CODE_401, "token验证失败");}//根据token中的用户账号查询数据库信息User user = userService.queryUserByAccount(userAccount);if (user == null) {throw new ServiceException(Constants.CODE_401, "用户不存在,请重新登录");}//用户密码加签验证tokenJWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(user.getPassword())).build();try {//通过 verifier.verify() 方法检验 token,如果token不符合则抛出异常jwtVerifier.verify(token);} catch (Exception e) {throw new ServiceException(Constants.CODE_401, e.getMessage());}return true;}
}

自定义注解:

package com.tick.tack.config;import java.lang.annotation.*;@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AuthAccess {
}

4,注册拦截器

将拦截器注册到SpringMVC中

package com.tick.tack.config;import com.tick.tack.common.interceptor.JWTInterceptor;
import org.springframework.context.annotation.Bean;
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 {@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(jwtInterceptor())//拦截的路径.addPathPatterns("/**") //拦截所有请求,通过判断token是否合法来决定是否需要登录//排除不校验的接口.excludePathPatterns("/loginUser", //排除路径的时候不用考虑全局上下文context-path"/register",//Swagger页面拦截取消"/swagger-resources/**", "/webjars/**", "/v3/**", "/swagger-ui.html/**", "doc.html", "/error");}//考虑到UserService,此处需要注入一下@Beanpublic JWTInterceptor jwtInterceptor() {return new JWTInterceptor();}
}

5,编写登录代码

  • 1,登录实体类
@Data
public class LoginUser {// 登入用户名private String userAccount;// 登录密码private String password;
}
  • 2,token实体类
@Data
public class TickToken {// 用户名private String userAccount;// 密码private String password;// tokenprivate String token;// 到期时间private Date expireTime;
}
  • 3,系统登录控制类
@RestController
public class LoginController {@Autowired //按照类型注入@Qualifier(value = "loginServiceImpl")private ILoginService ILoginService;//登录系统@PostMapping("/loginUser")public Result loginSystem(@RequestBody LoginUser user) {if (StringUtils.isBlank(user.getUserAccount()) || StringUtils.isBlank(user.getPassword())) {return Result.error(Constants.CODE_400, "参数错误");}TickToken tickToken = ILoginService.loginSystem(user);return Result.success(tickToken);}
}
  • 4,业务逻辑实现类
public TickToken loginSystem(LoginUser user) {User one = userService.queryUserByAccount(user.getUserAccount());if (one != null && one.getPassword().equals(user.getPassword())) {TickToken tickToken = new TickToken();//生成token信息并返回String token = TokenUtils.getToken(one.getUserAccount(), one.getPassword());tickToken.setToken(token);// 设置过期时间:当前时间两小时以后tickToken.setExpireTime(DateUtil.offsetHour(new Date(),2));// 处理用户的菜单信息,在登录的时候返回给用户List<Menu> roleMenus = getRoleMenus(one);//tickToken.setMenus(roleMenus);return tickToken;} else {throw new ServiceException(Constants.CODE_600, "用户名或密码错误");}}

6,测试

  • 1,测试拦截
    当前未登录,测试拦截是否生效
@RestController
@RequestMapping("/demo")
public class DemoController {@GetMapping("/{id}")//@AuthAccesspublic Result getUser(@PathVariable("id") Integer id) {User user = new User(1, "zhangSan");return Result.success(user);}
}

未登录,也没有加相应注解,会提示没有token信息。
在这里插入图片描述

  • 2,登录获取token
    在这里插入图片描述* 3,携带token请求
    在这里插入图片描述
    如部分接口不希望被拦截,则为该接口方法加上@AuthAccess注解即可。

四,说明

该token是基于账户和密码来生成的一串字符串,并指定了过期时间,假如登录请求是在A机器实现,下一次请求在经过负载均衡后负载到B机器,B机器也可对其验证,因为token已经包括了全部的验证信息,服务器不保存相关信息,这样在分布式环境下也可正常使用。

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

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

相关文章

Vue 2 nextTick方法|异步更新|事件循环

1 nextTick的用处 vm.$netTick的作用是将回调延迟到下次DOM更新周期之后执行。 它接受一个回调函数作为参数。 其实&#xff0c;在我们更新数据状态后&#xff0c;是不会立马渲染的&#xff0c;你不能即刻获取到新的DOM&#xff1a; <!DOCTYPE html> <html><…

[论文笔记]ESIM

引言 这是经典论文Enhanced LSTM for Natural Language Inference的笔记。 本篇论文文是建立在自然语言推理(Natural Language Inference,NLI)任务上的。提出了简单的通过基于LSTM的序列推理模型效果到达了当时的SOTA水平。同时基于该模型,在局部推理建模层和推理组合层使用了…

嵌入式岗位笔试面试专栏 - 岗位介绍

文章目录 一、嵌入式岗位的分类二、热门领域及公司三、发展前景四、技能要求沉淀、分享、成长,让自己和他人都能有所收获!😄 📢本篇我们将讲解嵌入岗位的工作职责 。 一、嵌入式岗位的分类 嵌入式软件工程师大致可以分为两种类型: 应用开发工程师驱动开发工程师应用工程…

安全基础 --- 原型链污染

原型链 大部分面向对象的编程语言&#xff0c;都是通过“类”&#xff08;class&#xff09;实现对象的继承。传统上&#xff0c;JavaScript 语言的继承不通过 class&#xff0c;而是通过“原型对象”&#xff08;prototype&#xff09;实现 1、prototype 属性的作用 JavaScri…

蝶形运算法

蝶形运算法是一种基于FFT&#xff08;Fast Fourier Transform&#xff09;算法的计算方法&#xff0c;其基本思想是将长度为N的DFT分解成若干个长度为N/2的DFT计算&#xff0c;并通过不断的合并操作得到最终的结果。该算法也称为“蝴蝶算法”&#xff0c;因为它的计算过程中需要…

Git 版本回退 超神步骤

Git 版本回退 一. 背景 多版本分支开发&#xff0c;合并版本问题太多&#xff0c;需要回滚到某次版本。我的git客服端工具是 sourcetree 二.操作步骤 2.1 切到当前需要回退版本的分支 2.2 右击需要具体某一个分支&#xff0c;这个分支就是你想切到的分支版本&#xff0c;具体…

Spark 环境安装与案例演示

Spark 环境安装 一、准备工作 1、hadoop成功安装 2、防火墙关闭 二、解压安装 1、上传 spark 安装包到/tools 目录&#xff0c;进入 tools 下&#xff0c;执行如下命令&#xff1a; tar -zxvf spark-2.1.0-bin-hadoop2.7.tgz -C /training/由于 Spark 的脚本命令和 Hadoop…

12 最小覆盖串

最小覆盖串 题解1 滑窗双指针模板&#xff08;labuladong&#xff0c;高效好套&#xff0c;length版&#xff09; 给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串&#xff0c;则返回空字符串 "&quo…

wpf从cs代码创建简单3D物体和3D Tools研究

前面已经说了&#xff0c;WPF项目中引入3DTools dll之后&#xff0c;在xaml中加入它的命名空间&#xff0c; xmlns:tools"clr-namespace:_3DTools;assembly3DTools" 把<Viewport3D>标签包含在<tools:TrackballDecorator>标签之中&#xff0c;就可以用鼠…

ping: www.baidu.com: Name or service not known 写了DNS还是不行

环境描述&#xff1a;ESXI平台上&#xff0c;一台Centos7虚拟主机。 问题描述&#xff1a;平台上的其他的虚拟机可以正常ping通&#xff0c;就这台ping IP地址可以通&#xff0c;ping域名解析失败。 排查过程&#xff1a; 1、检查网卡配置文件和/etc/resolv.conf配置文件是否…

【SQL学习笔记】关系模型与查询和更新数据

一、关系模型 1.1 主键 主键是关系表中记录的唯一标识。主键的选取非常重要&#xff1a;主键不要带有业务含义&#xff0c;而应该使用BIGINT自增或者GUID类型。主键也不应该允许NULL。 可以使用多个列作为联合主键&#xff0c;但联合主键并不常用。 1.2 外键 FOREIGN KEY …

0010Java程序设计-springboot+vue影院售票系统设计与实现

摘 要目 录系统实现开发环境 摘 要 看电影已经成为了人们生活中不可缺少的一部分&#xff0c;电影院售票及管理系统是电影院的日常管理及售票任务的核心&#xff0c; 在电影院中&#xff0c; 工作人员并非只是放映电影&#xff0c; 还有诸如票房统计、影片放映、影片场次安排、…

PE文件格式详解

摘要 本文描述了Windows系统的PE文件格式。 PE文件格式简介 PE&#xff08;Portable Executable&#xff09;文件格式是一种Windows操作系统下的可执行文件格式。PE文件格式是由Microsoft基于COFF&#xff08;Common Object File Format&#xff09;格式所定义的&#xff0c…

淘宝数据库,主键如何设计的?

聊一个实际问题&#xff1a;淘宝的数据库&#xff0c;主键是如何设计的&#xff1f; 某些错的离谱的答案还在网上年复一年的流传着&#xff0c;甚至还成为了所谓的 MySQL 军规。其中&#xff0c;一个最明显的错误就是关于MySQL 的主键设计。 大部分人的回答如此自信&#xff…

论文阅读_大模型_ToolLLM

英文名称: ToolLLM: Facilitating Large Language Models to Master 16000 Real-world APIs 中文名称: TOOLLLM&#xff1a;帮助大语言模型掌握16000多个真实世界的API 文章: http://arxiv.org/abs/2307.16789 代码: https://github.com/OpenBMB/ToolBench 作者: Yujia Qin 日期…

保姆级 C++ 学习路线

上周有小伙伴留言求安排一手C/C学习路线&#xff0c;这周一份保姆级的C语言安排上&#xff01; 以前就写过C语言的学习路线&#xff1a;可能是北半球最好的零基础C语言学习路线&#xff0c;这次把C的学习路线也安排上&#xff0c;专门花了一个多月写了这篇学习路线&#xff0c;…

[Linux]编写一个极简版的shell(版本1)

[Linux]编写一个极简版的shell-version1 文章目录 [Linux]编写一个极简版的shell-version1命令行提示符打印接收命令行参数将命令行参数进行解释执行用户命令完整代码 本文能够帮助Linux系统学习者通过代码的角度更好地理解命令行解释器的实现原理。 命令行提示符打印 Linux操…

常用命令之mysql命令之show命令

一、mysql show命令简介 mysql数据库中show命令是一个非常实用的命令&#xff0c;SHOW命令用于显示MySQL数据库中的信息。它可以用于显示数据库、表、列、索引和用户等各种对象的信息。我们常用的有show databases&#xff0c;show tables&#xff0c;show full processlist等&…

SpringMVC常用注解、参数传递及页面跳转

一.SpringMVC常用注解 1.1.RequestMapping RequestMapping注解是一个用来处理请求地址映射的注解&#xff0c;可用于映射一个请求或一个方法&#xff0c;可以用在类或方法上。 标注在方法上运行代码 用于方法上&#xff0c;表示在类的父路径下追加方法上注解中的地址将会访…

无涯教程-JavaScript - NORMDIST函数

NORMDIST函数替代Excel 2010中的NORM.DIST函数。 描述 该函数返回指定均值和标准差的正态分布。此功能在统计中有非常广泛的应用,包括假设检验。 语法 NORMDIST(x,mean,standard_dev,cumulative)争论 Argument描述Required/OptionalXThe value for which you want the dis…