2FA-双因素认证
双因素认证(2FA,Two-Factor Authentication)是一种提高安全性的方法,要求用户在登录或进行某些敏感操作时提供两种不同类型的身份验证信息。这种方法通过引入第二层验证,增加了账户被未经授权访问的难度。
项目结构
spring-boot-2fa-demo
├── src
│ ├── main
│ │ ├── java
│ │ │ └── com
│ │ │ └── example
│ │ │ └── demo
│ │ │ ├── DemoApplication.java
│ │ │ ├── security
│ │ │ │ ├── SecurityConfig.java
│ │ │ │ ├── TotpAuthenticationFilter.java
│ │ │ │ ├── TotpAuthenticationProvider.java
│ │ │ │ ├── TotpAuthenticationToken.java
│ │ │ │ └── TotpAuthenticator.java
│ │ │ └── web
│ │ │ ├── TotpSetupController.java
│ │ │ └── TotpVerifyController.java
│ └── main
│ └── resources
│ └── application.properties
└── pom.xml
1. pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.example</groupId><artifactId>demo</artifactId><version>0.0.1-SNAPSHOT</version><name>spring-boot-2fa-demo</name><description>Spring Boot 2FA Demo</description><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.7.0</version><relativePath/> <!-- lookup parent from repository --></parent><dependencies><!-- Spring Boot Starter Web --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- Spring Boot Starter Security --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><!-- TOTP Library --><dependency><groupId>de.taimos</groupId><artifactId>totp</artifactId><version>1.0.0</version></dependency><!-- Spring Boot Starter Test --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build>
</project>
2. DemoApplication.java
package com.example.demo; import com.example.demo.demo.security.TotpAuthenticator;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext; @SpringBootApplication
public class DemoApplication { public static void main(String[] args) { ApplicationContext context = SpringApplication.run(DemoApplication.class, args); String[] beanNames = context.getBeanNamesForType(TotpAuthenticator.class); for (String beanName : beanNames) { System.out.println("Found bean: " + beanName); }}
}```### 3. Security 配置#### `SecurityConfig.java````java
package com.example.demo.demo.security; import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; @Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() // 配置不需要认证的路径 .antMatchers("/login", "/totp-setup", "/totp-verify", "/auth/*","/test/*").permitAll() .anyRequest().authenticated() .and() .formLogin() .loginPage("/login") .defaultSuccessUrl("/totp-verify") .permitAll() .and() // 在用户名密码过滤器之前添加 TOTP 认证过滤器 .addFilterBefore(new TotpAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class); }
}
TotpAuthenticationFilter.java
package com.example.demo.demo.security; import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException; public class TotpAuthenticationFilter extends AbstractAuthenticationProcessingFilter { public TotpAuthenticationFilter() { super(new AntPathRequestMatcher("/totp-verify")); } @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { String totp = request.getParameter("totp"); String username = request.getParameter("username"); // 创建 TOTP 认证令牌 TotpAuthenticationToken token = new TotpAuthenticationToken(username, totp); return this.getAuthenticationManager().authenticate(token); } @Override protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException { SecurityContextHolder.getContext().setAuthentication(authResult); chain.doFilter(request, response); }
}```#### `TotpAuthenticationProvider.java````java
package com.example.demo.demo.security; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetailsService; public class TotpAuthenticationProvider implements AuthenticationProvider { @Autowired private TotpAuthenticator totpAuthenticator; @Autowired private UserDetailsService userDetailsService; @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { String username = authentication.getName(); String totp = (String) authentication.getCredentials(); // 验证 TOTP if (totpAuthenticator.verifyTotp(username, Integer.parseInt(totp))) { return new TotpAuthenticationToken(username, totp, userDetailsService.loadUserByUsername(username).getAuthorities()); } return null; } @Override public boolean supports(Class<?> authentication) { return TotpAuthenticationToken.class.isAssignableFrom(authentication); }
}
TotpAuthenticationToken.java
package com.example.demo.demo.security; import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority; import java.util.Collection; public class TotpAuthenticationToken extends AbstractAuthenticationToken { private final Object principal; private Object credentials; public TotpAuthenticationToken(Object principal, Object credentials) { super(null); this.principal = principal; this.credentials = credentials; setAuthenticated(false); } public TotpAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) { super(authorities); this.principal = principal; this.credentials = credentials; setAuthenticated(true); } @Override public Object getCredentials() { return this.credentials; } @Override public Object getPrincipal() { return this.principal; } @Override public void eraseCredentials() { super.eraseCredentials(); credentials = null; }
}
TotpAuthenticator.java
package com.example.demo.demo.security; import com.warrenstrange.googleauth.GoogleAuthenticator;
import com.warrenstrange.googleauth.GoogleAuthenticatorKey;
import com.warrenstrange.googleauth.GoogleAuthenticatorQRGenerator;
import org.springframework.stereotype.Component; /** * @author lei */@Component
public class TotpAuthenticator { private final GoogleAuthenticator gAuth = new GoogleAuthenticator(); // 生成 TOTP 密钥并返回 GoogleAuthenticatorKey 对象 public GoogleAuthenticatorKey generateSecret() { return gAuth.createCredentials(); } // 获取 TOTP QR 码 URL public String getQRCode(GoogleAuthenticatorKey secret, String account) { return GoogleAuthenticatorQRGenerator.getOtpAuthTotpURL(account, "SpringBootDemo", secret); } // 验证 TOTP public boolean verifyTotp(String secret, int verificationCode) { return gAuth.authorize(secret, verificationCode); }
}
4. 控制器
TotpSetupController.java
package com.example.demo.demo.web; import com.example.demo.demo.dto.QRCodeResponse;
import com.example.demo.demo.security.TotpAuthenticator;
import com.warrenstrange.googleauth.GoogleAuthenticatorKey;
import org.springframework.web.bind.annotation.*; import java.util.HashMap;
import java.util.Map; @RestController
@RequestMapping("/auth")
public class TotpSetupController { private final TotpAuthenticator totpAuthenticator; public TotpSetupController(TotpAuthenticator totpAuthenticator) { this.totpAuthenticator = totpAuthenticator; } // 设置 TOTP 密钥并返回 QR 码 URL @GetMapping("/totp-setup") public Map<String, String> setupTotp(@RequestParam String username) { // 写死一个 TOTP 密钥 String hardCodedSecret = "OZSNQGV44RGY63BL"; GoogleAuthenticatorKey googleAuthenticatorKey = new GoogleAuthenticatorKey.Builder(hardCodedSecret).build(); String qrCodeUrl = totpAuthenticator.getQRCode(googleAuthenticatorKey, username); Map<String, String> response = new HashMap<>(); response.put("secret", hardCodedSecret); response.put("qrCodeUrl", qrCodeUrl); return response; } // 设置 TOTP 密钥并返回 QR 码 URL @GetMapping("/totp-setup1") public QRCodeResponse setupTotp1(@RequestParam String username) { GoogleAuthenticatorKey googleAuthenticatorKey = totpAuthenticator.generateSecret(); // 保存密钥与用户名的关联关系,可以使用数据库等存储 // 这里只是示例,没有实际存储 String qrCodeUrl = totpAuthenticator.getQRCode(googleAuthenticatorKey, username); return new QRCodeResponse(googleAuthenticatorKey.getKey(), qrCodeUrl); }
}
TotpVerifyController.java
package com.example.demo.demo.web; import com.example.demo.demo.security.TotpAuthenticator;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.*; @RestController
@RequestMapping("/test")
public class TotpVerifyController { private final TotpAuthenticator totpAuthenticator; public TotpVerifyController(TotpAuthenticator totpAuthenticator) { this.totpAuthenticator = totpAuthenticator; } @GetMapping("/totp-verify") public String verifyTotp(@RequestParam int totp) { String username = SecurityContextHolder.getContext().getAuthentication().getName(); // 从存储中获取与用户名关联的密钥,这里假设已获取 String secret = "OZSNQGV44RGY63BL"; if (totpAuthenticator.verifyTotp(secret, totp)) { return "2FA 成功!"; } else { return "无效的 TOTP!"; } } @GetMapping("/test1") public String test() { return "hell1"; }}
5. 配置文件
application.properties
server.port=8080
spring.application.name=2FA-Demo
6. 启动项目
确保所有代码都已编写完成,然后运行 DemoApplication.java 启动项目。你可以通过以下步骤测试 2FA 功能:
- 访问
/totp-setup端点生成 TOTP 密钥和 QR 码。 - 使用 Google Authenticator 扫描 QR 码。
- 访问
/totp-verify端点并输入 Google Authenticator 生成的一次性密码。
- 接口输出url可通过二下面工具生成
- 二维码工具:https://www.runoob.com/try/try.php?filename=tryhtml5_QRCode
相关文章:
2FA-双因素认证
双因素认证(2FA,Two-Factor Authentication)是一种提高安全性的方法,要求用户在登录或进行某些敏感操作时提供两种不同类型的身份验证信息。这种方法通过引入第二层验证,增加了账户被未经授权访问的难度。 项目结构 …...
解决 Python 中的 TypeError 错误
解决 Python 中的 TypeError 错误 在 Python 编程中,TypeError 是一种常见的错误,通常发生在尝试对不兼容的类型进行操作时。了解这个错误的原因以及如何有效解决它,对于提高代码的可靠性和可读性至关重要。本文将详细讨论 TypeError 的成因…...
快速学会C 语言基本概念和语法结构
😀前言 本篇博文是关于C 语言的基本概念和语法结构,希望你能够喜欢 🏠个人主页:晨犀主页 🧑个人简介:大家好,我是晨犀,希望我的文章可以帮助到大家,您的满意是我的动力&a…...
Python酷库之旅-第三方库Pandas(172)
目录 一、用法精讲 791、pandas.UInt8Dtype类 791-1、语法 791-2、参数 791-3、功能 791-4、返回值 791-5、说明 791-6、用法 791-6-1、数据准备 791-6-2、代码示例 791-6-3、结果输出 792、pandas.UInt16Dtype类 792-1、语法 792-2、参数 792-3、功能 792-4、…...
Linux系统下minio设置SSL证书进行HTTPS远程连接访问
文章目录 1.配置SSL证书使用HTTPS访问2.MINIO SDK 忽略证书验证3.使用受信任的证书 1.配置SSL证书使用HTTPS访问 生成域名对应的SSL证书,下载Apache版本,我目前只发现Apache这个里面有对应的私钥和证书 私钥重命名为private.key证书重命名为public.crt&…...
npm 包的命名空间介绍,以及@typescript-eslint/typescript-eslint
npm 包的命名空间是一个重要的概念,用于组织和管理相关的包。通过命名空间,开发者可以避免命名冲突、增强包的可读性和可维护性。以下是关于 npm 命名空间的详细介绍,并以 typescript-eslint 作为示例。 1. 命名空间的结构 命名空间的格式为…...
ecovadis评估是什么,有什么提成自己评分等级
EcoVadis评估是一个企业社会责任(CSR)评级平台,旨在评估全球供应链的可持续性和道德情况。以下是对EcoVadis评估的详细介绍以及提升其评分等级的方法: 一、EcoVadis评估概述 定义:EcoVadis评估通过一系列框架评估公司…...
Vue3中ref、toRef和toRefs之间有什么区别?
前言 Vue 3 引入了组合式 API,其中 ref、toRef 和 toRefs 是处理响应式数据的核心工具。作为高级计算机工程师,我们有必要深入理解这些工具的细微差别,以便在实际项目中更加高效地管理状态。本文将详细解析 ref、toRef 和 toRefs 的区别&…...
react开发技巧
/* eslint-disable no-useless-escape */ const Validator { isEmail: /^([a-zA-Z0-9_\.\-])\(([a-zA-Z0-9\-])\.)([a-zA-Z0-9]{2,4})$/, // 校验邮箱 isPhoneNumber: /^1[3456789]\d{9}$/, // 手机号码验证 isMobileNumber: /^(\(\d{3,4}\)|\d{3,4}-|\s)?\d{7,14}$/, //…...
HarmonyOS第一课——HarmonyOS介绍
HarmonyOS第一课 HarmonyOS介绍 HarmonyOS是新一代的智能终端操作系统(泛终端服务的载体); 智慧互联协同,全场景交互体验; 核心技术理念: 一次开发 多次部署: 预览 可视化开发UI适配 事件交…...
XCode16中c++头文件找不到解决办法
XCode16中新建Framework,写完自己的c代码后,提示“<string> file not found”等诸如此类找不到c头文件的错误。 工程结构如下: App是测试应用,BoostMath是Framework。基本结构可以参考官方demo:Mix Swift and …...
CSS - 保姆级面试基础扫盲版本一
盒子模型 盒子模型定义: 当对一个盒子模型进行文档布局的时候,浏览器的渲染引擎会根据标准之一的CSS盒子模型(CSS basic box model),将所有元素表示成一个个矩阵盒子。 一个盒子通常由四部分组成:border p…...
51c自动驾驶~合集2
我自己的原文哦~ https://blog.51cto.com/whaosoft/11491137 #BEVWorld BEV潜在空间构建多模态世界模型,全面理解自动驾驶~一、引言 世界模型建模了有关环境的知识,其可以通过给定的条件对未来进行合理的想象。未来想象要求世界模型具有物理规律的理解…...
Redis后台任务有哪些
Redis后台任务 为了有更好的性能表现,redis对于一些比较耗时的操作会异步执行,不阻塞线上请求。文章从源码(redis7.0)来看,aof、rdb文件的关闭,aof文件的刷盘以及部分内存释放会采用异步方式,在后台线程中执行。接下来…...
TPair<TKey, TValue> 键值对
在 Delphi(或更准确地说是 Object Pascal,Delphi 的编程语言)中,TList<T> 是泛型列表的一个实现,其中 T 是列表中元素的类型。TPair<TKey, TValue> 是一个包含两个元素的记录(record࿰…...
【杂谈】城市规划教育的危与机
城市规划教育的危与机 (赵燕菁 原文为作者在 第21届中国城市规划学科发展论坛上的发言,有删减和改动)如有侵权,立即删除 过去几年,尤其是从2022年后房地产市场的下行开始,中国的城市规划陷入前所未有的危…...
金融工程--pine-script 入门
背景 脚本基本组成 指标 常见的趋势类指标:均线类(MAs)、支撑/压力位(Support/Resistance)、趋势线(Trend Lines)、趋势通道(Trend Channels)、一目均衡表(Ichimoku)和 艾略特波浪(ElliotWave)。 均线指标 策略 策略种类 在TradingView上,有许多交易…...
Vue3 跨标签页或跨窗口通信
在 Vue 应用中,跨标签页或跨窗口的通信通常涉及到两个或多个浏览器标签页之间的信息共享。由于每个标签页或窗口都是独立的 JavaScript 执行环境,它们不能直接通过 Vue 或其他 JavaScript 库来直接相互通信。但是,有一些方法可以实现这种跨标…...
Ollama: 使用Langchain的OllamaFunctions
1. 引言 Function call Langchain的Ollama 的实验性包装器OllamaFunctions,提供与 OpenAI Functions 相同的 API。因为网络的原因,OpenAI Functions不一定能访问,但如果能通过Ollama部署的本地模型实现相关的函数调用,还是有很好…...
java质数的判断 C语言指针变量的使用
1. public static void main(String[] args) {Scanner scnew Scanner(System.in);System.out.println("请输入一个值");int num sc.nextInt();boolean flagtrue;for (int i2;i<num;i){if (num%i0){flagfalse;break;}}if (flag){System.out.println(num"是一…...
springboot交通道路监测感知与车路协同系统可视化大屏
目录技术架构设计数据采集与处理可视化大屏功能模块系统集成与部署关键技术点测试与迭代项目技术支持源码获取详细视频演示 :文章底部获取博主联系方式!同行可合作技术架构设计 采用SpringBoot作为后端框架,提供RESTful API接口;…...
LingBot-Depth部署避坑指南:常见问题与解决方案汇总
LingBot-Depth部署避坑指南:常见问题与解决方案汇总 1. 引言:为什么需要这份指南 当你第一次尝试部署LingBot-Depth时,可能会遇到各种意想不到的问题——从模型下载失败到GPU内存不足,从端口冲突到奇怪的输出结果。这些问题往往…...
别再死记命令了!用EVE-NG模拟器5分钟搞定思科GRE隧道(附OSPF联动配置)
5分钟玩转思科GRE隧道:EVE-NG实战中的高效学习法 第一次在EVE-NG里搭建GRE隧道时,我盯着满屏的命令行发呆——这些配置到底在做什么?为什么tunnel接口要配源和目的地址?OSPF又是怎么和隧道联动的?直到我用Wireshark抓到…...
Wan2.2-T2V-A5B提示词怎么写?新手快速出效果的实用指南
Wan2.2-T2V-A5B提示词怎么写?新手快速出效果的实用指南 1. 认识Wan2.2-T2V-A5B视频生成模型 Wan2.2-T2V-A5B是一款由通义万相开源的轻量级文本到视频生成模型,拥有50亿参数规模。虽然它生成的视频分辨率是480P,但在时序连贯性和运动推理能力…...
OpenClaw故障自愈方案:Qwen3-32B镜像异常重启监控
OpenClaw故障自愈方案:Qwen3-32B镜像异常重启监控 1. 问题背景与解决思路 上周我的OpenClaw自动化助手突然"罢工"了——原本应该定时执行的日报生成任务没有按时完成。排查后发现是底层Qwen3-32B模型服务因OOM异常退出。这种情况在长期运行的AI服务中并…...
告别性能瓶颈:如何用NVIDIA Profile Inspector释放显卡90%潜能?
告别性能瓶颈:如何用NVIDIA Profile Inspector释放显卡90%潜能? 【免费下载链接】nvidiaProfileInspector 项目地址: https://gitcode.com/gh_mirrors/nv/nvidiaProfileInspector 为什么官方显卡控制面板永远像个"锁着的工具箱"&#…...
程序员转行学习 AI 大模型: 提示词工程 | 附精选学习资料
本文是程序员转行学习AI大模型的第12个核心知识点笔记,笔记后附精选的提示词工程学习资料。 当前阶段:还在学习知识点,由点及面,从 0 到 1 搭建 AI 大模型知识体系中。 系列更新,关注我,后续会持续记录分享…...
新能源企业数字化转型:从“卖设备“到“卖服务“的服务管理实践
在"双碳"目标驱动下,新能源产业正经历从"投建"到"运营服务"的战略转型。光伏、风电、储能等设备遍布全国各地,售后服务与运维效率直接关系到发电收益与品牌口碑。 然而,很多新能源企业面临一个共同的困境&…...
[特殊字符] 怕你停电的姐姐:UPS 还分 “直流” 和 “交流”? 今天一篇给你盘个透!
哈喽,我的老铁们!我是你们那个 “怕你停电” 的姐姐,也是专门卖 UPS 电源的姐姐!平时总有朋友问我:“姐姐,我看 UPS 有好多种,什么直流交流的,到底有啥区别?我该咋选&…...
效率革命:设计师必备的Sketch批量命名神器RenameIt完全指南
效率革命:设计师必备的Sketch批量命名神器RenameIt完全指南 【免费下载链接】RenameIt Keep your Sketch files organized, batch rename layers and artboards. 项目地址: https://gitcode.com/gh_mirrors/re/RenameIt 在现代UI/UX设计流程中,保…...
