框架模块说明 #06 二次验证(MFA)_01

背景

在用户登录或执行敏感操作时,我们引入了二次验证机制,以全面提升后台安全性。具体而言:

  1. 登录时的图形验证:通过图形验证码,有效防范恶意攻击和自动化脚本,确保初始登录的安全性。
  2. 针对海外用户的 TG 验证码二次验证:额外增加基于 Telegram 验证码的身份验证,进一步增强安全保障。
  3. 谷歌验证:谷歌验证器分为两种,一种是对敏感操作的认证,我们增加了注解进行强制验证,另外一种是行为检查式的验证,通常是半小时内必须输入一次验证的等式。

这种双重验证机制,不仅强化了登录行为的安全性,还为敏感操作提供了更加稳固的保护措施,实现了对系统安全的多层次防护,本章节只讲mfa的后台配置、行为校验拦截器和表结构。

常量

如下定义了两个常量,用来保存用户是否绑定google的标识,这两个值将保存在t_frame_user_ref表中,在下面的段落中将会介绍。

    _GOOGLE_MFA_USER_SECRET_REF_FlAG_ATTR_NAME("GOOGLE_MFA_USER_SECRET_BIND_FLAG", "谷歌验证绑定标识", "system.mfa.google.secret.flag"),_GOOGLE_MFA_USER_SECRET_REF_ATTR_NAME("GOOGLE_MFA_USER_SECRET_KEY", "谷歌key", "system.mfa.google.secret.key"),

表结构

其中google的密钥要用aes加密处理。

CREATE TABLE `t_frame_user_ref` (`id` bigint NOT NULL AUTO_INCREMENT,`user_id` varchar(200) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL,`attribute_name` varchar(50) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL,`attribute_value` varchar(200) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL,`create_by` varchar(50) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL,`create_date` datetime DEFAULT NULL,`update_by` varchar(50) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL,`update_date` datetime DEFAULT NULL,`remark` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL,PRIMARY KEY (`user_id`,`attribute_name`) USING BTREE,KEY `ak_kid` (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=40 DEFAULT CHARSET=utf8mb3;

API类

里面的代码太多了,这里就只贴出一部分,详细代码请参考GitCode - 全球开发者的开源社区,开源代码托管平台GitCode是面向全球开发者的开源社区,包括原创博客,开源代码托管,代码协作,项目管理等。与开发者社区互动,提升您的研发效率和质量。icon-default.png?t=O83Ahttps://gitcode.com/YouYouLongLong/springcloud-framework/blob/master/springcloud-tester/com.longyou.commservice/src/main/java/com/longyou/comm/conntroller/UserMfaController.java

@RestController
@RequestMapping("/user/mfa")
@SystemResource(path = "/common/user/mfa")
@Api(value = "UserMfaController", tags = "双因子验证API")
@Slf4j
public class UserMfaController {@Autowiredprivate FrameUserRefService frameUserRefService;@Value("${system.mfa.expired-time:1800}")private Long expiredTime;/*** 校验谷歌验证码是否已经绑定** @return* @throws Exception*/@ApiOperation("校验谷歌验证码是否已经绑定")@GetMapping("/checkUserGoogleSecretBindStatus")@SystemResource
//    @AuthLog(bizType = "framework", desc = "校验谷歌验证码是否已经绑定", operateLogType = OperateLogType.LOG_TYPE_FRONTEND)public CommonApiResult<Map<String, Object>> checkUserGoogleSecretBindStatus() throws Exception {LoginUserDetails loginUserDetails = RequestContextManager.single().getRequestContext().getUser();CommonApiResult<Map<String, Object>> responseResult = CommonApiResult.createSuccessResult();FrameUserRefVO frameUserRefVO = frameUserRefService.getUserRefByAttributeName(loginUserDetails.getId(),_GOOGLE_MFA_USER_SECRET_REF_FlAG_ATTR_NAME.value());final Map<String, Object> returnData = new LinkedHashMap<>();if (frameUserRefVO != null && "true".equals(frameUserRefVO.getAttributeValue())) {returnData.put(_GOOGLE_MFA_USER_SECRET_REF_FlAG_ATTR_NAME.value(), true);} else {String googleSecretEnc = GoogleAuthenticatorUtil.single().getCurrentUserVerifyKey(true);if (googleSecretEnc == null) {frameUserRefVO = GoogleAuthenticatorUtil.single().createNewUserRefVO(loginUserDetails);frameUserRefService.create(frameUserRefVO);googleSecretEnc = frameUserRefVO.getAttributeValue();}final String googleSecret = AES128Util.single().decrypt(googleSecretEnc);returnData.put(_GOOGLE_MFA_USER_SECRET_REF_FlAG_ATTR_NAME.value(), false);returnData.put("description", CORRELATION_YOUR_GOOGLE_KEY.description());returnData.put("secret", googleSecret);returnData.put("secretQRBarcode", GoogleAuthenticatorUtil.single().getQRBarcode(loginUserDetails.getUsername(), googleSecret));
//            returnData.put("secretQRBarcodeURL",
//                GoogleAuthenticatorUtil.single().getQRBarcodeURL(loginUserDetails.getUsername(), "", googleSecret));RedisUtil.single().set(__MFA_TOKEN_USER_GOOGLE_SECRET_CACHE_KEY + loginUserDetails.getId(), googleSecretEnc);}responseResult.setData(returnData);return responseResult;}/*** 绑定谷歌验证码** @return* @throws Exception*/@ApiOperation("绑定谷歌验证")@GetMapping("/bindUserGoogleSecret")@SystemResource(value = "bindUserGoogleSecret", description = "绑定谷歌验证")@AuthLog(bizType = "framework", desc = "绑定谷歌验证", operateLogType = OperateLogType.LOG_TYPE_FRONTEND)public CommonApiResult<FrameUserRefVO> bindUserGoogleSecret() throws Exception {// 绑定的时候每次都要重新的生成一个出来String googleSecret = GoogleAuthenticatorUtil.single().getCurrentUserVerifyKey();if (!GoogleAuthenticatorUtil.single().checkGoogleVerifyCode(googleSecret)) {throw new BusinessException("system.error.google.valid", 400);}LoginUserDetails loginUserDetails = RequestContextManager.single().getRequestContext().getUser();CommonApiResult<FrameUserRefVO> responseResult = CommonApiResult.createSuccessResult();FrameUserRefVO frameUserRefVO = frameUserRefService.getUserRefByAttributeName(loginUserDetails.getId(),_GOOGLE_MFA_USER_SECRET_REF_FlAG_ATTR_NAME.value());if (frameUserRefVO == null) {frameUserRefVO = new FrameUserRefVO();frameUserRefVO.setUserId(loginUserDetails.getId());frameUserRefVO.setAttributeName(_GOOGLE_MFA_USER_SECRET_REF_FlAG_ATTR_NAME.value());frameUserRefVO.setAttributeValue("true");frameUserRefVO.setRemark(_GOOGLE_MFA_USER_SECRET_REF_FlAG_ATTR_NAME.description());frameUserRefService.create(frameUserRefVO);} else {frameUserRefVO.setAttributeValue("true");frameUserRefService.update(frameUserRefVO);}responseResult.setData(frameUserRefVO);ThreadUtil.execAsync(() -> RedisUtil.single().removeUserLoginToken(loginUserDetails.getId()));return responseResult;}

行为校验过滤器

主要是对操作过程中的二次验证,按配置的时间间隔进行校验,如果用户在redis的mfa校验已经过期了,那么要重新校验,其中过期的KEY是和你的IP进行绑定的。

GitCode - 全球开发者的开源社区,开源代码托管平台GitCode是面向全球开发者的开源社区,包括原创博客,开源代码托管,代码协作,项目管理等。与开发者社区互动,提升您的研发效率和质量。icon-default.png?t=O83Ahttps://gitcode.com/YouYouLongLong/springcloud-framework/blob/master/core-common-parent/mfa-common/src/main/java/com/unknow/first/mfa/config/MfaFilterConfig.java

package com.unknow.first.mfa.config;import static org.cloud.constant.LoginTypeConstant.LoginTypeEnum.LOGIN_BY_ADMIN_USER;
import static org.cloud.constant.MfaConstant.CORRELATION_GOOGLE_NOT_VERIFY_OR_EXPIRE;import com.unknow.first.util.GoogleAuthenticatorUtil;
import java.io.IOException;
import java.util.List;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import lombok.Setter;
import org.cloud.constant.CoreConstant;
import org.cloud.context.RequestContext;
import org.cloud.context.RequestContextManager;
import org.cloud.core.redis.RedisUtil;
import org.cloud.entity.LoginUserDetails;
import org.cloud.exception.BusinessException;
import org.cloud.utils.HttpServletUtil;
import org.cloud.utils.IPUtil;
import org.jetbrains.annotations.NotNull;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.web.filter.OncePerRequestFilter;/*** mfa验证器,目前只支持google*/
@ConfigurationProperties(prefix = "system.mfa")
@Configuration
@ConditionalOnProperty(prefix = "system.mfa", name = "enabled", matchIfMissing = true)
public class MfaFilterConfig {public final static String __MFA_TOKEN_USER_CACHE_KEY = "system:mfa:user:verify:result:";  // 校验结果public final static String __MFA_TOKEN_USER_GOOGLE_SECRET_CACHE_KEY = "system:mfa:user:secret:result:"; // 谷歌key@Setterprivate List<String> excludeUri;      // 默认为内部调用的url也可以自己添加private final CoreConstant.MfaAuthType mfaAuthType = CoreConstant.MfaAuthType.GOOGLE;  //默认为google验证@Beanpublic FilterRegistrationBean<?> mfaWebFilter() {this.excludeUri.add("/v2/api-docs");this.excludeUri.add("/inner/**/*");this.excludeUri.add("/user/verify/generate/*");this.excludeUri.add("/user/mfa/**");this.excludeUri.add("/app/userRef/isBindLoginIp");this.excludeUri.add("/app/userRef/ipLoginLockFlagOpen");this.excludeUri.add("/app/userRef/changeLoginIp");this.excludeUri.add("/app/userRef/getCurrentIp");this.excludeUri.add("/user/menu/getMenus");FilterRegistrationBean<?> registration = new FilterRegistrationBean<>(new MfaWebFilter(excludeUri));registration.addUrlPatterns("/*");registration.setName("mfaWebFilter");registration.setOrder(100);return registration;}static class MfaWebFilter extends OncePerRequestFilter {private final List<String> noMfaCheckUrl;      // 默认为内部调用的url也可以自己添加public MfaWebFilter(List<String> noMfaCheckUrl) {this.noMfaCheckUrl = noMfaCheckUrl;}@Overrideprotected void doFilterInternal(@NotNull HttpServletRequest httpServletRequest, @NotNull HttpServletResponse httpServletResponse,@NotNull FilterChain filterChain) throws ServletException, IOException {if (HttpServletUtil.single().isExcludeUri(httpServletRequest, noMfaCheckUrl)) {filterChain.doFilter(httpServletRequest, httpServletResponse);return;}// 如果数据字典中关闭了双因子校验,那么不进行双因子校验,关闭数据字典的全局配置,防止密码泄露后可以更改此项目绕过google
//            TSystemDicItem dicItem = SystemDicUtil.single().getDicItem("systemConfig", "isMfaVerify");
//            if (dicItem != null && StringUtils.hasLength(dicItem.getDicItemValue()) && "false".equals(dicItem.getDicItemValue())) {
//                filterChain.doFilter(httpServletRequest, httpServletResponse);
//                return;
//            }RequestContext currentRequestContext = RequestContextManager.single().getRequestContext();LoginUserDetails user = currentRequestContext.getUser();// 只有需要登录鉴权的接口并且用户类型为管理员才需要校验双因子if (user == null || (!LOGIN_BY_ADMIN_USER.userType.equals(user.getUserType()))) {filterChain.doFilter(httpServletRequest, httpServletResponse);return;}try {GoogleAuthenticatorUtil.single().verifyCurrentUserBindGoogleKey();} catch (BusinessException businessException) {try {HttpServletUtil.single().handlerBusinessException(businessException, httpServletResponse);} catch (Exception e) {logger.warn("校验google验证绑定情况失败时将信息写入到response时失败," + e.getMessage());}logger.error("用户未绑定google");return;}final String ipHash = RedisUtil.single().getMd5Key(IPUtil.single().getIpAddress(httpServletRequest));Boolean isValidatePass = RedisUtil.single().get(__MFA_TOKEN_USER_CACHE_KEY + user.getId() + ":" + ipHash);// 如果规定时间内校验过并且未过期,那么不校验if (isValidatePass != null && isValidatePass) {filterChain.doFilter(httpServletRequest, httpServletResponse);} else {try {HttpServletUtil.single().handlerBusinessException(new BusinessException(CORRELATION_GOOGLE_NOT_VERIFY_OR_EXPIRE.value(),CORRELATION_GOOGLE_NOT_VERIFY_OR_EXPIRE.description(), HttpStatus.BAD_REQUEST.value()), httpServletResponse);} catch (Exception e) {logger.warn("校验google验证是否有效时将信息写入到response时失败," + e.getMessage());}logger.error("用户google未校验或者校验失效了");}}}
}

总结

本篇内容介绍了如何绑定及在操作过程中的定时的Google验证器的行为校验及如何检测的处理,下篇将介绍一些工具类和业务强制校验的实现过程

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

深度解析Spring AI:请求与响应机制的核心逻辑

开始解析 首先&#xff0c;对于还没有项目的同学&#xff0c;请务必安装所需的POM依赖项。请注意&#xff0c;JDK的版本要求为17。因此&#xff0c;你可以在IDEA中轻松下载和配置这个版本。 <?xml version"1.0" encoding"UTF-8"?> <project xml…

查询产品所涉及的表有(product、product_admin_mapping)

文章目录 1、ProductController2、AdminCommonService3、ProductApiService4、ProductCommonService5、ProductSqlService1. 完整SQL分析可选部分&#xff08;条件筛选&#xff09;&#xff1a; 2. 涉及的表3. 总结4. 功能概述 查询指定管理员下所有产品所涉及的表&#xff1f;…

STM32MX 配置CANFD收发通讯

一、环境 MCU&#xff1a;STM32G0B1CEU6 CAN收发器&#xff1a;JIA1042 二、MX配置 配置SYS 配置canfd并开启中断&#xff0c;我开了两个FDCAN&#xff0c;配置是一样的&#xff0c;这里贴一下波特率的计算公式&#xff1a; 也就是&#xff1a;CAN时钟频率/预分频器/&…

系统思考—战略决策

最近与一位企业创始人深入交流&#xff0c;聊到了他这几年来的多次尝试与探索。回顾过去&#xff0c;他尝试了很多方向&#xff0c;投入了大量的精力与资源&#xff0c;但今天他却感到&#xff0c;无论哪个业务模块&#xff0c;都没有真正突破&#xff0c;原本的业务也未见明显…

【NOIP 2024】遗失的赋值

[Problem Discription] \color{blue}{\texttt{[Problem Discription]}} [Problem Discription] [Analysis] \color{blue}{\texttt{[Analysis]}} [Analysis] 疑似某退役 OIer 重新回归打 NOIP。 个人觉得比 T1 要简单&#xff0c;主要是贪心题是真的不敢写。 首先&#xff0c…

day05【入门】MySQL学习(2)

今日继续学习MySql数据库部分&#xff0c;这块用的比较多的是带有各种条件的select。 目录 1、students表准备&#xff08;查询&#xff09; 2、字段的别名 3、表的别名 4、distinct 过滤重复记录 5、where子句 6、select 查询的基本规律 7、比较运算法 8、逻辑运算符 …

江南大学《2024年807自动控制原理真题》 (完整版)

本文内容&#xff0c;全部选自自动化考研联盟的&#xff1a;《江南大学807自控考研资料》的真题篇。后续会持续更新更多学校&#xff0c;更多年份的真题&#xff0c;记得关注哦~ 目录 2024年真题 Part1&#xff1a;2024年完整版真题 2024年真题

PDF文件页面转换成图片怎么弄-免费PDF编辑工具分享

>>更多PDF文件处理应用技巧请前往 96缔盟PDF处理器 主页 查阅&#xff01; —————————————————————————————————————— 序言 我之前的文章也有介绍过如何使用96缔盟PDF处理器对PDF文件转换成图片&#xff0c;但是当时是使用DMPDFU…

菲涅尔透镜加工:倚光科技的光学创新之路

在光学元件的广袤星空中&#xff0c;菲涅尔透镜以其独特的设计和卓越的性能闪耀着独特光芒。菲涅尔透镜通过将透镜表面由一系列同心棱纹组成&#xff0c;大幅减少了材料的使用量&#xff0c;却依然能够有效地汇聚或发散光线&#xff0c;在众多领域展现出无可比拟的优势&#xf…

电机瞬态分析基础(14):电机的电磁转矩

1. 电机的电磁转矩和转子运动方程 在电机驱动系统中&#xff0c;电动机向其驱动的负载提供驱动转矩&#xff0c;对负载运动的控制是通过对电动机电磁转矩的控制而实现的&#xff0c;如图1所示。 图1. 电动机驱动系统 由图1&#xff0c;根据动力学原理&#xff0c;可列写出机械运…

节点操作+

DOM节点查找节点增加节点删除节点 查找父节点&#xff1a; 想要关闭三个类名都为box1的其中一个&#xff0c;点哪个关哪个 查找子节点&#xff1a; 增加节点&#xff1a; 放到后面 放到前面&#xff08;两个参数&#xff09; 删除节点&#xff1a;

VUE拖拽对象到另一个区域

最近有个需求是需要在web端定制手机的界面UI&#xff08;具体实现比较复杂&#xff0c;此处不做阐述&#xff0c;此文章只说明拖拽效果实现&#xff09;&#xff0c;为了方便用户操作&#xff0c;就想实现这种效果&#xff1a;从右侧的图标列表中拖拽图标到左侧模拟的手机界面上…

优化 LabVIEW 系统内存使用

在 LabVIEW 中&#xff0c;内存使用管理是确保高效系统性能的关键因素&#xff0c;尤其是在进行复杂的数据采集、信号处理和控制任务时。LabVIEW 程序的内存消耗可能会随着项目的规模和复杂度增加&#xff0c;导致性能下降&#xff0c;甚至出现内存溢出或程序崩溃。通过合理优化…

一个实用的端到端的深度学习库存模型

G1 文章信息 文章题为“A Practical End-to-End Inventory Management Model withDeep Learning”&#xff0c;该文于2022年发表至“MANAGEMENT SCIENCE”。文章的核心是提出了端到端的框架用于多周期库存补货问题。 2 摘要 文章研究了一个数据驱动的多周期库存补货问题&am…

STL-需求分析

本小节主要是对要实现的各个功能梳理&#xff0c;理解各个设计之间的关联。&#xff08;未完结&#xff09; 1 list数据结构 可以毫不夸张的说&#xff0c;我们整个项目都是围绕list展开的。因此&#xff0c;我们首先得清楚要使用哪种list。 有请灵魂画手登场&#xff1a; …

STM32进阶 定时器3 通用定时器 案例1:LED呼吸灯——PWM脉冲

功能 它有基本定时器所有功能&#xff0c;还增加以下功能 TIM2、TIM3、TIM4、TIM5 多种时钟源&#xff1a; 外部时钟源模式1&#xff1a; 每个定时器有四个输入通道 只有通道1和通道2的信号可以作为时钟信号源 通道1 和通道2 的信号经过输入滤液和边缘检测器 外部时钟源…

Chrome控制台 网站性能优化指标一览

打开chrome-》f12/右键查看元素-》NetWrok/网络 ctrlF5 刷新网页&#xff0c;可以看到从输入url到页面资源请求并加载网页&#xff0c;用于查看资源加载&#xff0c;接口请求&#xff0c;评估网页、网站性能等&#xff0c;如下图&#xff1a; request、stransferred、resour…

buu ciscn_2019_ne_5

下载附件然后checksec一下如图 32位的程序&#xff0c;nx保护开的&#xff0c;存在栈溢出&#xff0c;拖进ida32中看看 梳理思路&#xff1a; 简单分析并写个注释&#xff0c;这边梳理一下大致流程&#xff0c;先是输入一字符串&#xff0c;然后比对&#xff0c;然后再选择相…

如何降低DApp开发中的Gas费消耗?

Gas费是链上运行DApp时的一项关键成本&#xff0c;直接影响用户体验和应用的吸引力。过高的Gas费可能导致用户流失&#xff0c;尤其在交易密集型应用中。因此&#xff0c;优化Gas费已成为DApp开发者的重要任务。那么&#xff0c;怎样才能有效降低Gas费消耗呢&#xff1f; 1. 优…

CC工具箱使用指南:【湖北省村规结构调整表(D)】

一、简介 群友定制工具。 工具根据输入的现状用地和规划用地图层&#xff0c;生成村域和村庄建设边界内的结构调整表。 二、工具参数介绍 点击【定制2】组里的【湖北省村规结构调整表(D)】工具&#xff1a; 即可打开下面的工具框界面&#xff1a; 1、现状用地图层 2、现状…