使用SpringBoot+JWT+MybatisPlus实现简单的登陆状态验证

文章目录

  • 前言
  • 一、JWT是什么?
  • 二、使用步骤
    • 1.创建项目,导入依赖,配置、引入工具类
    • 2.编写LoginController和UserController
    • 3.编写跨域拦截器和token验证拦截器
    • 4.全局拦截器配置
  • 三、业务逻辑
  • 四、测试
  • 总结


前言

登陆功能是每个系统的最基本功能,在SSM技术栈中,登陆状态验证一般会使用服务端的session,但是session并没有想象中的那么好用,经常会出现由于sessionid不一致导致的信息丢失,更好的解决方案就是使用JWT的Token生成。


一、JWT是什么?

JSON Web Token (JWT)是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于作为JSON对象在各方之间安全地传输信息,该信息可以被验证和信任,因为它是数字签名的,常用于单点登录。

JWT-token
JSON Web Token由三部分组成,它们之间用圆点(.)连接。这三部分分别是:头部,载荷,签名

  • Header
    头部用于描述关于该JWT的最基本的信息,例如其类型以及签名所用的算法等。这也可以被表示成一个JSON对象,示例:{“typ”:“JWT”,“alg”:“HS256”}
    在头部指明了签名算法是HS256算法。 我们可以通过BASE64进行编码/解码:https://www.matools.com/base64/
  • Payload
    载荷就是存放有效信息的地方。这个名字像是特指飞机上承载的货品,这些有效信息包含三个部分。
    1.标准中注册的声明(建议但不强制使用):例如:sub表示jwt所面向的用户
    2.公共的声明:可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密.
    3.私有的声明:私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。
    例如:定义一个payload:
    {“sub”:“1234567890”,“name”:“John Doe”,“admin”:true}
    然后将其进行base64加密,得到Jwt的第二部分:eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9
  • Signature
    jwt的第三部分是一个签证信息,这个签证信息由三部分组成:header + payload + secret
    这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。
    注意:secret是保存在服务器端的,jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和jwt的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了

本次使用jwt的token生成来实现一个登陆状态的验证。

二、使用步骤

1.创建项目,导入依赖,配置、引入工具类

本次使用mybatis-plus的一键生成项目,具体的步骤可以查看我之前的文章mybatis-plus详解http://t.csdn.cn/F0QcR
引入依赖

 <!-- jwt -->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-impl</artifactId>
            <version>0.11.2</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-api</artifactId>
            <version>0.11.2</version>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-jackson</artifactId>
            <version>0.11.2</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
            <version>2.7.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-text</artifactId>
            <version>1.8</version>
        </dependency>

生成token的工具类:

package com.lzl.utils;


import io.jsonwebtoken.*;
import org.apache.commons.lang3.time.DateUtils;

import javax.crypto.spec.SecretKeySpec;
import java.security.Key;
import java.util.Date;

//jwt工具类
public class JwtHelper {

    // 生成Jwt
    public static String jwsWithHS(SignatureAlgorithm signatureAlgorithm, String userInfo, int num, String secret) {
        Key key = new SecretKeySpec(secret.getBytes(), signatureAlgorithm.getJcaName());
        Claims claims = Jwts.claims();
        claims.setSubject(userInfo);  // jwt的信息的本身
        claims.setExpiration(DateUtils.addSeconds(new Date(), num)); //设置jwt多少秒过期
        String jws = Jwts.builder()
                .setClaims(claims).signWith(key, signatureAlgorithm).compact();
        return jws;
    }

    // 校验Jwt
    public static Jwt verifySign(String jws, String secret, SignatureAlgorithm signatureAlgorithm) {
        Key key = new SecretKeySpec(secret.getBytes(), signatureAlgorithm.getJcaName());
        Jwt jwt = Jwts.parserBuilder().setSigningKey(key).build().parse(jws);
        return jwt;
    }


}

本次使用的secret:
在这里插入图片描述
由于我们需要在controller中使用,所以把两个参数,密钥和加密方式配置在yml文件中

这个secret可以去https://jwt.io/网站生成
在这里插入图片描述

上边选择加密方法,下边输入你想加密的信息,左侧会显示出加密后的信息。

2.编写LoginController和UserController

package com.lzl.controller;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.lzl.pojo.User;
import com.lzl.service.UserService;
import com.lzl.utils.JwtHelper;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

/**
 * --效率,是成功的核心关键--
 *
 * @Author lzl
 * @Date 2023/3/21 08:21
 */
@RestController
@RequestMapping("/login")
public class LoginController {
    @Value("${jwt.secret}")
    private String secret;

    @Value("${jwt.signature-algorithm}")
    private String algorithm;

    @Autowired
    private UserService service;

    @RequestMapping("/verify")
    public Map<String,Object> loginVerify(User user){
        Map<String, Object> map = new HashMap<>();
        //去库中查询
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.eq("username",user.getUsername())
                .eq("password",user.getPassword());
        User one = service.getOne(wrapper);
        if(one != null){
            map.put("username",one.getUsername());
            //登录成功,生成token
            String token = JwtHelper.jwsWithHS(
                    SignatureAlgorithm.forName(algorithm),
                    user.getUsername(),
                    3600,
                    secret
            );
            map.put("token",token);
        }
        return map;
    }
}

package com.lzl.controller;


import com.lzl.pojo.User;
import com.lzl.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RestController;

import java.util.List;

/**
 * <p>
 *  前端控制器
 * </p>
 *
 * @author zhenLong
 * @since 2023-03-20
 */
@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    private UserService service;

    @RequestMapping("/getAll")
    public List<User> getUser(){
        return service.list();
    }
}


3.编写跨域拦截器和token验证拦截器

package com.lzl.interceptor;

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * --效率,是成功的核心关键--
 * 拦截器,用于处理跨域请求
 *
 * @Author lzl
 * @Date 2022/10/1 10:20
 */

public class CrossOriginInterceptor implements HandlerInterceptor {
    //主要逻辑:在handler之前执行:抽取handler中的冗余代码
    @Override
    public boolean preHandle(HttpServletRequest request,
                             HttpServletResponse response, Object handler) throws Exception {
        String origin = request.getHeader("Origin");
        // 允许的跨域
        response.setHeader("Access-Control-Allow-Origin",origin);
        // 允许携带Cookie
        response.setHeader("Access-Control-Allow-Credentials","true");
        // 允许的请求头  预检请求需要这个设置
        response.setHeader("Access-Control-Allow-Headers", "Content-Type,X-Requested-With,accept,Origin,Access-Control-Request-Method,Access-Control-Request-Headers,Authorization,Access-Token,token");
        response.setHeader("Access-Control-Expose-Headers", "*");//响应客户端的头部 允许携带Token 等等
        response.setHeader("Access-Control-Max-Age", "3600");   // 预检请求的结果缓存时间
        if (request.getMethod().equals("OPTIONS")){
            return false;
        }
        return true;
    }
    //在handler之后执行:进一步的响应定制
    @Override
    public void postHandle(HttpServletRequest request,
                           HttpServletResponse response, Object handler,
                           ModelAndView modelAndView) throws Exception {

    }
    //在页面渲染完毕之后,执行:资源回收
    @Override
    public void afterCompletion(HttpServletRequest request,
                                HttpServletResponse response, Object handler, Exception ex)
            throws Exception {

    }
}

package com.lzl.interceptor;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;

/**
 * --效率,是成功的核心关键--
 * token验证拦截器
 * @Author lzl
 * @Date 2023/3/21 08:19
 */

public class VerifyTokenInterceptor implements HandlerInterceptor {
    //在controller之前执行
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String token = request.getHeader("token");

        /*
        请求走到这,证明是非login方法(login方法已经被放行),验证token是否存在,不存在直接拦截
        存在?验证是否有效,有效放行,无效拦截
         */
        if (token == null){
            response.setStatus(500);
            Map<String, Object> map = new HashMap<>();
            response.setContentType("application/json;charset=utf-8");
            //is null or token unavailable
            map.put("msg","登陆状态已过期");
            map.put("code",500);
            response.getWriter().write(new ObjectMapper().writeValueAsString(map));
            return false;
        }
        return true;
    }
}

4.全局拦截器配置

在springboot中配置拦截器需要对拦截器进行注册

package com.lzl.config;

import com.lzl.interceptor.CrossOriginInterceptor;
import com.lzl.interceptor.VerifyTokenInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * --效率,是成功的核心关键--
 * 拦截器配置
 *
 * @Author lzl
 * @Date 2023/1/26 14:12
 */
@Configuration
public class GlobalInterceptorConfig implements WebMvcConfigurer {

    @Override   //拦截器配置
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new CrossOriginInterceptor()) //拦截器跨域注册对象
                .addPathPatterns("/**"); //指定要拦截的请求
        registry.addInterceptor(new VerifyTokenInterceptor())//token验证拦截器
                .addPathPatterns("/**")
                .excludePathPatterns("/login/*"); //排除请求
    }

}

三、业务逻辑

登陆状态验证的业务逻辑很简单,主要流程如下:
首先,用户没有登陆的情况下,用户无法访问除了login路径下的接口,以外的任何一个接口,访问的时候直接将页面重定向到登录页,若是前后端分离项目,则给前端返回错误状态码,让前端将用户跳转到登陆页。用户通过login接口输入正确的帐号和密码,后端生成一个token,并返回给前端,可以设置在请求头,或者以参数的形式传递,当前端拿到这个token时就可以访问登陆以外的页面了。

四、测试

当没有登陆状态时

在这里插入图片描述

访问login方法登陆,传回来一个token

在这里插入图片描述

携带token访问后端方法

在这里插入图片描述
访问成功


总结

这里只是对token简单的使用,在微服务架构中,token一般用于单点登陆验证,即登陆完成后,将token传到redis中存储,当访问除了登陆以外的其它服务时,去redis中查找。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

国内怎么注册chatgpt4-gptchat账号用多久

GPT &#xff08;Generative Pre-trained Transformer&#xff09;是一种强大的自然语言处理人工智能技术&#xff0c;可以用于各种语言任务&#xff0c;包括机器翻译、问答系统、自然语言生成等。作为一种技术&#xff0c;GPT并没有一个固定的玩法&#xff0c;但可以通过各种工…

VMware(虚拟机)安装CentOS7并且连接XShell教程

VMware&#xff08;虚拟机&#xff09;安装CentOS7 安装准备&#xff08;本文安装Centos7.6版本&#xff09; Centos7.6下载地址 创建 选择自定义 下一步默认 选择稍后安装 选择好下一步 修改下一步 根据自己的需求修改 虚拟机内存分配默认即可 先用默认后面…

【spring boot】在Java中操作缓存:

文章目录一、Jedis二、Spring Data Redis&#xff08;常用&#xff09;【1】pom.xml【2】application.yml【3】RedisConfig【4】RuiJiWaiMaiApplicationTests三、Spring Cache【1】常用注解&#xff1a;【2】使用案例【3】底层不使用redis&#xff0c;重启服务&#xff0c;内存…

两句话,ChatGPT帮我写一个打飞机的游戏

大家好&#xff0c;我是全村的希望今天的主题是让 chatGPT 来帮我们写一个打飞机的游戏记得我刚学 Python 的时候&#xff0c;看的那本很经典的入门书《Python 编程&#xff1a;从入门到实践》&#xff0c;里面就有小项目就是教你编写一个打飞机的游戏我那时候是对着书一个一个…

4.8 Beijing Rust Meetup | Call For Presenters

如果你有兴趣参与探讨Rust作为一种强调性能、安全和并发性的编程语言的各种应用、实践和无限可能性的头脑风暴&#xff0c;就一定不能错过这场来自达坦科技、南京大学、CloudWeGo、华为等技术专家带来的关于Rust编程语言相关应用的线下Meetup。我们的主题是Rust &#x1f4a1;X…

优维科技联合广发证券发起「CD持续交付一期项目复盘会」

2023年3月16日&#xff0c;由优维科技联合广发证券共同发起的「广发证券CD持续交付一期项目复盘会」在广发总部大厦闭幕。 复盘目的 为了对广发证券CD持续交付一期项目的总体执行请看落实“回头看”方阵&#xff0c;充分挖掘项目的落地价值&#xff0c;总结项目未来可持续性开…

基于springboot实现广场舞团管理系统演【附项目源码】

基于springboot实现广场舞团管理系统演开发语言&#xff1a;Java 框架&#xff1a;springboot JDK版本&#xff1a;JDK1.8 服务器&#xff1a;tomcat7 数据库&#xff1a;mysql 5.7 数据库工具&#xff1a;Navicat11 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#…

【OS面试】说说你对IO多路复用的理解

面试官&#xff1a; 说说你对IO多路复用的理解&#xff1f; 我&#xff1a; IO多路复用是一种高效地管理多个文件描述符&#xff08;FD&#xff09;的方式&#xff0c;它允许同时监听多个IO事件并在有事件发生时立即响应。 在传统的IO编程中&#xff0c;每个socket连接都需要…

Linux中,和,|和||区别

1、& 表示任务在后台执行&#xff0c;比如运行一个jar文件&#xff0c;但是希望是后台运行可以执行 java -jar test.jar & 2、&& 表示前一条命令执行成功时&#xff0c;才执行后一条命令 &#xff0c;如 echo step1‘ && echo ‘step2’ step1执行成功…

个人百度百科词条创建怎么收费?

互联网时代&#xff0c;百科营销起到举足轻重的作用&#xff0c;因为现在几乎人人都会在百度上去搜索答案&#xff0c; 当用户不了解你的时候&#xff0c;考虑是否和你合作的时候&#xff0c;也会下意识地去百度上搜索一下&#xff0c;看看有没有相关介绍。 这个时候创建一个百…

大屏使用dv-digital-flop定时刷新显示总人数

本文在基础上进行改进&#xff0c;后端使用若依后端IofTV-Screen: &#x1f525;一个基于 vue、datav、Echart 框架的物联网可视化&#xff08;大屏展示&#xff09;模板&#xff0c;提供数据动态刷新渲染、屏幕适应、数据滚动配置&#xff0c;内部图表自由替换、Mixins注入等功…

OSPF(开放式最短路径优先协议2)

OSPF的不规则区域 远离骨干的非骨干区域 不连续骨干 使用tunnel隧道 在R2和R3之间构建一条隧道&#xff0c;之后&#xff0c;将这个隧道宣告到Area0&#xff0c;相当于将R3这个非法的ABR设备合法 化。 使用vpn隧道解决不规则区域的问题 可能产生选路不佳可能造成重复更新因为…

vue打包上线利用插件去除 console

开发阶段&#xff0c;我们会编写大量的 console 语句用于测试 使用 build 命令打包时&#xff0c;会作为警告提出&#xff0c;因为上线之后&#xff0c;这些 console 语句是没有任何作用的&#xff0c;所以警告你应该移除 挨个文件手动删除的方法&#xff0c;会有两个问题 耗…

用ChatGPT学习多传感器融合中的基础知识

困惑与解答&#xff1a; 问题&#xff1a;匈牙利算法中的增广矩阵路径是什么意思 解答&#xff1a; 匈牙利算法是解决二分图最大匹配的经典算法之一。其中的增广矩阵路径指的是在当前匹配下&#xff0c;从一个未匹配节点开始&#xff0c;沿着交替路&#xff08;交替路是指依次…

C++的异常机制导致的crash问题

问题背景 最近工作中遇到了一个非常奇怪的crash问题&#xff0c;反反复复分析了好久。由于保密原因&#xff0c;这里只记录一下分析思路&#xff0c;不会涉及到代码。 初步log分析 tombstone显示这是一个abort&#xff1a;Cmdline: /vendor/bin/hw/vendor.qti.camera.provid…

HAL库(STM32CubeMX)之外部中断(STM32F103C8T6)

系列文章目录 HAL库&#xff08;STM32CubeMX&#xff09;——ADC学习总结&#xff08;包含单次/连续模式下的轮询/中断/DMA&#xff09;&#xff08;蓝桥杯STM32G431RBT6&#xff09; HAL库(STM32CubeMX)——DAC学习&#xff08;STM32G431RBT6&#xff09; HAL库(STM32CubeM…

【Spring源码】讲讲Bean的生命周期

1、前言 面试官&#xff1a;“看过Spring源码吧&#xff0c;简单说说Spring中Bean的生命周期” 大神仙&#xff1a;“基本生命周期会经历实例化 -> 属性赋值 -> 初始化 -> 销毁”。 面试官&#xff1a;“......” 2、Bean的生命周期 如果是普通Bean的生命周期&am…

ZYNQ中的AXI DMA

AXI DMA简介 DMA—直接内存访问—从外设到内存或者从内存到外设&#xff0c;不干涉CPU 硬核DMA和DMA软核如何选择 硬核—内存到内存、内存到PL&#xff08;通过GP传输速率低&#xff09;、内存到IO外设软核—从PL部分将大量数据进行搬运到内存&#xff08;连接到HP速率更高&am…

【中级软件设计师】—数据结构与算法基础考点总结篇(八)

【中级软件设计师】—数据结构与算法基础考点总结篇&#xff08;八&#xff09; 课程大纲 1.1 数组 按行存储&#xff1a;a(2*53)*2 其中a表示的就是a[0][0] 1.2 稀疏矩阵 本题采用代入法&#xff0c;首先代入A0,0&#xff0c;A0,0存入的位置是M【1】&#xff0c;把i0,j0分别…

[网络原理] TCP 协议的相关特性

TCP和UDP都是传输层的协议. 文章目录1. TCP协议格式2. TCP连接及断开连接管理2.1 三次握手2.2 四次挥手3. TCP可靠性机制3.1 确认应答3.2 超时重传4. 滑动窗口5. 流量控制6. 拥塞控制7. 延迟应答8. 捎带应答9. 面向字节流10. 异常情况1. TCP协议格式 TCP的特点是有连接,可靠性…