别挠了,都快秃了,双因素认证我直接告诉你算了
bigegpt 2024-09-27 00:32 3 浏览
两因素身份认证是如何工作的
从技术上讲,两(或多)因素身份认证是一个安全过程,用户必须提供两个或更多安全因素来让自己得到认证。也就是说,用户需要提供除密码以外的另一个标识符,例如:一次性密码、硬件令牌、生物特征(如:指纹)等。
该安全过程涉及到如下步骤:
- 用户输入电子邮件(用户名)和密码。
- 除了第一凭据,用户还要提交由认证应用生成的一次性代码。
- 应用程序在对电子邮件(用户名)和密码进行身份认证的同时,也使用在注册过程中颁发的用户密钥来认证一次性代码
由此可见,与使用短信传递口令代码相比,使用诸如Google Authenticator、Microsoft Authenticator、以及FreeOTP等身份认证应用,既能够避免SIM卡遭受攻击又能够无需蜂窝网络或互联网连接,进行正常认证。
应用示例
下面,我们将逐步构建一个使用两因素身份认证技术的简单REST API。该API要求用户提供电子邮件密码对,和由应用生成的短代码。该应用会用到JDK 11、Maven、以及用于存储用户个人信息的MongoDB。其项目组织结构如下图所示:
在此,我不会遍历地介绍每一个组成部分,而只会专注于AuthService、TokenManager和TotpManager。这些部分主要负责身份的认证流程。它们分别提供了以下功能:
AuthService –该组件主要用于存储、认证和授权所有的业务逻辑,其中包括:注册、登录和令牌认证。
TokenManager–该组件通过抽象代码,以生成和认证JWT令牌。它能够使得主要业务逻辑的实现与具体的JWT库相互独立。
TotpManager–作为另一种抽象,它能够将实现与基本逻辑相隔离。TotpManager既可被用于生成用户的密钥,又可以断言(assert,可以立即为验证)给出的短代码。
由于在此仅关注认证组件,因此我们将从用户的创建过程(注册)开始,同时涉及到密钥的生成和令牌的颁发。接着,我们将进入登录流程,涉及一个由用户提供的短代码的断言。
实现注册流程
下面,我们将完成一个注册的过程,其中涉及以下步骤:
- 从客户端获取注册请求。
- 检查该用户是否存在。
- 对密码进行哈希。
- 生成一个密钥。
- 将用户存储到数据库中。
- 颁发JWT。
- 返回带有用户ID、私钥和令牌的响应。
我将主要的业务逻辑(AuthServiceImpl)与令牌的生成,以及密钥的产生分离开来。
一般步骤
主要组件AuthServiceImpl会接受SignupRequest,并返回SignupResponse。在后台,它负责整个注册的逻辑。下面是具体的实现代码:
Java
1. @Override
2. public Mono<SignupResponse> signup(SignupRequest request) {
3. // generating a new user entity params
4. // step 1
5. String email = request.getEmail().trim().toLowerCase();
6. String password = request.getPassword();
7. String salt = BCrypt.gensalt();
8. String hash = BCrypt.hashpw(password, salt);
9. String secret = totpManager.generateSecret();
10. User user = new User(null, email, hash, salt, secret);
11. // preparing a Mono
12. Mono<SignupResponse> response = repository.findByEmail(email)
13. .defaultIfEmpty(user) // step 2
14. .flatMap(result -> {
15. // assert, that user does not exist
16. // step 3
17. if (result.getUserId() == null) {
18. // step 4
19. return repository.save(result).flatMap(result2 -> {
20. // prepare token
21. // step 5
22. String userId = result2.getUserId();
23. String token = tokenManager.issueToken(userId);
24. SignupResponse signupResponse = new SignupResponse();
25. signupResponse.setUserId(userId);
26. signupResponse.setSecretKey(secret);
27. signupResponse.setToken(token);
28. signupResponse.setSuccess(true);
29.
30. return Mono.just(signupResponse);
31. });
32. } else {
33. // step 6
34. // scenario - user already exists
35. SignupResponse signupResponse = new SignupResponse();
36. signupResponse.setSuccess(false);
37.
38. return Mono.just(signupResponse);
39. }
40. });
41. return response;
下面,让我们逐步解读上述实现的过程。在逻辑判读中:如果当前用户是新用户,我们将对其进行注册;如果该用户已经存在于数据库之中,那么我们就必须拒绝该请求。具体步骤为:
- 我们根据请求数据创建一个新的用户实体,并生成一个相应的密钥。
- 如果该用户过去不存在,则将给出的新实体作为其默认实体。
- 检查存储库的调用结果。
- 将用户保存在数据库中,并获取其userId。
- 颁发JWT。
- 如果用户已经存在,则返回一个拒绝响应。
相比以漏洞和安全问题而闻名的SHA函数,我在此选用jBcrypt库,来产生各种安全的哈希和salt(盐)。
生成密钥
接下来,我们需要实现一个用来生成新的密钥的函数。它是由TotpManager.generateSecret()内部抽象而来。下面是它的代码:
Java:
1. @Override
2. public String generateSecret() {
3. SecretGenerator generator = new DefaultSecretGenerator();
4. return generator.generate();
5. }
测试
实现了注册逻辑之后,我们需要测试它是否能够按预期进行认证。首先,让我们调用signup端点以创建一个新的用户。其结果对象应当包含我们需要添加到应用生成器(如:Google Authenticator)的userId、令牌和密钥:
不过,我们应当禁止同一封电子邮件两次进行注册。在此,我们通过断言,以保证应用在创建新用户之前,去检查现有的电子邮件列表:
登录
下面,我们来讨论登录流程。该流程包括两个主要部分:认证电子邮件的密码凭据,以及认证由用户提供的一次性代码。和上一节一样,我们首先介绍登录所涉及的步骤:
- 从客户端获取登录请求。
- 在数据库中找到该用户。
- 使用请求中提供的密码进行断言。
- 断言一次性代码。
- 返回带有令牌的登录响应。
而JWT的生成过程与注册的过程比较类似。
一般步骤
作为该示例的功能重点,AuthServiceImpl.login将实现主要的业务逻辑。首先,我们需要通过在数据库中请求电子邮件,来查找用户;否则,我们需要提供带有空字段的默认值。也就是说,让user.getUserId() == null,以表示该用户并不存在,登录流程随即终止。
接着,我们需要断言密码的匹配。当我们将密码的哈希值存储在数据库中时,就需要使用存储的salt对请求中的密码进行哈希处理,进而断言这两个值。
如果密码匹配,我们需要使用之前存储的密钥值来认证提交的代码。认证成功与否的结果,将在产生JWT和创建LoginResponse对象后得出。以下便是此部分的最终源代码:
Java
1. @Override
2. public Mono<LoginResponse> login(LoginRequest request) {
3. String email = request.getEmail().trim().toLowerCase();
4. String password = request.getPassword();
5. String code = request.getCode();
6. Mono<LoginResponse> response = repository.findByEmail(email)
7. // step 1
8. .defaultIfEmpty(new User())
9. .flatMap(user -> {
10. // step 2
11. if (user.getUserId() == null) {
12. // no user
13. LoginResponse loginResponse = new LoginResponse();
14. loginResponse.setSuccess(false);
15.
16. return Mono.just(loginResponse);
17. } else {
18. // step 3
19. // user exists
20. String salt = user.getSalt();
21. String secret = user.getSecretKey();
22. boolean passwordMatch = BCrypt.hashpw(password, salt).equalsIgnoreCase(user.getHash());
23. if (passwordMatch) {
24. // step 4
25. // password matched
26. boolean codeMatched = totpManager.validateCode(code, secret);
27. if (codeMatched) {
28. // step 5
29. String token = tokenManager.issueToken(user.getUserId());
30. LoginResponse loginResponse = new LoginResponse();
31. loginResponse.setSuccess(true);
32. loginResponse.setToken(token);
33. loginResponse.setUserId(user.getUserId());
34.
35. return Mono.just(loginResponse);
36. } else {
37. LoginResponse loginResponse = new LoginResponse();
38. loginResponse.setSuccess(false);
39. return Mono.just(loginResponse);
40. }
41. } else {
42. LoginResponse loginResponse = new LoginResponse();
43. loginResponse.setSuccess(false);
44.
45. return Mono.just(loginResponse);
46. }
47. }
48. });
49. return response;
50. }
可见,后台的逻辑步骤为:
- 提供具有空字段的默认用户实体。
- 检查该用户是否确实存在。
- 从请求和salt处生成密码的哈希,并存储在数据库中。
- 断言密钥是否能够确实匹配。
- 认证一次性代码,并颁发JWT。
断言一次性代码
为了认证由应用生成的一次性代码,我们必须向TOTP库提供相应的代码和密钥,并将它们保存为用户实体的一部分。具体代码如下:
Java
1. @Override
2. public boolean validateCode(String code, String secret) {
3. TimeProvider timeProvider = new SystemTimeProvider();
4. CodeGenerator codeGenerator = new DefaultCodeGenerator();
5. CodeVerifier verifier = new DefaultCodeVerifier(codeGenerator, timeProvider);
6. return verifier.isValidCode(secret, code);
7. }
测试
最后,我们可以通过测试,以认证登录的过程是否如期运行。我们将由Google Authenticator生成的代码作为登录请求的负载,去调用login端点。
如下图所示,为了检查出密码错误的情况,我们需要将进程终止在密码断言阶段:
至此,我们已经创建了一个简单的REST API,它可以通过Spring Webflux的TOTP来提供两因素身份认证。如前文所述,为了更专注于身份认证的逻辑,我们省略了所有的其他部分。
相关推荐
- 有些人能留在你的心里,但不能留在你生活里。
-
有时候,你必须要明白,有些人能留在你的心里,但不能留在你生活里。Sometimes,youhavetorealize,Somepeoplecanstayinyourheart,...
- Python学不会来打我(34)python函数爬取百度图片_附源码
-
随着人工智能和大数据的发展,图像数据的获取变得越来越重要。作为Python初学者,掌握如何从网页中抓取图片并保存到本地是一项非常实用的技能。本文将手把手教你使用Python函数编写一个简单的百度图片...
- 软网推荐:图像变变变 一“软”见分晓
-
当我们仅需要改变一些图片的分辨率、裁减尺寸、添加水印、标注文本、更改图片颜色,或将一种图片转换为另一种格式时,总比较讨厌使用一些大型的图像处理软件,尤其是当尚未安装此类软件时,更是如此。实际上,只需一...
- 首款WP8.1图片搜索应用,搜照片得资料
-
首款WP8.1图片搜索应用,搜照片得资料出处:IT之家原创(天际)2014-11-1114:32:15评论WP之家报道,《反向图片搜索》(ReverseImageSearch)是Window...
- 盗墓笔记电视剧精美海报 盗墓笔记电视剧全集高清种子下载
-
出身“老九门”世家的吴邪,因身为考古学家的父母在某次保护国家文物行动时被国外盗墓团伙杀害,吴家为保护吴邪安全将他送去德国读书,因而吴邪对“考古”事业有着与生俱来的兴趣。在一次护宝过程中他偶然获得一张...
- 微软调整Win11 24H2装机策略:6月起36款预装应用改为完整版
-
IT之家7月16日消息,微软公司今天(7月16日)发布公告,表示自今年6月更新开始,已默认更新Windows1124H2和WindowsServer2025系统中预装...
- 谷歌手把手教你成为谣言终结者 | 域外
-
刺猬公社出品,必属原创,严禁转载。合作事宜,请联系微信号:yunlugongby贾宸琰编译、整理11月23日,由谷歌新闻实验室(GoogleNewsLab)联合Bellingcat、DigD...
- NAS 部署网盘资源搜索神器:全网资源一键搜,免费看剧听歌超爽!
-
还在为找不到想看的电影、电视剧、音乐而烦恼?还在各个网盘之间来回切换,浪费大量时间?今天就教你如何在NAS上部署aipan-netdisk-search,一款强大的网盘资源搜索神器,让你全网资源...
- 使用 Docker Compose 简化 INFINI Console 与 Easysearch 环境搭建
-
前言回顾在上一篇文章《搭建持久化的INFINIConsole与Easysearch容器环境》中,我们详细介绍了如何使用基础的dockerrun命令,手动启动和配置INFINICon...
- 为庆祝杜特尔特到访,这个国家宣布全国放假?
-
(观察者网讯)近日,一篇流传甚广的脸书推文称,为庆祝杜特尔特去年访问印度,印度宣布全国放假,并举办了街头集会以示欢迎。菲媒对此做出澄清,这则消息其实是“假新闻”。据《菲律宾世界日报》2日报道,该贴子...
- 一课译词:毛骨悚然(毛骨悚然的意思是?)
-
PhotobyMoosePhotosfromPexels“毛骨悚然”,汉语成语,意思是毛发竖起,脊梁骨发冷;形容恐惧惊骇的样子(withone'shairstandingonend...
- Bing Overtakes Google in China's PC Search Market, Fueled by AI and Microsoft Ecosystem
-
ScreenshotofBingChinahomepageTMTPOST--Inastunningturnintheglobalsearchenginerace,Mic...
- 找图不求人!6个以图搜图的识图网站推荐
-
【本文由小黑盒作者@crystalz于03月08日发布,转载请标明出处!】前言以图搜图,专业说法叫“反向图片搜索引擎”,是专门用来搜索相似图片、原始图片或图片来源的方法。常用来寻找现有图片的原始发布出...
- 浏览器功能和“油管”有什么关联?为什么要下载
-
现在有没有一款插件可以实现全部的功能,同时占用又小呢,主题主要是网站的一个外观,而且插件则主要是实现wordpress网站的一些功能,它不仅仅可以定制网站的外观,还可以实现很多插件的功能,搭载chro...
- 一周热门
- 最近发表
- 标签列表
-
- mybatiscollection (79)
- mqtt服务器 (88)
- keyerror (78)
- c#map (65)
- xftp6 (83)
- bt搜索 (75)
- c#var (76)
- xcode-select (66)
- mysql授权 (74)
- 下载测试 (70)
- linuxlink (65)
- pythonwget (67)
- androidinclude (65)
- libcrypto.so (74)
- linux安装minio (74)
- ubuntuunzip (67)
- vscode使用技巧 (83)
- secure-file-priv (67)
- vue阻止冒泡 (67)
- jquery跨域 (68)
- php写入文件 (73)
- kafkatools (66)
- mysql导出数据库 (66)
- jquery鼠标移入移出 (71)
- 取小数点后两位的函数 (73)