用户登录JWT技术,Redis存储token,登录拦截

2023-11-14

SpringBoot项目 用户登录JWT技术,登录拦截

1.JWT技术

登录使用JWT技术。

jwt 可以生成 一个加密的token,做为用户登录的令牌,当用户登录成功之后,发放给客户端。

请求需要登录的资源或者接口的时候,将token携带,后端验证token是否合法。

jwt 有三部分组成:A.B.C

A:Header,{“type”:“JWT”,“alg”:“HS256”} 固定

B:playload,存放信息,比如,用户id,过期时间等等,可以被解密,不能存放敏感信息

C: 签证,A和B加上秘钥 加密而成,只要秘钥不丢失,可以认为是安全的。

jwt 验证,主要就是验证C部分 是否合法。

pom.xml导入依赖包:

 <dependency>
     <groupId>io.jsonwebtoken</groupId>
     <artifactId>jjwt</artifactId>
     <version>0.9.1</version>
 </dependency>

 <dependency> <!--md5加密的依赖包-->
     <groupId>commons-codec</groupId>
     <artifactId>commons-codec</artifactId>
 </dependency>

jwt工具类,一般放在utils目录包下

import com.fwind.blog.service.impl.LoginServiceImpl;
import io.jsonwebtoken.Jwt;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.apache.commons.codec.digest.DigestUtils;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;
//jwt token令牌分为 A.B.C 三部分
public class JWTUtils {

    private static final String jwtToken = "123456abc@##$"; //token密钥,可自定义

	//生成token
    public static String createToken(Long userId){	//参数为用户Id
        Map<String,Object> claims = new HashMap<>();
        claims.put("userId",userId);

        JwtBuilder jwtBuilder = Jwts.builder()
                .signWith(SignatureAlgorithm.HS256, jwtToken) // 签发算法,秘钥为jwtToken
                .setClaims(claims) // body数据,要唯一,自行设置
                .setIssuedAt(new Date()) // 设置签发时间
                .setExpiration(new Date(System.currentTimeMillis() + 24 * 60 * 60 * 60 * 1000));//过期时间 一天的有效时间

        String token = jwtBuilder.compact();
        return token;
    }

	//检查token是否合法,合法则取出数据
    public static Map<String, Object> checkToken(String token){
        try {
            Jwt parse = Jwts.parser().setSigningKey(jwtToken).parse(token);
            return (Map<String, Object>) parse.getBody();
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }

	//以下是测试部分,可以删去
    public static void main(String[] args) {
        String token = JWTUtils.createToken(100l);
        System.out.println(token);
        Map<String, Object> map = JWTUtils.checkToken(token);
        System.out.println(map.get("userId"));
    }

}

2.登录注册-代码实现

前端请求参数
路径:/login
请求方式:POST

参数名 参数类型 说明
account String 账号
password String 密码

后端登录参数类

import lombok.Data;

@Data
public class LoginParam {

    private String account;

    private String password;

    private String nickname; //注册需要的参数
}

Controller层

@RestController
@RequestMapping("/")
public class LoginController {

    @Autowired
    private LoginService loginService;
	//用户登录
    @PostMapping("login")
    public Result login(@RequestBody LoginParam loginParam){
        return loginService.login(loginParam);
    }
	//退出登录
    @GetMapping("logout")	//Authorization为前端请求参数,保存在头部
    public Result logout(@RequestHeader("Authorization") String token){
        return loginService.logout(token);
    }
    //注册
    @PostMapping("register")
    public Result register(@RequestBody LoginParam loginParam){
        return loginService.register(loginParam);
    }
}

Service层

@Service
@Transactional //事务:若添加用户出错(如服务器崩了),数据库不能有数据
public class LoginServiceImpl implements LoginService {

    private static final String slat = "rngnb!@###"; //加密盐

    @Autowired
    private SysUserService sysUserService;

    @Autowired
    private RedisTemplate<String,String> redisTemplate;

    @Override
    public Result login(LoginParam loginParam) {
        /*
        1.检查参数是否合法
        2.根据用户名密码查询user是否存在
        3.不存在 登陆失败
        4.存在使用jwt 生成token返回给前端
        5.将token放入redis中,redis token:user信息,设置过期时间
        (登录认证先认证字符串是否合法,再去redis认证是否存在)
         */
        String account = loginParam.getAccount();
        String password = loginParam.getPassword();
        if(StringUtils.isBlank(account)||StringUtils.isBlank(password)){
            return Result.fail(ErrorCode.PARAMS_ERROR.getCode(),ErrorCode.PARAMS_ERROR.getMsg());
        }
        password = DigestUtils.md5Hex(password +slat); //密码加密(增加加密盐),因为数据库保存的密码经过了md5加密
        SysUser user = sysUserService.findUser(account, password);  //找到对应的user对象
        if(user == null){ //若用户不存在,返回错误信息(自定义错误码)
            return Result.fail(ErrorCode.ACCOUNT_PWD_NOT_EXIST.getCode(),ErrorCode.ACCOUNT_PWD_NOT_EXIST.getMsg());
        }
		//用户存在,则创建token
        String token = JWTUtils.createToken(user.getId());
        //将user对象转化为Json字符串放入redis
        redisTemplate.opsForValue().set("TOKEN_"+token, JSON.toJSONString(user),1, TimeUnit.DAYS); //1天过期时间

        return Result.success(token); //token返回给前端
    }

    //检验token,主要给拦截处理器使用
    @Override
    public SysUser checkToken(String token) {
        /*
        1.token合法性校验(是否为空,解析是否成功,redis是否存在)
        2.校验失败,返回错误;校验成功,返回结果LoginVo
         */
        if(StringUtils.isBlank(token)){
            return null;
        }
        //判断token是否能解析成功
        Map<String, Object> stringObjectMap = JWTUtils.checkToken(token);
        if(stringObjectMap == null){
            return null;
        }

        //从rides拿到对应user json字符串
        String userJSON = redisTemplate.opsForValue().get("TOKEN_" + token);
        if(StringUtils.isBlank(userJSON)){
            return null;
        }
        //把user json字符串解析为user对象
        SysUser sysUser = JSON.parseObject(userJSON, SysUser.class);
        return sysUser;
    }

    //退出登录
    @Override
    public Result logout(String token) {
        //后端只需要清除redis的token
        redisTemplate.delete("TOKEN_"+token);
        return Result.success(null);
    }

    //注册用户
    @Override
    public Result register(LoginParam loginParam) {
        String account = loginParam.getAccount();
        String password = loginParam.getPassword();
        String nickname = loginParam.getNickname();
        if(StringUtils.isBlank(account)||StringUtils.isBlank(password)||StringUtils.isBlank(nickname)){
            return Result.fail(ErrorCode.PARAMS_ERROR.getCode(),ErrorCode.PARAMS_ERROR.getMsg());
        }
        SysUser user = sysUserService.findUserByAccount(account);
        if(user != null){
            return Result.fail(ErrorCode.ACCOUNT_HAS_EXIST.getCode(), ErrorCode.ACCOUNT_HAS_EXIST.getMsg());
        }
        SysUser sysUser = new SysUser();
        sysUser.setNickname(nickname);
        sysUser.setAccount(account);
        sysUser.setPassword(DigestUtils.md5Hex(password+slat)); //密码加密
        sysUser.setCreateDate(System.currentTimeMillis());
        sysUser.setLastLogin(System.currentTimeMillis());
        sysUser.setAvatar("/static/img/logo.b3a48c0.png");
        sysUser.setAdmin(1); //1 为true
        sysUser.setDeleted(0); // 0 为false
        sysUser.setSalt("");
        sysUser.setStatus("");
        sysUser.setEmail("");
        this.sysUserService.save(sysUser); //mybatis-plus有自己的save方法 加了this用的是自己的save方法,id会自动增加

        //保存用户后,自动登录(同上)
        String token = JWTUtils.createToken(sysUser.getId());
        //将user对象转化为Json字符串放入redis
        redisTemplate.opsForValue().set("TOKEN_"+token, JSON.toJSONString(sysUser),1, TimeUnit.DAYS);

        return Result.success(token); //token返回给前端
    }


    //以下为测试,打印md5加密后的字符串,如密码123
    public static void main(String[] args) {
        System.out.println(DigestUtils.md5Hex("123" + LoginServiceImpl.slat));
    }
}

3.登录拦截

使用拦截器,进行登录拦截,遇到需要登录才能访问的接口时,若未登录,拦截器直接返回,并跳转登录页面。

WebMvcConfig.class
配置类,增加拦截器 ,一般在/config包下

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Autowired
    private LoginInterceptor loginInterceptor;

    @Override   //跨域配置
    public void addCorsMappings(CorsRegistry registry) {
        //跨域配置,不可设置为*,不安全, 前后端分离项目,可能域名不一致
        //本地测试 端口不一致 也算跨域                        //允许该网址(8080端口)访问本域名
        registry.addMapping("/**").allowedOrigins("http://localhost:8080");
        
    }

    @Override  //增加拦截器
    public void addInterceptors(InterceptorRegistry registry) {
        /* 拦截所有路径,除了登录和注册接口 */
        registry.addInterceptor(loginInterceptor)
        .addPathPatterns("/**")
        .excludePathPatterns("/login")
        .excludePathPatterns("/register");
    }
}

登录拦截处理类,一般放/handler包下。
LoginInterceptor.class

import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Component
@Slf4j  //日志
public class LoginInterceptor implements HandlerInterceptor {

    @Autowired
    private LoginService loginService;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //在执行controller方法(handler)之前进行执行
        /**
         * 1.需要判断请求的接口路径是否为HandlerMethod(controller方法)
         * 2.判断token是否为空,如果为空 未登录
         * 3.token不为空,登录验证 loginService checkToken
         * 4.如果认证成功,放行
         */
        if(!(handler instanceof HandlerMethod)){
            //有可能是资源handler 如RequestResourceHandler,访问静态资源,直接放行
            return true;
        }
        String token = request.getHeader("Authorization"); //获取浏览器头部token

		//打印日志
        log.info("=================request start===========================");
        String requestURI = request.getRequestURI();
        log.info("request uri:{}",requestURI);
        log.info("request method:{}",request.getMethod());
        log.info("token:{}", token);
        log.info("=================request end===========================");

        if(StringUtils.isBlank(token)){ //检查token是否为空
            Result result = Result.fail(ErrorCode.NO_LOGIN.getCode(), "未登录");
            response.setContentType("application/json;charset=utf-8");
            response.getWriter().print(JSON.toJSONString(result)); //未登录,传给前端信息
            return false;
        }

        SysUser sysUser = loginService.checkToken(token);
        if(sysUser == null){  //检查token是否有用户信息
            Result result = Result.fail(ErrorCode.NO_LOGIN.getCode(), "未登录");
            response.setContentType("application/json;charset=utf-8");
            response.getWriter().print(JSON.toJSONString(result)); //未登录,传给前端信息
            return false;
        }
        //登录成功,放行,并将用户信息保存在 本地用户类(后面有介绍)
        UserThreadLocal.put(sysUser); //放入本地local
        return true;
    }

    @Override  //所以的方法都执行完之后,收尾工作
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        //如果不删除 ThreadLocal中用完的信息,会有内存泄露的风险
        UserThreadLocal.remove();
    }
}

4.本地用户信息

拦截处理后,把用户信息保存在本地,可能存在多个用户同时登录,这里采用多线程,这样互不干扰。

//保存登录后的用户信息(多线程)
public class UserThreadLocal {

    private UserThreadLocal(){} //私有构造方法

    //线程变量隔离(每个线程都有各自的LOCAL,互不干扰)  ThreadLocal屏蔽了线程间的通讯,避免了多线程问题
    private static final ThreadLocal<SysUser> LOCAL = new ThreadLocal<>(); //唯一初始化变量LOCAL

    public static void put(SysUser sysUser){
        LOCAL.set(sysUser);   //将user放入
    }

    public static SysUser get(){
        return LOCAL.get();
    }

    public static void remove(){
        LOCAL.remove();
    }
}

其他service层有逻辑需要可以直接获取本地用户,但要保证该controller层的路径增加了拦截,否则不会把用户信息保存在UserThreadLocal,如果前面拦截了所有路径则不用考虑了。

SysUser sysUser = UserThreadLocal.get();//要增加路径拦截,否则拿不到用户
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

用户登录JWT技术,Redis存储token,登录拦截 的相关文章

  • Java中RandomAccessFile的并发

    我正在创建一个RandomAccessFile对象通过多个线程写入文件 在 SSD 上 每个线程都尝试在文件中的特定位置写入直接字节缓冲区 并且我确保线程写入的位置不会与另一个线程重叠 file getChannel write buffe
  • 检查发送到网页的请求数

    我正在编写一个 Java 多线程应用程序 它可以访问不同 Web 服务器的数百万个 有时甚至数十亿个 URL 这个想法是检查这些 URL 是否给出有效的 200OK 响应或 404 其他代码 我如何知道我的程序是否不会在他们的服务器上造成高
  • RxJava + Retrofit 2 的正确使用方法

    我有这样的 JSON success true data id 29 name u0420 u0435 u0441 u0442 u043e u0440 u0430 u0446 u0456 u044f u0411 u0430 u0447 u0
  • 如何实现具有LinkedHashMap类似功能的ConcurrentHashMap?

    我用过LinkedHashMap with accessOrdertrue 并同时允许最多 500 个条目作为数据的 LRU 缓存 但由于可扩展性问题 我想转向一些线程安全的替代方案 ConcurrentHashMap在这方面似乎不错 但缺
  • 如何在Spring Boot中初始化一次MongoClient并使用它的方法?

    您好 我正在尝试导出MongoClient在 Spring Boot 中成功连接后 我尝试在其他文件中使用它 这样我就不必每次需要在 MongoDB 数据库中进行更改时都调用该连接 连接非常简单 但目标是将应用程序连接到我的数据库一次 然后
  • jpa2 CriteriaBuilder order by “ORDER BY 表达式必须出现在选择列表中”

    我正在写一个查询标准生成器 但无法添加order by子句 因为它随消息一起抛出错误ORDER BY 表达式必须出现在选择列表中这是我的实体 public class A Integer aId ManyToOne JoinColumn n
  • 如何为java注释处理器编写自动化单元测试?

    我正在尝试使用 java 注释处理器 我可以使用 JavaCompiler 编写集成测试 事实上我现在正在使用 hickory 我可以运行编译过程并分析输出 问题 即使我的注释处理器中没有任何代码 单个测试也会运行大约半秒 对于以 TDD
  • 从字符串生成密钥?

    我需要从字符串生成一个密钥 以便我始终可以从同一字符串创建相同的密钥 具体来说是一个Key对象 这样我就可以用它来创建Cipher进而创建SealedObject 这在 Java 中可行吗 我应该考虑什么类 方法组合才能做到这一点 对于 A
  • AffineTransform.rotate() - 如何同时缩放、旋转和缩放?

    我有以下代码 它可以完成我想要绘制一个上面有一些棋子的棋盘的 第一部分 Image pieceImage getImage currentPiece int pieceHeight pieceImage getHeight null dou
  • Android 游戏偶尔出现延迟

    我正在用 Java 制作一个简单的 Android 游戏 我注意到每 20 40 秒就会出现一些烦人的延迟 首先 我认为它们是由垃圾收集器引起的 但当我检查 LogCat 时 我发现游戏滞后时没有垃圾收集 每当游戏开始滞后时 我都会标记日志
  • c和java语言中的换行符

    现在行分隔符取决于系统 但在 C 程序中我使用 n 作为行分隔符 无论我在 Windows 还是 Linux 中运行它都可以正常工作 为什么 在java中 我们必须使用 n 因为它与系统相关 那么为什么我们在c中使用 n 作为新行 而不管我
  • 按文件名过滤 eclipse 中的警告

    我们使用 Eclipse 进行 Java 开发 并使用 Maven 将 JSP 编译成 servlet 以便在嵌入式 Jetty 实例中使用 这意味着要从 Eclipse 运行该项目 我必须包含 target jsp source 作为源文
  • org.apache.commons.codec.digest.Md5Crypt.md5Crypt 函数。 linux下出现异常,windows下正常

    我们正在使用commons codec加密密码 使用org apache commons codec digest Md5Crypt md5Crypt功能 在Windows环境下工作正常 但在CentOS上却抛出异常 我们有3台centOS
  • 反应式 Spring Webflux REST 控制器内部重定向

    我正在为 spring 反应项目创建简单的控制器服务器 在设置重定向到另一个位置时 我在调用时发现错误http localhost 8080 There was an unexpected error type Internal Serve
  • java中日期转换dd-MMM-yyyy到dd-MM-yyyy

    在Java中将23 Mar 2011转换为23 03 2011的最简单方法是什么 感谢大家 这似乎解决了这个问题 try Calendar cal Calendar getInstance cal setTime new SimpleDat
  • 使用 Box2d(适用于 Android)进行碰撞检测?

    有人可以解释一下使用 box2d for android 进行碰撞检测的工作原理吗 我无法理解 BBContactListener 以什么方式工作 BBContactListener listener new BBContactListen
  • 更新分页。是否可以?

    他们是否存在一些方法来处理更新分页 例如我有 100 行类型 Id private Integer id Column private boolean flag Column private Date last 一开始它们看起来像 id f
  • 如何创建具有同等时间元素的 JavaFX 转换?

    我正在尝试 JavaFX 和动画 尤其是PathTransition 我正在创建一个简单的程序 使球 弹跳 而不使用QuadCurveTo班级 到目前为止 这是我的代码 Ellipse ball new Ellipse 375 250 10
  • Retrofit 2.0:预期为 BEGIN_OBJECT,但在第 1 行第 1 列路径 $ [重复] 处为 STRING

    这个问题在这里已经有答案了 我在邮递员上传递了更新用户请求并获得了成功的响应 参见图片 现在当我尝试使用 Retrofit 2 在我的应用程序中执行相同操作时 出现错误 com google gson JsonSyntaxException
  • 获取Java中ResultSet返回的行数

    我用过一个ResultSet返回一定数量的行 我的代码是这样的 ResultSet res getData if res next System out println No Data Found while res next code t

随机推荐

  • Linux安装anaconda3是否初始化的区别

    Linux安装anaconda3提示是否希望安装程序通过运行conda init来初始化Anaconda3 Do you wish the installer to initialize Anaconda3 by running conda
  • TSP(旅行者问题)——动态规划详解

    1 问题定义 TSP问题 旅行商问题 是指旅行家要旅行n个城市 要求各个城市经历且仅经历一次然后回到出发城市 并要求所走的路程最短 假设现在有四个城市 0 1 2 3 他们之间的代价如图一 可以存成二维表的形式 图一 现在要从城市0出发 最
  • OpenMP GPU并行计算

    GPU并行计算 一 C源码 gpustbench c 1 OpenMP与MPI 二 Linux中编译运行 三 执行命令 四 查看运行结果 一 C源码 gpustbench c GPU并行计算能力 计算矩阵行列式 任一行的各元素与其对应的代数
  • Linux环境下,安装libevent库

    前言 最近在进行网络编程的学习 在安装libevent库时 遇到了各种各样的问题 最后通过一条条的去搜索问题关键字 费尽千辛万苦 终于完成了安装 也能够成功的运行起来其中所提供的案例代码 所以在这里将各种零碎的问题以及解决方案整理一下 帮助
  • 联想 计算机无线网络设置方法,联想笔记本无线网络开关,详细教您联想笔记本无线网络开关...

    联想笔记本是联想集团生产的可携带的笔记本 可帮助我们娱乐 办公 笔记本电脑在生活办公中使用很方便 有时候想要连接无线网却看不到图标 一般来说笔记本电脑都配有无线网络快捷开关 那么怎么打开笔记本无线网络 下面 小编给大家介绍联想笔记本无线网络
  • CARLA pygame window界面大小调节两种方法-Ubuntu18.04

    CARLA pygame window界面大小调节两种方法 Ubuntu18 04 文章目录 前言 一 crala安装 二 pygame window界面界面大小调节方法 1 打开manual control py修改分辨率 2 打开设置
  • 记一次安装plsql,踩坑踩到人麻了!

    大家好 我是鱼尾 开门见山 有事说事吧 记一次安装plsql 踩坑踩到人麻了 说到这里了 那么什么是plsql呢 PL SQL也是一种程序语言 叫做过程化SQL语言 Procedural Language SQL PL SQL是Oracle
  • 无名图书(网站)

    首先 这个电子书资源网站 里面的资源涵盖面广非常的齐全 包含有文学类 社会文化 历史 经济 自然科学 理工科 美食旅行 政治 计算机 设计 思想 健康 生物 建筑 绘本 天文等等 完全能满足日常使用需求 网站支持搜索功能 小伙伴们可以通过搜
  • Blender2.5快捷键

    Blender2 5快捷键 整理了最全面的快捷键和解释 希望大家继续补充 Basics 基础 CTRL U Save as Default 保存界面 Right Click Select 选择 Middle Click Pan 平移视角 M
  • python大作业爬虫_Python大作业---微博爬虫及简单数据分析

    刚开始学python 选了这个题目 把代码放上来留念 没有用到很流行的框架 所以代码量挺大 GUI用wxpython写的 coding UTF 8 import os import re import requests import sys
  • 上传本地jar包文件到私服

    一 公司的项目上传到公司私服 使用idea中maven的install将项目打包 放到本地仓库 install三个关键步骤 将项目打包成jar包放到项目的target目录下 这一步等同于mvn package命令的操作 将jar包insta
  • java 比较日期差值_日期的大小比较及差值计算

    一 LocalDate 的 isBefore isAfter 返回值为 boolean 类型 public static void main String args LocalDate ld LocalDate now LocalDate
  • Ubuntu系统下安装NVIDIA驱动

    介绍两种不同的方法 这两种方法基本不会出现任何问题 1 直接使用系统的apt get进行nvidia的安装 具体参考自这篇https blog csdn net breeze5428 article details 80013753 具体步
  • [论文阅读] (10)基于溯源图的APT攻击检测安全顶会总结

    娜璋带你读论文 系列主要是督促自己阅读优秀论文及听取学术讲座 并分享给大家 希望您喜欢 由于作者的英文水平和学术能力不高 需要不断提升 所以还请大家批评指正 非常欢迎大家给我留言评论 学术路上期待与您前行 加油 前一篇文章分享了S P201
  • 更新预告:chatGPT知识树。

    从一个知识点出发 无限扩展到无数个子知识点 是学习 了解其他行业知识 专业技能的利器 10分钟 就可以对一个行业 一个专业有大概 又专业的了解 简单列2个例子 让你感受下神的强大 上线时间 2023 9 7 体验地址 https ppwor
  • 浅谈Android原生开发现状,终究是错付了

    浅谈Android原生开发现状 终究是错付了 客户端3年内必死 小程序 跨端方案盛行 很多公司已经开始裁客户端了 这不是危言耸听 不少同行已经发声 客户端面临的危机前所未有 前端跨平台和小程序蚕食移动端市场 客户端行业内部内卷严重 同行一直
  • planet-lab平台的布置

    最近需要把国家自然基金项目赶快结题 所以导师也催的紧 正好自己也在研究网格和高性能计算 所以老板就把部署planet lab环境的任务交给我 鄙人英语很烂 所以花了很长时间的去读指导书 最后基本上搞定 但是还有问题 希望网友们能给我点解答
  • Android Binder 系统级使用demo

    Android System Binder Usage 添加系统级服务Java C Server Client https github com qianjigui android system service exampleAndroid
  • 华为OD机试 -自动售货系统(C++ & Java & JS & Python)

    描述 1 总体说明 考生需要模拟实现一个简单的自动售货系统 实现投币 购买商品 退币 查询库存商品及存钱盒信息的功能 系统初始化时自动售货机中商品为6种商品 商品的单价参见1 1规格说明 存钱盒内放置1元 2元 5元 10元钱币 商品数量和
  • 用户登录JWT技术,Redis存储token,登录拦截

    SpringBoot项目 用户登录JWT技术 登录拦截 1 JWT技术 登录使用JWT技术 jwt 可以生成 一个加密的token 做为用户登录的令牌 当用户登录成功之后 发放给客户端 请求需要登录的资源或者接口的时候 将token携带 后