学习目标:

Spring Boot 整合JWT实现基于自定义注解的 登录请求接口拦截

例:

  • 一篇掌握 JWT 入门知识

 1.1 在学习SpringBoot 整合JWT之前,我们先来说说JWT进行用户身份验证的流程

  • 1:客户端使用用户名和密码请求登录
    2:服务端收到请求,验证用户名和密码
    3:验证成功后,服务端会签发一个token,再把这个token返回给客户端
    4:客户端收到token后可以把它存储起来,比如放到cookie中
    5:客户端每次向服务端请求资源时需要携带服务端签发的token,可以在cookie或者header中携带
    6:服务端收到请求,然后去验证客户端请求里面带着的token,如果验证成功,就向客户端返回请求数据
1.2 而JWT就是上述流程当中token的一种具体实现方式,其全称是JSON Web Token,官网地址:https://jwt.io/
并且这个JWT token带有签名信息,接收后可以校验是否被篡改,所以可以用于在各方之间安全地将信息作为Json对象传输。JWT的认证流程如下:
  • 1:首先,前端通过Web表单将自己的用户名和密码发送到后端的接口,这个过程一般是一个POST请求。建议的方式是通过SSL加密的传输(HTTPS),从而避免敏感信息被嗅探
    2:后端核对用户名和密码成功后,将包含用户信息的数据作为JWT的Payload,将其与JWT Header分别进行Base64编码拼接后签名,形成一个JWT Token,形成的JWT Token就是一个如同lll.zzz.xxx的字符串
    3:后端将JWT Token字符串作为登录成功的结果返回给前端。前端可以将返回的结果保存在浏览器中,退出登录时删除保存的JWT Token即可
    4:前端在每次请求时将JWT Token放入HTTP请求头中的Authorization属性中(解决XSS和XSRF问题)
    后端检查前端传过来的JWT Token,验证其有效性,比如检查签名是否正确、是否过期、token的接收方是否是自己等等
    5:验证通过后,后端解析出JWT Token中包含的用户信息,进行其他逻辑操作(一般是根据用户信息得到权限等),返回结果

 1.3  JWT结构

JWT由3部分组成:标头(Header)、有效载荷(Payload)和签名(Signature)。在传输的时候,会将JWT的3部分分别进行Base64编码后用.进行连接形成最终传输的字符串

JWTString=Base64(Header).Base64(Payload).HMACSHA256(base64UrlEncode(header)+"."+base64UrlEncode(payload),secret)JWTString=Base64(Header).Base64(Payload).HMACSHA256(base64UrlEncode(header)+"."+base64UrlEncode(payload),secret)

 1.3.1 Header

JWT头是一个描述JWT元数据的JSON对象,alg属性表示签名使用的算法,默认为HMAC SHA256(写为HS256);typ属性表示令牌的类型,JWT令牌统一写为JWT。最后,使用Base64 URL算法将上述JSON对象转换为字符串保存
{"alg": "HS256","typ": "JWT"
}

1.3.2 Payload

有效载荷部分,是JWT的主体内容部分,也是一个JSON对象,包含需要传递的数据。 JWT指定七个默认字段供选择
iss:发行人
exp:到期时间
sub:主题
aud:用户
nbf:在此之前不可用
iat:发布时间
jti:JWT ID用于标识该JWT

 除以上默认字段外,我们还可以自定义私有字段,一般会把包含用户信息的数据放到payload中,如下例:

{"sub": "1234567890","name": "Helen","admin": true
}

请注意:默认情况下JWT是未加密的,因为只是采用base64算法,拿到JWT字符串后可以转换回原本的JSON数据,任何人都可以解读其内容,因此不要构建隐私信息字段,比如用户的密码一定不能保存到JWT中,以防止信息泄露。JWT只是适合在网络中传输一些非敏感的信息

JWT 解码工具:在线JWT Token解析解码工具_x@lijun的博客-CSDN博客

1.3.3 Signature

签名哈希部分是对上面两部分数据签名,需要使用base64编码后的header和payload数据,通过指定的算法生成哈希,以确保数据不会被篡改。首先,需要指定一个密钥(secret)。该密码仅仅为保存在服务器中,并且不能向用户公开。然后,使用header中指定的签名算法(默认情况下为HMAC SHA256)根据以下公式生成签名
HMACSHA256(base64UrlEncode(header)+"."+base64UrlEncode(payload),secret)
HMACSHA256(base64UrlEncode(header)+"."+base64UrlEncode(payload),secret)
在计算出签名哈希后,JWT头,有效载荷和签名哈希的三个部分组合成一个字符串,每个部分用.分隔,就构成整个JWT对象

  • 注意JWT每部分的作用,在服务端接收到客户端发送过来的JWT token之后:header和payload可以直接利用base64解码出原文,从header中获取哈希签名的算法,从payload中获取有效数据signature由于使用了不可逆的加密算法,无法解码出原文,它的作用是校验token有没有被篡改。服务端获取header中的加密算法之后,利用该算法加上secretKey对header、payload进行加密,比对加密后的数据和客户端发送过来的是否一致。注意secretKey只能保存在服务端,而且对于不同的加密算法其含义有所不同,一般对于MD5类型的摘要加密算法,secretKey实际上代表的是盐值

SpringBoot 整合JWT实现基于自定义注解的-实现一个登录请求验证拦截

1:创建数据库结构

CREATE TABLE `users` (`id` int NOT NULL AUTO_INCREMENT,`username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL,`password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL,PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC;

2: 创建SpringBoot(2.3.5.RELEASE版本)工程,引入pom依赖

pom.xml

 <!-- web --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- MP --><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.4.0</version></dependency><!-- mysql --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><!-- lombok --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><!-- jwt --><dependency><groupId>com.auth0</groupId><artifactId>java-jwt</artifactId><version>3.11.0</version></dependency><!--  hutool --><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.12</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope><exclusions><exclusion><groupId>org.junit.vintage</groupId><artifactId>junit-vintage-engine</artifactId></exclusion></exclusions></dependency>

3: application.properties 文件

server.port=8900
spring.application.name=jwtmybatis-plus.configuration.log-impl=org.apache.ibatis.logging.log4j2.Log4j2Impl
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.password=root
spring.datasource.url=jdbc:mysql://localhost:3306/peixun?serverTimezone=GMT%2B8
spring.datasource.username=root

 4:JWTUtil 工具类

public class JWTUtils {private static final String APP_SECRET  = "ukc8BDbRigUDaY6pZFfWus2jZWLPHOsdadasdasfdssfeweee";/*** 生成token* @param map  传入payload* @return 返回token*/public static String getToken(Map<String,String> map){Calendar instance = Calendar.getInstance();
//        instance.add(Calendar.MILLISECOND,20);instance.add(Calendar.DAY_OF_WEEK,7);// 创建 JWT builderJWTCreator.Builder builder = JWT.create();// payloadmap.forEach(builder::withClaim);// 设置过期时间builder.withExpiresAt(instance.getTime());// 设置签名加密String token = builder.sign(Algorithm.HMAC256(APP_SECRET));return token;}/*** 验证token* @param token* @return*/public static void verify(String token){JWT.require(Algorithm.HMAC256(APP_SECRET)).build().verify(token);}/*** 获取token中payload* @param token* @return*/public static DecodedJWT getToken(String token){return JWT.require(Algorithm.HMAC256(APP_SECRET)).build().verify(token);}
}

5:封装统计返回结果类

Result

public class Result implements Serializable {/****/private static final long serialVersionUID = 1L;//状态码private Integer code;//响应消息private String msg;//响应数据private Object data;private Integer count;public Integer getCount() {return count;}public void setCount(Integer count) {this.count = count;}public Integer getCode() {return code;}public void setCode(Integer code) {this.code = code;}public String getMsg() {return msg;}public void setMsg(String msg) {this.msg = msg;}public Object getData() {return data;}public void setData(Object data) {this.data = data;}public Result(ResultCode resultCode,Object data) {this.code=resultCode.getCode();this.msg=resultCode.getMsg();this.data=data;}public Result(Integer code,String msg,Object data) {this.code=code;this.msg=msg;this.data=data;}}

 ResultCode

public enum ResultCode {//成功SUCCESS(200,"成功"),PARAM_IS_INVALID(1001,"参数无效"),PARAM_IS_BLANK(1002,"参数为空"),PARAM_IS_ERROR(1004,"参数错误"),PARAM_IS_COMPLETE(1003,"参数缺失"),USER_LONGIN_ERROR(2001,"账号或密码错误"),USER_LONGIN_EXIST(2002,"用户不存在"),USER_LONGIN_EXISTD(2003,"用户已存在"),KHXX_EXISTD(2010,"客户信息已存在!"),USER_LONGIN_EXAMINE(2004,"用户待审核"),UPDATE_EXAMINE(2006,"修改申请已提交,等待管理员审核"),USER_LONGIN_STOP(2005,"用户已停用"),ERROR(500,"操作失败"),CUSTOMER_EXISTD(400,"客户信息已存在"),PHONE_ERROR(402,"验证码或者手机号错误"),PHONE_EXIST(405,"手机号为空"),PHONE_RETRY(405,"请2分钟后重试"),PHONE_FPRMATEXIST(406,"手机号错误"),SINE_ERROR(401,"用户信息已过期,请重新登录"),LONG_OVERTIME(406,"登录超时,请重新登录"),KHXX_HTJH_EXISTD(1005,"该类型的计划已存在"),USER_INSUFFICIENT_AUTHORITY(403,"权限不足"),FILE_NOTBLANK(2007,"上传文件不能为空"),FILE_MAXSIZE(2008,"上传文件不能超过2M"),FILE_ERRORSUFFIX(2009,"上传文件错误,只能上传文档或表格以及PDF格式");private Integer code;private String msg;public Integer getCode() {return code;}public void setCode(Integer code) {this.code = code;}public String getMsg() {return msg;}public void setMsg(String msg) {this.msg = msg;}ResultCode(Integer code,String msg){this.code=code;this.msg=msg;}}

上述准备工作以及完成

6:pojo 实体类

@Data
public class Users {private String id;private String username;private String password;
}

7: annotation 自定义注解

/*** @author xia* @version 1.0* @Data 用来跳过验证的 PassToken* @date 2023/2/13 16:16*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface PassToken {boolean required() default true;
}
/*** @author xia* @version 1.0* @DATA  用于登录后才能操作的token* @date 2023/2/13 16:16*/@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface UserLoginToken {boolean required() default true;
}

8:拦截器

@Configuration
public class InterceptorConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(jwtInterceptor()).excludePathPatterns("/user/**").addPathPatterns("/**");}@Beanpublic JWTInterceptor jwtInterceptor() {return new JWTInterceptor();}
}
@Component
@SuppressWarnings("all")
public class JWTInterceptor implements HandlerInterceptor {@AutowiredUser user;// 请求前到达之前拦截@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object object)throws Exception {// 从请求头获取 tokenString token = request.getHeader("token");//首先映射是不是方法,如果不是则返回if(!(object instanceof HandlerMethod)){return true;}HandlerMethod handlerMethod=(HandlerMethod) object;Method method=handlerMethod.getMethod();//检查是否有passtoken注释,有则跳过认证if (method.isAnnotationPresent(PassToken.class)) {PassToken passToken = method.getAnnotation(PassToken.class);if (passToken.required()) {return true;}}// token 验证失败后的返回信息if (method.isAnnotationPresent(UserLoginToken.class)) {UserLoginToken userLoginToken = method.getAnnotation(UserLoginToken.class);if (userLoginToken.required()) {if (StrUtil.isBlank(token)) {throw new RuntimeException( "无token,请重新登录");}// 获取 token 中的 user idint userId;try {DecodedJWT decode = JWT.decode(token);String id = decode.getClaim("id").asString();userId = Integer.parseInt(id);} catch (JWTDecodeException j) {throw new RuntimeException("401");}System.out.println(userId);Users users = user.findUserById(userId);if (users == null) {throw new RuntimeException("用户不存在,请重新登录");}try {// JWT 工具包验证 tokenJWTUtils.verify(token);return true;} catch (TokenExpiredException e) {throw new RuntimeException("Token已经过期!!!");} catch (SignatureVerificationException e){throw new RuntimeException("签名错误!!!");} catch (AlgorithmMismatchException e){throw new RuntimeException("加密算法不匹配!!!");} catch (Exception e) {e.printStackTrace();throw new RuntimeException("无效token~~");}}}return true;}
}

9:controller 层

@RestController
@Slf4j
@SuppressWarnings("all")
public class JwtController {@Resourceprivate UsersMapper mapper;// 认证@PassToken@PostMapping("/user/login")public Result login(@RequestBody Users user) {System.out.println("前端传来的用户数据:"+user);try {// 查询数据库QueryWrapper<Users> queryWrapper = new QueryWrapper<>();HashMap<String, Object> queryMap = new HashMap<>();queryMap.put("username", user.getUsername());queryMap.put("password", user.getPassword());queryWrapper.allEq(queryMap);Users userDb = mapper.selectOne(queryWrapper);// 认证失败if(userDb == null) {throw new RuntimeException("没有此用户。请重新登录");}// 认证成功Map<String, String> tokenClaimsMap = new HashMap<>(); //用来存放payloadtokenClaimsMap.put("id",userDb.getId());tokenClaimsMap.put("username", userDb.getUsername());String token = JWTUtils.getToken(tokenClaimsMap);// 返回的数据return new Result(ResultCode.SUCCESS,token);} catch (Exception e) {// 认证失败,返回的数据e.printStackTrace();return new Result(ResultCode.ERROR,e);}}@UserLoginToken@GetMapping("/private/info")public String info(HttpServletRequest request){String token = request.getHeader("token");DecodedJWT tokenJWT = JWTUtils.getToken(token);System.out.println(tokenJWT.getClaim("username").asString());System.out.println(tokenJWT.getClaim("userId").asInt());return "这是一段私人信息。只有登录才能显示";}@UserLoginToken@GetMapping("/private/Test")public Result info(){String code = "这是一段私人信息。只有登录才能显示";return new Result(ResultCode.SUCCESS,code);}@PassToken()@PostMapping("/private/Test2")public Result info2(){String code = "这是一段普通信息。不登录也能显示";return new Result(ResultCode.SUCCESS,code);}
}

10: service 以及实现类

@Service
public interface User {Users findUserById(int userId);
}
@Service
public class UserService implements User{@Resourceprivate UsersMapper mapper;@Overridepublic Users findUserById(int userId) {Users users = mapper.selectById(userId);System.out.println("------------>"+ users);return users;}
}

11: mapper层


@Mapper
public interface UsersMapper extends BaseMapper<Users> {
}

启动项目,演示效果如下:

1:账号登录成功生成Token (登录请求,使用@PassToken() 注解 跳过token验证)

    @PassToken@PostMapping("/user/login")public Result login(@RequestBody Users user) {System.out.println("前端传来的用户数据:"+user);try {// 查询数据库QueryWrapper<Users> queryWrapper = new QueryWrapper<>();HashMap<String, Object> queryMap = new HashMap<>();queryMap.put("username", user.getUsername());queryMap.put("password", user.getPassword());queryWrapper.allEq(queryMap);Users userDb = mapper.selectOne(queryWrapper);// 认证失败if(userDb == null) {throw new RuntimeException("没有此用户。请重新登录");}// 认证成功Map<String, String> tokenClaimsMap = new HashMap<>(); //用来存放payloadtokenClaimsMap.put("id",userDb.getId());tokenClaimsMap.put("username", userDb.getUsername());String token = JWTUtils.getToken(tokenClaimsMap);// 返回的数据return new Result(ResultCode.SUCCESS,token);} catch (Exception e) {// 认证失败,返回的数据e.printStackTrace();return new Result(ResultCode.ERROR,e);}

2:使用这个注解 :@UserLoginToken(登录需要验证)

    @UserLoginToken@GetMapping("/private/info")public String info(HttpServletRequest request){String token = request.getHeader("token");DecodedJWT tokenJWT = JWTUtils.getToken(token);System.out.println(tokenJWT.getClaim("username").asString());System.out.println(tokenJWT.getClaim("userId").asInt());return "这是一段私人信息。只有登录才能显示";}@UserLoginToken@GetMapping("/private/Test")public Result info(){String code = "这是一段私人信息。只有登录才能显示";return new Result(ResultCode.SUCCESS,code);}

不携带Token 打印结果:

Token 错误:

3: 使用@PassToken() 放行请求 ,即不需要Token 也可登录

    @PassToken()@PostMapping("/private/Test2")public Result info2(){String code = "这是一段普通信息。不登录也能显示";return new Result(ResultCode.SUCCESS,code);}

源码地址如下:

SpringBoot 整合 jwt: 学习token的1111

SpringBoot 整合JWT实现基于自定义注解的-登录请求验证拦截(保姆级教学,附:源码)相关推荐

  1. springboot+vue.js+mysql+基于VUE框架的商城综合项目自动化系统的实现 毕业设计-附源码051018

    商城综合项目自动化系统 摘 要 目前电商系统商城项目管理极其频繁,迫切地需要自动化测试来代替人工繁琐而又重复的劳动.自动化测试相关的研究已经很多,但多数只是针对某一方面,比如单一接口或者单一页面或者性 ...

  2. java计算机毕业设计ssm基于Vue的校园电脑租赁系统设计与开发19xy6(附源码、数据库)

    java计算机毕业设计ssm基于Vue的校园电脑租赁系统设计与开发19xy6(附源码.数据库) 项目运行 环境配置: Jdk1.8 + Tomcat8.5 + Mysql + HBuilderX(We ...

  3. 毕业设计-基于SSM框架大学教务管理平台项目开发实战教程(附源码)

    文章目录 1.项目简介 2.项目收获 3.项目技术栈 4.测试账号 5.项目部分截图 6.常见问题 毕业设计-基于SSM框架大学教务管理平台项目实战教程-附源码 课程源码下载地址:https://do ...

  4. Springboot整合多数据源(自定义注解+aop切面实现)

    原理: 通过后台配置多个数据源,自定义注解,通过aop配置注解切面,前端调用需要传递数据源参数,根据判断数据源参数,调用相应的service或mapper方法. 实现: 准备俩个数据库:俩张表 表sq ...

  5. springboot基于web模式的师资管理系统的设计与实现 毕业设计-附源码040928

    springboot师资管理系统设计与实现 摘 要 随着互联网趋势的到来,各行各业都在考虑利用互联网将自己推广出去,最好方式就是建立自己的互联网系统,并对其进行维护和管理.在现实运用中,应用软件的工作 ...

  6. 基于SpringBoot的医院门诊管理系统,高质量毕业论文范例-可直接参考使用,附源码和数据库脚本,项目导入运行视频教程,论文撰写教程

    1.项目技术栈 前端必学三个基础HTML.CSS.JS,基本每个B/S架构项目都要用到,基础中的基础.此外项目页面使用thymeleaf等前端框架技术. 后端使用Java主流的框架SpringBoot ...

  7. SpringBoot做的两个系统,一个时间定时任务(quartz),一个微信签到(附源码)

    简单的两个SpringBoot Demo ps:有什么不懂的可以直接提出来,我可以一一解答,如quartz如何对SpringBoot进行依赖注入等等这些问题都可以的,需要的话我收集问题专门写一篇文章解 ...

  8. 基于Spring+SpringMVC+Mybatis的分布式敏捷开发系统架构(附源码)

    点击上方 好好学java ,选择 星标 公众号重磅资讯,干货,第一时间送达 今日推荐:推荐19个github超牛逼项目!个人原创100W +访问量博客:点击前往,查看更多 作者:zheng gitee ...

  9. 基于Java开发一套完整的区块链系统(附源码)

    来源:https://blog.csdn.net/victory_long 前言 近几年区块链概念越来越火,特别是区块链技术被纳入国家基础设施建设名单后,各大企业也开始招兵买马,对区块链技术进行研究, ...

最新文章

  1. 技术图文:Numpy 一维数组 VS. Pandas Series
  2. 【建模】可视化描绘现实世界-三种模型转换
  3. 【工作经验分享】java图片转文字
  4. C++学习之路 | PTA(天梯赛)—— L2-007 家庭房产 (25分)(带注释)(并查集)(精简)
  5. linux下通过gprs模块拨号上网(转)
  6. poj1066--Treasure Hunt(规范相交)
  7. c# listbox使用
  8. 《高等代数学》(姚慕生),习题1.4:行列式的展开和转置
  9. Linux将一个文件夹或文件夹下的所有内容复制到另一个文件夹
  10. linux用cat建文件,如何使用Linux cat命令
  11. YOLOv4 改进 | 记录如何一步一步改进YOLOv4到自己的数据集(性能、速度炸裂)
  12. 估计四川长虹的beta系数
  13. 不想工作了怎么破?那就去这4个地方看一看
  14. 用vue简单写一个音乐播放器
  15. 【Pytorch基础教程34】EGES召回模型
  16. 计算机科学与技术专业为什么要学物理,「物理」一定要好的14个大学专业
  17. Should we ban guns 英语禁枪议论文
  18. 2022全新直播短视频系统源码+附教程/可二开可采集
  19. 编译原理———词法分析器
  20. 《花开半夏》--4 生死之间的吻(1)

热门文章

  1. 矩阵向右旋转90° — C语言
  2. 不开通百度云会员也可以会员速度下载
  3. IDEA使用中directory和package
  4. 网龙3D人物部件制作工艺介绍
  5. macOS终端字体颜色DIY教程
  6. 雅虎日本如何用 Pulsar 构建日均千亿的消息平台
  7. Java之List系列--去重的方法
  8. 高频拨号、一键拨号,外呼系统功能多到你难以想象
  9. 谷歌人工智能开发套件AIY Kit亮相CTE2019!
  10. Android应用统计-使用时长及次数统计(一)