登录管理——认证方案(JWT、拦截器、ThreadLocal、短信验证)
两种常见的认证方案
基于Session认证
- 登录状态信息保存在服务器内存中,若访问量增加,单台节点压力会较大
- 集群环境下需要解决集群中的各种服务器登录状态共享问题

解决方案:将登录状态保存的Redis中,从Redis中查找登录状态
基于Token认证
- 登录状态保存在客户端,服务器没有存储开销
- 客户端发起的每个请求自身均携带登录状态,所以即使后台为集群,也不会面临登录状态共享的问题

Token登录方案




登录流程如下

登录管理使用的三个接口
- 获取图形验证码,通过easy-captcha依赖引入工具
- 登录,通过jjwt-api jjwt-impl jjwt-jackson 依赖引入工具
- 获取登录用户个人信息
为了使得所有受保护的接口增加验证JWT合法性逻辑,通过HandlerInterceptor拦截实现
common模块中,创建JWT工具类
public class JwtUtil{private static SecretKey secretKey = Keys.hmacShaKeyFor("任意复杂字符串大于指定字节数".getBytes());public static String createToken(Long userId,String username){//官方设置用set,自定义设置用claimString jwt = Jwts.builder().setExpiration(new Date(System.currentTimeMillis()+3600000)).setSubject("LOGIN_USER").claim("userId",userId).claim("username",username).signWith(secretKey,SignatureAlgorithm.HS256)//设置签名以及所使用的算法种类.compact();return jwt;}//校验token是否合法public static Claims parseToken(String token){if(token==null){throw new LeaseException(ResultCodeEnum.ADMIN_LOGIN_AUTH);}try{JwtParser jwtParser = JWTS.parserBuilder().setSigningKey(secretKey).build();Jws<Claims> claimsJws = jwtParser.parse(token);return claimsJws.getBody();}catch(ExpiredJwtExpection e){throw new LeaseException(ResultCodeEnum.TOKEN_EXPIRED);}catch(JwtException e){throw new LeaseException(ResultCodeEnum.TOKEN_INVALID);}}
}
①、Controller接口
@Tag(name="后台管理系统登录")
@RestController
@RequestMapping("/admin")
public class LogicController{@Autowiredprivate LoginService service;@Operation(summary="获取图形验证码")@GetMapping("login/captcha")public Result<CaptchaVo> getCaptcha(){CaptchaVo result = servcie.getCaptcha();return Result.ok(result);}@Operation(summary="登录")@GetMapping("login")public Result<String> login(@RequestBody Login loginVo){String jwt = service.login(loginVo);return Result.ok(jwt);}@Operation(summary="获取登录用户个人信息")@GetMapping("info")public Result<SystemUserInfoVo> info(@RequestHeader("access-token") String token){//将请求的某个header绑定到tokenClaims claims = JwtUtil.parseToken(token);Long userId = claims.get("userId",Long.class);SystemUserInfoVo systemUserInfoVo = service.getLoginUserInfoById(userId);return Result.ok(systemUserInfoVo);}
}
②、接口及其实现类
@Service
public class LoginServiceImpl implements LoginService{@Autowiredpirvate String RedisTemplate stringRedisTemplate;@Autowiredprivate SystemUserMapper systemUserMapper;@Overridepublic CaptchaVo getCaptcha(){SpecCaptcha specCaptcha = new SpecCaptcha(130,48,4);String code = specCaptcha.text().toLowerCase();//Redis中的key命名——项目名:功能模块名 admin:loginString key = "admin:login:" + UUID.randomUUID();//后台管理系统登录模块stringRedisTemplate.opsForValue().set(key,code,60,TimeUnit.SECONDS)return new CaptchaVo(specCpatcha.toBase64(),key);}@Overridepublic String login(LoginVo loginVo){if(loginVo.getCaptchaCode()==null){throw new LeaseException(ResultCodeEnum.ADMIN_CAPTCHA_CODE_NOTFOUND);}//从Redis获取保存的验证码String code = stringRedisTemplate.opsForValue().get(loginVo.getCaptchaKey());if(code==null){//验证码过期throw new LeaseException(ResultCodeEnum.ADMIN_CAPTCHA_CODE_EXPIRED);}if(!code.equals(loginVo.getCaptchaCode().toLowerCase())){throw new LeaseException(ResultCodeEnum.ADMIN_CAPTCHA_CODE_ERROR);}//LambdaQueryWrapper<SystemUser> queryWrapper = new LambdaQueryWrapper<>();//queryWrapper.eq(SystemUser::getUsername,loginVo.getUsername());/**由于该接口使用通用查询会导致和实体类的属性注解select=false查询功能,不显示密码字段的冲突该接口出现了空指针异常所以采用自定义查询*///SystemUser systemUser = systemUserMapper.selectOne(queryWrapper);SystemUser systemUser = systemUserMapper.selectOneByUsername(queryWrapper);if(systemUser==null){throw new LeaseException(ResultCodeEnum.ADMIN_ACCOUNT_NOT_EXIST_ERROR);}if(systemUser.getStatus()==BaseStatus.DISABLE){throw new LeaseException(ResultCodeEnum.ADMIN_ACCOUNT_DISABLED_ERROR);}if(systemUser.getPassword().equals(DigestUtils.md5Hex(loginVo.getPassword))){throw new LeaseException(ResultCodeEnum.ADMIN_ACCOUNT_ERROR);}//生成JWTreturn JwtUtil.createToken(systemUser.getId(),systemUser.getUsername());}@Overridepublic SystemUserInfoVo getLoginUserInfoById(Long userId){SystemUser systemUser = systemUserMapper.selectById(userId);SystemUserInfo systemUserInfoVo = new SystemUserInfo();systemUserInfoVo.setName(systemUser.getName());systemUserInfoVo.setAvatarUrl(systemUser.getAvatarUrl());return systemUserInfoVo;}
}
③、为登录之后才能访问的接口增加验证JWT合法性的逻辑(拦截器)
@Component
public class AuthenticationInterceptor implements HandlerInterceptor{//Controller接口之前执行@Overridepublic boolean preHandle(HttpServletRequest request,HttpServletResposne response,Object handler)throw Exception{//从请求头获取tokenString token = request.getHeader("access-token");//同前端约定好的键值对JwtUtil.parseToken(token);return true;}
}
注册拦截器
@Configuration
public class WebMvcConfiguration implements WebMvcConfigurer{@Autowiredprivate AuthenticationIntercpetor authenticationIntercpetor;@Overridepublic void addInterceptor(InterceptorRegistry registry){registry.addInterceptor(this.authenticationIntercpetor).addPathPatterns("/admin/**")//拦截admin下所有路径.excludePathPatterns("/admin/login/**");}
}
关于拦截器的重复解析(一次在拦截器中,一次是controller接口中)
JwtUtil.parseToken(token)
所以要通过拦截器的解析结果保存起来,由于拦截器限制性,通常将结果保存在ThreadLocal中

①、在common模块中创建工具类
public class LoginUserHolder{public static ThreadLocal<LoginUser> threadLocal = new ThreadLocal<>();public static void setLoginUser(LoginUser loginUser){threadLocal.set(loginUser);}public static LoginUser getLoginUser(){return threadLocal.get();}public static void clear(){threadLocal.remove();}
}
同时创建LoginUser
@Data
@AllArgsConstrctor
public class LoginUser{private Long userId;private String username;
}
②、修改拦截器
@Component
public class AuthenticationInterceptor implements HandlerInterceptor{@Overridepublic boolean preHandle(HttpServletRequest request,HttpServletResponse response,Object handler)throw Exception{String token = request.getHeader("access-token");Claims claims = JwtUtil.parseToken(token);Long userId = claims.get("userId",Long.class);String username = claims.get("username",String.class);LoginUserHolder.setLoginUser(new LoginUser(userId,username));return true;}//清理线程池的内容@Overridepublic void afterCompletion(HttpServletRequest request,HttpServletResponse response,Object handler){LoginUserHolder.clear();}
}
③、改造Controller
@Operation(sumary="获取登录用户个人信息")
@GetMapping("info")
public Result<SystemUserInfoVo> info(){Long userId = LoginUserHolder.getLoginUser().getUserId();SystemUserInfoVo systemUserInfo = service.getLoginUserInfoById(userId);return Result.ok(systemUserInfoVo);
}
手机移动端登录的具体流程

①、Controller
@Tag(name="登录管理")
@RestController
@RequestMapping("/app/")
public class LoginController{@Autowiredprivate LoginService service;@GetMapping("login/getCode")@Operation(summary="获取短信验证码")public Result getCode(@RequestParam String phone){service.getCode(phone);return Result.ok();}@PostMapping("login")@Operation(summary="登录")public Result<String> login(@RequestBody LoginVo loginVo){return Result.ok()}@GetMapping("info")@Operation(summary="获取登录用户信息")public Result<UserInfoVo> info(){retur Result.ok();}
}
②、service
Ⅰ、登录的service及其实现类
public interface LoginService{void getCode(String phone);
}
@Service
public class LoginServiceImpl implements LoginService{@Autowiredprivate SmsService smsService;@Autowiredprivate StringRedisTemplate redisTemplate;@Overridepublic void getCode(String phone){}
}
Ⅱ、发送短信的service及其实现类
public interface SmsService{void sendCode(String phone,String code);
}
@Service
public class SmsServiceImpl implements SmsService{@Autowiredprivate Client client;@Overridepublic void sendCode(String phone,String code){SendSmsRequest request = new SendSmsRequest();requst.setPhoneNumber(phone);request.setSignName("阿里云短信测试");request.setTemplateCode("SMS_154950909");request.setTemplateParam("{\"code\":\""+code+"\"}");try{client.sendSms(request);}catch(Exception e){throw new RuntimeException(e);}}
}

调用阿里云短信服务,common模块中依赖:dysmsapi20170525
applicaiton.yml增加:access-key-id(账号) access-key-secret(密码) endpoint



对配置类进行映射,common模块中
@Data
@ConfigurationProperties(prefix="aliyun.sms")
public class AliyunSMSProperties{private String accessKeysId;private String accessKeySecret;private String endpoint;
}
将映射到的参数进行创建访问阿里云的客户端配置
@Configuration
@EnableConfigurationProperties(AliyunSMSProperties.class)
@ConditionalOnProperty(name="aliyun.sms.endpoint")//条件注解,放置其他模块没有配置访问参数而报错
public class AliyunSMSConfiguration{@Autowiredprivate AliyunSMSProperties properties;@Beanpublic Client createClient(){Config config = new Config();config.setAccessKeyId(properties.getAccessKeyId());config.setAccessKeySecret(properties.getAccessKeySecret());config.setEndpoint(properties.getEndpoint())try{return new Client(config);}catch(Exception e){throw new RuntimeException(e);}}
}
相关文章:
登录管理——认证方案(JWT、拦截器、ThreadLocal、短信验证)
两种常见的认证方案 基于Session认证 登录状态信息保存在服务器内存中,若访问量增加,单台节点压力会较大集群环境下需要解决集群中的各种服务器登录状态共享问题 解决方案:将登录状态保存的Redis中,从Redis中查找登录状态 基于…...
Java实现LFU缓存策略实战
LFU算法原理在Java中示例实现集成Caffeine的W-TinyLFU策略缓存实战总结LFU与LRU稍有不同,LFU是根据数据被访问的频率来决定去留。尽管它考虑了数据的近期使用,但它不会区分数据的首次访问和后续访问,淘汰那些访问次数最少的数据。 这种缓存策略主要用来处理以下场景: 数据…...
物业系统改革引领行业智能化管理与提升服务质量的新征程
内容概要 在当今迅速变化的社会中,物业系统改革正在悄然推动行业的智能化管理进程。物业管理作为一个古老而传统的领域,面临着诸多挑战,包括效率低下、业主需求难以满足等。数字化转型为这一现象注入了新活力,帮助物业公司通过先…...
QT+mysql+python 效果:
# This Python file uses the following encoding: utf-8 import sysfrom PySide6.QtWidgets import QApplication, QWidget,QMessageBox from PySide6.QtGui import QStandardItemModel, QStandardItem # 导入需要的类# Important: # 你需要通过以下指令把 form.ui转为ui…...
动手学图神经网络(4):利用图神经网络进行图分类
利用图神经网络进行图分类:从理论到实践 引言 在之前的学习中,大家了解了如何使用图神经网络(GNNs)进行节点分类。本次教程将深入探讨如何运用 GNNs 解决图分类问题。图分类是指在给定一个图数据集的情况下,根据图的一些结构属性对整个图进行分类,而不是对图中的节点进…...
【Block总结】PConv,部分卷积|即插即用
论文信息 标题: Run, Don’t Walk: Chasing Higher FLOPS for Faster Neural Networks 论文链接: https://arxiv.org/pdf/2303.03667 GitHub链接: https://github.com/JierunChen/FasterNet 创新点 该论文的核心创新在于提出了一种新的运算符——部分卷积(PCo…...
接口使用实例(1)
大家好,今天我们来看看接口的一些实例,关于如何定义和实现接口,相信通过这些例子,我们能有一些清晰的认知。 先定义一个学生类: 再给定一个学生数组,对这个对象数组中的元素进行排序(按分数排&…...
动态规划DP 最长上升子序列模型 总览
最长上升子序列模型 1. 最长上升子序列 1.1 怪盗基德的滑翔伞 1.1.1 登山 1.1.2 合唱队形 1.2 友好城市 1.3 最长上升子序列和 1.4 导弹拦截...
网络工程师 (7)进程管理
一、进程相关的概念 (一)定义 进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,也是操作系统结构的基础。进程是程序的一次执行实例,具有动…...
登录授权流程
发起一个网络请求需要:1.请求地址 2.请求方式 3.请求参数 在检查中找到request method,在postman中设置同样的请求方式将登录的url接口复制到postman中(json类型数据)在payload中选择view parsed,将其填入Body-raw中 …...
Flutter_学习记录_导航和其他
Flutter 的导航页面跳转,是通过组件Navigator 和 组件MaterialPageRoute来实现的,Navigator提供了很多个方法,但是目前,我只记录我学习过程中接触到的方法: Navigator.push(), 跳转下一个页面Navigator.pop(), 返回上一…...
二叉树-堆(补充)
二叉树-堆 1.二叉树的基本特性2.堆2.1.堆的基本概念2.2.堆的实现2.2.1.基本结构2.2.2.堆的初始化2.2.3.堆的销毁2.2.4.堆的插入2.2.5.取出堆顶的数据2.2.6.堆的删除2.2.7.堆的判空2.2.8.堆的数据个数2.2.9.交换2.2.10.打印堆数据2.2.11.堆的创建2.2.12.堆排序2.2.13.完整代码 3…...
Big Bird:适用于更长序列的Transformer模型
摘要 基于Transformer的模型,如BERT,已成为自然语言处理(NLP)中最成功的深度学习模型之一。然而,它们的一个核心限制是由于其全注意力机制,对序列长度的二次依赖(主要是在内存方面)…...
doris:MySQL Load
Doris 兼容 MySQL 协议,可以使用 MySQL 标准的 LOAD DATA 语法导入本地文件。MySQL Load 是一种同步导入方式,执行导入后即返回导入结果。可以通过 LOAD DATA 语句的返回结果判断导入是否成功。一般来说,可以使用 MySQL Load 导入 10GB 以下的…...
电感的饱和、温升、额定电流
电感饱和电流的定义: 电感的感值下降30%时候对应的电流 注意不要让电感的瞬间电流大于饱和电流: 温升电流: 电感器的饱和电流、温升电流和额定电流是描述电感在不同工作条件下表现的三个重要参数。它们分别反映了电感的不同工作特性…...
基于阿里云百炼大模型Sensevoice-1的语音识别与文本保存工具开发
基于阿里云百炼大模型Sensevoice-1的语音识别与文本保存工具开发 摘要 随着人工智能技术的不断发展,语音识别在会议记录、语音笔记等场景中得到了广泛应用。本文介绍了一个基于Python和阿里云百炼大模型的语音识别与文本保存工具的开发过程。该工具能够高效地识别东…...
【go语言】函数
一、什么是函数 函数是入门简单精通难,函数是什么??? 函数就是一段代码的集合go 语言中至少有一个 main 函数函数需要有一个名字,独立定义的情况下,见名知意函数可能需要有一个结果,也可能没有…...
CTF-web: phar反序列化+数据库伪造 [DASCTF2024最后一战 strange_php]
step 1 如何触发反序列化? 漏洞入口在 welcome.php case delete: // 获取删除留言的路径,优先使用 POST 请求中的路径,否则使用会话中的路径 $message $_POST[message_path] ? $_POST[message_path] : $_SESSION[message_path]; $msg $userMes…...
从0开始使用面对对象C语言搭建一个基于OLED的图形显示框架(动态菜单组件实现)
目录 面对对象C的程序设计(范例) 面对对象C的程序设计(应用) 进一步谈论我上面给出的代码——继承 实现一个面对对象的文本编辑器 所以,什么是继承 重申我们对菜单的抽象 抽象菜单项目 抽象菜单动画 实现菜单功…...
EtherCAT主站IGH-- 23 -- IGH之fsm_slave.h/c文件解析
EtherCAT主站IGH-- 23 -- IGH之fsm_slave.h/c文件解析 0 预览一 该文件功能`fsm_slave.c` 文件功能函数预览二 函数功能介绍`fsm_slave.c` 中主要函数的作用1. `ec_fsm_slave_init`2. `ec_fsm_slave_clear`3. `ec_fsm_slave_exec`4. `ec_fsm_slave_set_ready`5. `ec_fsm_slave_…...
设计模式和设计原则回顾
设计模式和设计原则回顾 23种设计模式是设计原则的完美体现,设计原则设计原则是设计模式的理论基石, 设计模式 在经典的设计模式分类中(如《设计模式:可复用面向对象软件的基础》一书中),总共有23种设计模式,分为三大类: 一、创建型模式(5种) 1. 单例模式(Sing…...
使用VSCode开发Django指南
使用VSCode开发Django指南 一、概述 Django 是一个高级 Python 框架,专为快速、安全和可扩展的 Web 开发而设计。Django 包含对 URL 路由、页面模板和数据处理的丰富支持。 本文将创建一个简单的 Django 应用,其中包含三个使用通用基本模板的页面。在此…...
TDengine 快速体验(Docker 镜像方式)
简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能,本节首先介绍如何通过 Docker 快速体验 TDengine,然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker,请使用 安装包的方式快…...
【WiFi帧结构】
文章目录 帧结构MAC头部管理帧 帧结构 Wi-Fi的帧分为三部分组成:MAC头部frame bodyFCS,其中MAC是固定格式的,frame body是可变长度。 MAC头部有frame control,duration,address1,address2,addre…...
MFC内存泄露
1、泄露代码示例 void X::SetApplicationBtn() {CMFCRibbonApplicationButton* pBtn GetApplicationButton();// 获取 Ribbon Bar 指针// 创建自定义按钮CCustomRibbonAppButton* pCustomButton new CCustomRibbonAppButton();pCustomButton->SetImage(IDB_BITMAP_Jdp26)…...
用docker来安装部署freeswitch记录
今天刚才测试一个callcenter的项目,所以尝试安装freeswitch 1、使用轩辕镜像 - 中国开发者首选的专业 Docker 镜像加速服务平台 编辑下面/etc/docker/daemon.json文件为 {"registry-mirrors": ["https://docker.xuanyuan.me"] }同时可以进入轩…...
均衡后的SNRSINR
本文主要摘自参考文献中的前两篇,相关文献中经常会出现MIMO检测后的SINR不过一直没有找到相关数学推到过程,其中文献[1]中给出了相关原理在此仅做记录。 1. 系统模型 复信道模型 n t n_t nt 根发送天线, n r n_r nr 根接收天线的 MIMO 系…...
OPENCV形态学基础之二腐蚀
一.腐蚀的原理 (图1) 数学表达式:dst(x,y) erode(src(x,y)) min(x,y)src(xx,yy) 腐蚀也是图像形态学的基本功能之一,腐蚀跟膨胀属于反向操作,膨胀是把图像图像变大,而腐蚀就是把图像变小。腐蚀后的图像变小变暗淡。 腐蚀…...
深度学习习题2
1.如果增加神经网络的宽度,精确度会增加到一个特定阈值后,便开始降低。造成这一现象的可能原因是什么? A、即使增加卷积核的数量,只有少部分的核会被用作预测 B、当卷积核数量增加时,神经网络的预测能力会降低 C、当卷…...
如何更改默认 Crontab 编辑器 ?
在 Linux 领域中,crontab 是您可能经常遇到的一个术语。这个实用程序在类 unix 操作系统上可用,用于调度在预定义时间和间隔自动执行的任务。这对管理员和高级用户非常有益,允许他们自动执行各种系统任务。 编辑 Crontab 文件通常使用文本编…...
