ByteCinema(1):用户的登录注册
文章目录
- 主要功能
- 生成图形验证码
- redis滑动窗口操作限流
- 0.限流设计的必要性
- 1.原理
- 2.代码(邮箱发验证码为例)
- 3. 问题与解决
- 高并发环境下redis操作的原子性
- 过时数据的积累
- 续约token实现长期登录
- 0.设计的出发点
- 1.前置知识:JWT
- 什么是 JWT?
- JWT 的结构
- 1. Header(头部)
- 2. Payload(负载)
- 3. Signature(签名)
- JWT 的工作原理
- 2.思路
- 正常登录的流程
- 访问受限资源
- code
主要功能
- 生成图形验证码
- redis滑动窗口操作限流
- 续约token实现长期登录的效果
生成图形验证码
hutoool提供了工具类,直接用就行
public Captcha createCaptcha(){// 定义图形验证码的长和宽LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(200, 100);//将验证码储存到redis中,加上TTLString uuid = UUID.randomUUID().toString();stringRedisTemplate.opsForValue().set(LOGIN_CAPTCHA + uuid, lineCaptcha.getCode(), LOGIN_CAPTCHA_TTL, TimeUnit.MINUTES);return new Captcha(uuid, lineCaptcha.getImageBase64());}
redis滑动窗口操作限流
0.限流设计的必要性
用户可能有很多行为,是无意义,或者非法的。比如:频繁发送短信、频繁修改个人信息、频繁的点赞、评论等等行为,这些做法不仅是意义不大的操作,而且还会对我们的服务器带来压力,所以需要设计限流操作。
1.原理
Redis 中的有序集合(ZSet,或称为 Sorted Set)是按照成员的分数(score)从小到大排序的。因此我们将当前的时间戳作为分数的话,这样我们就得到了一个“时间轴”
。
Key的格式设计为【场景:行为:用户唯一标识】,score分数值是时间戳,value值是什么都可以,不重要,一般会放时间戳、用户唯一标识和次数等等。
具体流程:
- 当用户每次发生限流行为,都会记录这个行为,以Redis zset的方式进行记录
- 在业务处理流程中,使用java api进行查询判断,其实本质就是调用redis的zcount命令,这个命令可以传入起始分值和结束分值。我就把当前时间戳作为结束分值,然后当前时间戳减去限流时间,比如说5分钟的毫秒值,求出来5分钟前的时间戳。于是根据这两个时间戳作为分值,范围查询zset中出现的次数,就得到用户在5分钟内,这个行为一共触发了几次。
- 后续的业务,就是不同场景中,根据不同的需求,进行校验就行了。
2.代码(邮箱发验证码为例)
public void getCode(String email) {//0.合法性检验,虽然前端会检验邮箱合法性,但后端最好还是也做一些保底的检验//TODO:更多的校验步骤int in = email.indexOf('@');if(in == -1){throw new RuntimeException("邮箱地址不合法");}//1.获取当前的时间窗口long currentTimeMillis = System.currentTimeMillis();long start = currentTimeMillis - LOGIN_EMAIL_WINDOW;//2.执行限流操作前,检查用户是否达到了限制条件Long count = stringRedisTemplate.opsForZSet().count(LOGIN_EMAIL + email, start, currentTimeMillis);//时间窗口里面的操作次数if(count != null && count > 2){//3.达到限流条件,进行限制(deny user)throw new RuntimeException("操作过于频繁,请稍后再试");}//4.未达到,执行操作String code = RandomGenerator.generateRandom(6);MailUtil.send(email, "注册验证码", code, false);stringRedisTemplate.opsForValue().set(LOGIN_CODE + email, code, LOGIN_CODE_TTL, TimeUnit.MINUTES);//5.记录此次操作stringRedisTemplate.opsForZSet().add(LOGIN_EMAIL + email, email, currentTimeMillis);}
3. 问题与解决
高并发环境下redis操作的原子性
使用lua脚本进行一步执行
public void getCode(String email) {// 检验邮箱合法性int in = email.indexOf('@');if (in == -1) {throw new RuntimeException("邮箱地址不合法");}// Lua 脚本String luaScript ="local count = redis.call('zcount', KEYS[1], ARGV[1], ARGV[2])\n" +"if count > tonumber(ARGV[3]) then\n" +" return false\n" +"else\n" +" redis.call('zadd', KEYS[1], ARGV[2], ARGV[4])\n" +" redis.call('setex', KEYS[2], ARGV[5], ARGV[6])\n" +" return true\n" +"end";long currentTimeMillis = System.currentTimeMillis();long start = currentTimeMillis - LOGIN_EMAIL_WINDOW;String code = RandomGenerator.generateRandom(6);// 参数设置List<String> keys = Arrays.asList(LOGIN_EMAIL + email, LOGIN_CODE + email);List<String> args = Arrays.asList(String.valueOf(start),String.valueOf(currentTimeMillis),"2", // 请求次数上限email,String.valueOf(LOGIN_CODE_TTL * 60), // 过期时间(秒)code);// 执行Lua脚本Boolean result = stringRedisTemplate.execute(new DefaultRedisScript<Boolean>(luaScript, Boolean.class),keys,args.toArray(new String[0]));// 判断结果if (Boolean.FALSE.equals(result)) {throw new RuntimeException("操作过于频繁,请稍后再试");}// 发送邮件MailUtil.send(email, "注册验证码", code, false);}
过时数据的积累
定时任务清理过时数据
@Component
public class RedisDataCleaner {private final StringRedisTemplate stringRedisTemplate;public RedisDataCleaner(StringRedisTemplate stringRedisTemplate) {this.stringRedisTemplate = stringRedisTemplate;}@Scheduled(fixedRate = 300000) // 每5分钟执行一次public void cleanOldEntries() {long currentTimeMillis = System.currentTimeMillis();long threshold = currentTimeMillis - (5 * 60 * 1000); // 5分钟前stringRedisTemplate.opsForZSet().removeRangeByScore("yourZSetKey", 0, threshold);}
}
续约token实现长期登录
0.设计的出发点
减少用户登录次数,提高用户体验
1.前置知识:JWT
什么是 JWT?
JWT(JSON Web Token)是一种基于 JSON 的开放标准(RFC 7519),用于在各方之间安全地传输信息。它可以被用来进行身份验证和信息交换。由于 JWT 是经过数字签名的,因此信息是可信任的。
JWT 的结构
一个 JWT 由三个部分组成,每部分之间用点(.
)分隔:
- Header(头部)
- Payload(负载)
- Signature(签名)
组合后的格式如下:
xxxxx.yyyyy.zzzzz
1. Header(头部)
Header 通常包含两部分信息:
- 类型:即令牌类型,JWT。
- 算法:用于生成签名的哈希算法,例如 HMAC SHA256 或 RSA。
示例:
{"alg": "HS256","typ": "JWT"
}
然后,将 JSON 格式的 Header 使用 Base64URL 编码,得到 JWT 的第一部分。
2. Payload(负载)
Payload 部分包含声明(Claims),即需要传递的数据。这些声明分为三类:
- 注册声明(Registered Claims):一组预定义的声明,推荐但不强制使用,例如
iss
(发行人)、exp
(过期时间)、sub
(主题)、aud
(受众)等。 - 公共声明(Public Claims):可自定义的声明,但为了避免冲突,最好在 IANA JSON Web Token Registry 中注册或使用 URI 形式。
- 私有声明(Private Claims):自定义的声明,用于双方协商使用,不在公共注册中。
示例:
{"sub": "1234567890","name": "John Doe","admin": true
}
同样,将 Payload 使用 Base64URL 编码,得到 JWT 的第二部分。
3. Signature(签名)
签名部分是对前两部分的签名,确保令牌的完整性和真实性。签名的生成方式如下:
signature = HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload),secret
)
其中,secret
是服务器端的密钥,不能泄露。
JWT 的工作原理
-
认证阶段:用户通过提供凭证(如用户名和密码)向服务器请求认证。
-
生成 JWT:服务器验证用户身份后,生成 JWT,包含用户信息和其他声明,并使用密钥进行签名。
-
返回 JWT:服务器将生成的 JWT 返回给客户端。
-
存储 JWT:客户端通常将 JWT 存储在本地存储(LocalStorage)或 Cookie 中。
-
请求带上 JWT:客户端在后续请求中,将 JWT 放在 HTTP 请求的
Authorization
头部中:Authorization: Bearer <token>
-
服务器验证 JWT:服务器接收到请求后,验证 JWT 的签名和有效性,确认后处理请求。
2.思路
首先明确,无论用户用什么方式登录(包括第三方认证)的,全是返回token作为认证
正常登录的流程
后端在返回Token的时候,是生成两个Token:
一个是AccessToken,我管他叫访问令牌,我处于安全考虑,比如防止令牌被恶意使用,设置他的有效期为3个小时,每次请求资源时携带这个令牌;
另一个是RefreshToken,我管他叫刷新令牌,这个令牌不能用来访问资源,只能用来刷新访问令牌,就是每当访问令牌过期,前端携带这个RefreshToken获取新的AccessToken,这个刷新Token的有效期我设置为7天,当然这个可以改,这是写在配置文件中的。
当Token返回给前端后,浏览器端用的是localStorage保存的,App端的话有他们自己的本地保存方式,将这两个Token保存下来。
访问受限资源
我设置了一个拦截器对受限资源进行拦截,这个拦截器会检验请求中是否携带accessToken,携带了正常的accessToken,放行就行,在这里没有讨论的必要,这里讲解一下其它几种情况:
- 未携带,直接拒绝
- 携带了过期的accessToken,返回accessToken过期的标识,提醒客户端使用refresh刷新accessToken;
前端判断拒绝的状态码为AccessToken无效后,会重新发起一次请求,携带RefreshToken重新请求续约接口,这个续约接口是不需要网关拦截的,然后续约接口针对RefreshToken进行解密后,校验签名没有问题,没有被篡改,于是重新颁发新的AccessToken,返回给前端。
前端重新携带AccessToken发起请求就行了。
code
登录返回双重token
public LoginVO login(LoginDTO loginDTO) {String email = loginDTO.getEmail().toLowerCase();LambdaQueryWrapper<User> queryWrapper = Wrappers.lambdaQuery(User.class).eq(User::getEmail, email).eq(User::getPassword, loginDTO.getPassword()).eq(User::getDelFlag, 0);User user = userService.getOne(queryWrapper);if(BeanUtil.isEmpty(user)){throw new RuntimeException("用户账号或密码错误");}// 生成令牌String accessToken = jwtTokenUtil.generateAccessToken(user);String refreshToken = jwtTokenUtil.generateRefreshToken(user);return new LoginVO(accessToken, refreshToken);
}
刷新接口,提供使用refreshToken刷新accessToken
public LoginVO refresh(String refreshToken) {boolean f = jwtTokenUtil.validateToken(refreshToken);if(!f){throw new RuntimeException("刷新令牌异常,请重新登录");}LambdaQueryWrapper<User> queryWrapper = Wrappers.lambdaQuery(User.class).eq(User::getEmail, jwtTokenUtil.getEmailFromToken(refreshToken)).eq(User::getDelFlag, 0);User user = userService.getOne(queryWrapper);if(BeanUtil.isEmpty(user)){throw new RuntimeException("刷新令牌异常,请重新登录");}String accessToken = jwtTokenUtil.generateAccessToken(user);String newRefreshToken = jwtTokenUtil.generateRefreshToken(user);return new LoginVO(accessToken, newRefreshToken);
}
拦截器,实现对accessToken的解析
public class JwtAuthenticationIntercept implements HandlerInterceptor {private JwtTokenUtil jwtTokenUtil;public JwtAuthenticationIntercept(JwtTokenUtil jwtTokenUtil) {this.jwtTokenUtil = jwtTokenUtil;}@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {String header = request.getHeader("Authorization");if (StrUtil.isNotBlank(header) && header.startsWith("Bearer ")) {String token = header.substring(7);if (jwtTokenUtil.validateToken(token)) {String email = jwtTokenUtil.getEmailFromToken(token);UserHolder.saveUser(new UserDTO(email));return true;}response.setHeader("accessToken", "outdated");response.getWriter().write("访问token过期");}return false;}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {UserHolder.removeUser();}
}
相关文章:
ByteCinema(1):用户的登录注册
文章目录 主要功能生成图形验证码redis滑动窗口操作限流0.限流设计的必要性1.原理2.代码(邮箱发验证码为例)3. 问题与解决高并发环境下redis操作的原子性过时数据的积累 续约token实现长期登录0.设计的出发点1.前置知识:JWT什么是 JWT?JWT 的…...
电力电网电线变电站输电线绝缘子无人机类数据集/农业植物病虫害类数据集/光伏板/工程煤矿矿场类数据集/道路类数据集
电力电网电线变电站输电线红外缺陷类数据集 传送门链接: 1.电线覆盖物检测数据集 气球风筝鸟巢 1300张 voc yol-CSDN博客 2.变电站可见光缺陷数据集数据集包含8376张巡检图像,带xml标签,共包含17类巡检标签!具体缺陷分类见下图!…...
深度学习之表示学习 - 引言篇
序言 在数据爆炸的今天,如何从纷繁复杂的信息中抽取有价值的知识,成为了人工智能领域亟待解决的核心问题。深度学习,作为机器学习的一个重要分支,以其强大的特征表示能力和自动化学习特性,引领了这场数据革命的浪潮。…...

Linux驱动开发 ——架构体系
只读存储器(ROM) 1.作用 这是一种非易失性存储器,用于永久存储数据和程序。与随机存取存储器(RAM)不同,ROM中的数据在断电后不会丢失,通常用于存储固件和系统启动程序。它的内容在制造时或通过…...
Django一分钟:lookupAPI详解,使用django orm生成高效的WHERE子句
一、Lookup API概述 Lookup API是Django用于构建数据库查询WHERE子句的API。 Lookup API的核心包含两部分: RegisterLookupMixin:为子类提供注册lookup的方法Query Expression API:一个接口,规定了可以被注册为lookup的类需要实…...

信息安全工程师(8)网络新安全目标与功能
前言 网络新安全目标与功能在当前的互联网环境中显得尤为重要,它们不仅反映了网络安全领域的最新发展趋势,也体现了对网络信息系统保护的不断加强。 一、网络新安全目标 全面防护与动态应对: 目标:建立多层次、全方位的网络安全防…...
返利机器人在电商返利系统中的负载均衡实现
返利机器人在电商返利系统中的负载均衡实现 大家好,我是微赚淘客返利系统3.0的小编,是个冬天不穿秋裤,天冷也要风度的程序猿!今天我们来聊一聊如何在电商返利系统中实现返利机器人的负载均衡,尤其是在面对高并发和大量…...
MATLAB中typecast函数用法
目录 语法 说明 示例 将整数转换为相同存储大小的无符号整数 将 8 位整数转换为单精度 将 32 位整数转换为 8 位整数 将 8 位整数转换为 16 位整数 提示 typecast函数的功能是在不更改基础数据的情况下转换数据类型。 语法 Y typecast(X,type) 说明 Y typecast(X,…...

植物大战僵尸【源代码分享+核心思路讲解】
植物大战僵尸已经正式完结,今天和大家分享一下,话不多说,直接上链接!!!(如果大家在运行这个游戏遇到了问题或者bug,那么请私我谢谢) 大家写的时候可以参考一下我的代码思…...

变压器设备漏油数据集 voc txt
变压器设备漏油数据集 油浸式变压器通常采用油浸自冷式、油浸风冷式和强迫油循环三种冷却方式。该数据集采集于油浸式变压器的设备漏油情况,一般用于变电站的无人巡检,代替传统的人工巡检,与绝缘子的破损检测来源于同一课题。数据集一部分来自…...

算法练习题25——leetcode3279统计重新排列后包含另一个字符串的子字符串的数目(滑动窗口 双指针 哈希)
题目描述 解题思路 本题用到了滑动窗口 双指针 哈希 刚开始我是没读懂题的因为我笨 我想把我的思路说一下 左端不轻易缩小 只有找到跟word2匹配了 比如说abbcdd 遍历到c的时候才能匹配这个word2 对吧 那么之后加上以一个d或者俩d 都符合了 然后我们算完了 才能缩小左端 扩大…...

JavaEE: 深入探索TCP网络编程的奇妙世界(二)
文章目录 TCP核心机制TCP核心机制二: 超时重传为啥会丢包?TCP如何对抗丢包?超时重传的时间设定超时时间该如何确定? TCP核心机制 前一篇文章 JavaEE: 深入探索TCP网络编程的奇妙世界(一) 书接上文~ TCP核心机制二: 超时重传 在网络传输中,并不会一帆风顺,而是可能出现&qu…...

GPT1-GPT3论文理解
GPT1-GPT3论文理解 视频参考:https://www.bilibili.com/video/BV1AF411b7xQ/?spm_id_from333.788&vd_sourcecdb0bc0dda1dccea0b8dc91485ef3e74 1 历史 2017.6 Transformer 2018.6 GPT 2018.10 BERT 2019.2 GPT-2 2020…...

C/C++内存管理 ——
目录 五、C/C内存管理 1、C/C内存分布 2、C语言中动态内存管理方式:malloc/calloc/realloc/free 3、C内存管理方式 1.new/delete操作内置类型 2.new和delete操作自定义类型 4、operator new与operator delete函数 5、new和delete的实现原理 1.内置类…...

深度学习02-pytorch-04-张量的运算函数
在 PyTorch 中,张量(tensor)运算是核心操作之一,PyTorch 提供了丰富的函数来进行张量运算,包括数学运算、线性代数、索引操作等。以下是常见的张量运算函数及其用途: 1. 基本数学运算 加法运算:…...

OpenHarmony(鸿蒙南向开发)——小型系统内核(LiteOS-A)【文件系统】上
往期知识点记录: 鸿蒙(HarmonyOS)应用层开发(北向)知识点汇总 鸿蒙(OpenHarmony)南向开发保姆级知识点汇总~ 子系统开发内核 轻量系统内核(LiteOS-M) 轻量系统内核&#…...
NISP 一级 | 8.4 《网络安全法》
关注这个证书的其他相关笔记:NISP 一级 —— 考证笔记合集-CSDN博客 2017 年 6 月 1 日,《中华人民共和国网终安全法》(以下简称《网终安全法》)正式实施。这是我国第一部全面规范网络空间安全管理方面问题的基础性法律࿰…...

实现人体模型可点击
简化需求:实现项目内嵌人体模型,实现点击不同部位弹出部位名称 一:优先3d, 方案:基于three.js,.gltf格式模型,vue3 缺点:合适且免费的3d模型找不到,因为项目对部位有要…...

C++ | Leetcode C++题解之第429题N叉树的层序遍历
题目: 题解: class Solution { public:vector<vector<int>> levelOrder(Node* root) {if (!root) {return {};}vector<vector<int>> ans;queue<Node*> q;q.push(root);while (!q.empty()) {int cnt q.size();vector<…...
Pandas简介
Pandas 是一个流行的开源数据分析库,它是基于 NumPy 构建的,为 Python 编程语言提供了高性能、易用的数据结构和数据分析工具。Pandas 主要用于数据清洗、数据转换、数据分析等任务,使得数据处理工作变得更加高效和便捷。 Pandas 的两个主要…...

手游刚开服就被攻击怎么办?如何防御DDoS?
开服初期是手游最脆弱的阶段,极易成为DDoS攻击的目标。一旦遭遇攻击,可能导致服务器瘫痪、玩家流失,甚至造成巨大经济损失。本文为开发者提供一套简洁有效的应急与防御方案,帮助快速应对并构建长期防护体系。 一、遭遇攻击的紧急应…...
Ubuntu系统下交叉编译openssl
一、参考资料 OpenSSL&&libcurl库的交叉编译 - hesetone - 博客园 二、准备工作 1. 编译环境 宿主机:Ubuntu 20.04.6 LTSHost:ARM32位交叉编译器:arm-linux-gnueabihf-gcc-11.1.0 2. 设置交叉编译工具链 在交叉编译之前&#x…...
React Native在HarmonyOS 5.0阅读类应用开发中的实践
一、技术选型背景 随着HarmonyOS 5.0对Web兼容层的增强,React Native作为跨平台框架可通过重新编译ArkTS组件实现85%以上的代码复用率。阅读类应用具有UI复杂度低、数据流清晰的特点。 二、核心实现方案 1. 环境配置 (1)使用React Native…...

NFT模式:数字资产确权与链游经济系统构建
NFT模式:数字资产确权与链游经济系统构建 ——从技术架构到可持续生态的范式革命 一、确权技术革新:构建可信数字资产基石 1. 区块链底层架构的进化 跨链互操作协议:基于LayerZero协议实现以太坊、Solana等公链资产互通,通过零知…...
Mobile ALOHA全身模仿学习
一、题目 Mobile ALOHA:通过低成本全身远程操作学习双手移动操作 传统模仿学习(Imitation Learning)缺点:聚焦与桌面操作,缺乏通用任务所需的移动性和灵活性 本论文优点:(1)在ALOHA…...

安宝特方案丨船舶智造的“AR+AI+作业标准化管理解决方案”(装配)
船舶制造装配管理现状:装配工作依赖人工经验,装配工人凭借长期实践积累的操作技巧完成零部件组装。企业通常制定了装配作业指导书,但在实际执行中,工人对指导书的理解和遵循程度参差不齐。 船舶装配过程中的挑战与需求 挑战 (1…...

Linux 内存管理实战精讲:核心原理与面试常考点全解析
Linux 内存管理实战精讲:核心原理与面试常考点全解析 Linux 内核内存管理是系统设计中最复杂但也最核心的模块之一。它不仅支撑着虚拟内存机制、物理内存分配、进程隔离与资源复用,还直接决定系统运行的性能与稳定性。无论你是嵌入式开发者、内核调试工…...
CSS | transition 和 transform的用处和区别
省流总结: transform用于变换/变形,transition是动画控制器 transform 用来对元素进行变形,常见的操作如下,它是立即生效的样式变形属性。 旋转 rotate(角度deg)、平移 translateX(像素px)、缩放 scale(倍数)、倾斜 skewX(角度…...
NPOI操作EXCEL文件 ——CAD C# 二次开发
缺点:dll.版本容易加载错误。CAD加载插件时,没有加载所有类库。插件运行过程中用到某个类库,会从CAD的安装目录找,找不到就报错了。 【方案2】让CAD在加载过程中把类库加载到内存 【方案3】是发现缺少了哪个库,就用插件程序加载进…...

[ACTF2020 新生赛]Include 1(php://filter伪协议)
题目 做法 启动靶机,点进去 点进去 查看URL,有 ?fileflag.php说明存在文件包含,原理是php://filter 协议 当它与包含函数结合时,php://filter流会被当作php文件执行。 用php://filter加编码,能让PHP把文件内容…...