目前,在大多情况下,尤其是对于企业级应用、或处理敏感数据的应用(如:财务应用)场景中,多因素身份认证(Multi-factor authentication,MFA)已成为了最常见的处理方法。此外,MFA也被相关法律要求在越来越多的行业中是强制执行(尤其是在欧盟)。因此,如果您正在开发应用程序,那么大概率会以某种形式启用双(或多)因素身份认证。
成都创新互联公司服务紧随时代发展步伐,进行技术革新和技术进步,经过十多年的发展和积累,已经汇集了一批资深网站策划师、设计师、专业的网站实施团队以及高素质售后服务人员,并且完全形成了一套成熟的业务流程,能够完全依照客户要求对网站进行成都做网站、网站制作、建设、维护、更新和改版,实现客户网站对外宣传展示的首要目的,并为客户企业品牌互联网化提供全面的解决方案。
在本文中,我将向您展示如何为使用Spring Webflux,来构建的响应式API,以实现两因素身份认证。该应用主要使用电子邮件和密码对作为第一安全因素,并采用用户设备上应用程序(如:Google Authenticator)所生成的一次性代码(TOTP)作为第二安全因素。
两因素身份认证是如何工作的
从技术上讲,两(或多)因素身份认证是一个安全过程,用户必须提供两个或更多安全因素来让自己得到认证。也就是说,用户需要提供除密码以外的另一个标识符,例如:一次性密码、硬件令牌、生物特征(如:指纹)等。
该安全过程涉及到如下步骤:
由此可见,与使用短信传递口令代码相比,使用诸如Google Authenticator、Microsoft Authenticator、以及FreeOTP等身份认证应用,既能够避免SIM卡遭受攻击(请参见--
https://www.theverge.com/2017/6/17/15772142/how-to-set-up-two-factor-authentication),又能够无需蜂窝网络或互联网连接,进行正常认证。
应用示例
下面,我们将逐步构建一个使用两因素身份认证技术的简单REST API。该API要求用户提供电子邮件密码对,和由应用生成的短代码。在此,我使用Android版的Google Authenticator来生成TOTP。其源代码的github库链接为--
https://github.com/mednikoviurii/spring-twofactor-example。该应用会用到JDK 11、Maven、以及用于存储用户个人信息的MongoDB。其项目组织结构如下图所示:
应用示例的项目结构
在此,我不会遍历地介绍每一个组成部分,而只会专注于AuthService、TokenManager和TotpManager。这些部分主要负责身份的认证流程。它们分别提供了以下功能:
由于在此仅关注认证组件,因此我们将从用户的创建过程(注册)开始,同时涉及到密钥的生成和令牌的颁发。接着,我们将进入登录流程,涉及一个由用户提供的短代码的断言。
实现注册流程
下面,我们将完成一个注册的过程,其中涉及以下步骤:
我将主要的业务逻辑(AuthServiceImpl)与令牌的生成,以及密钥的产生分离开来。
一般步骤
主要组件AuthServiceImpl会接受SignupRequest,并返回SignupResponse。在后台,它负责整个注册的逻辑。下面是具体的实现代码:
Java
- 1. @Override
- 2. public Mono
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
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;
下面,让我们逐步解读上述实现的过程。在逻辑判读中:如果当前用户是新用户,我们将对其进行注册;如果该用户已经存在于数据库之中,那么我们就必须拒绝该请求。具体步骤为:
相比以漏洞和安全问题而闻名的SHA函数,我在此选用jBcrypt库(请参见-- https://www.mindrot.org/projects/jBCrypt/),来产生各种安全的哈希和salt(盐)。如不你不太熟悉jBcrypt的话,请参见教程--
https://dzone.com/articles/password-encryption-and-decryption-using-bcrypt,以获取更多信息。
生成密钥
接下来,我们需要实现一个用来生成新的密钥的函数。它是由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
login(LoginRequest request) { - 3. String email = request.getEmail().trim().toLowerCase();
- 4. String password = request.getPassword();
- 5. String code = request.getCode();
- 6. Mono
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. }
可见,后台的逻辑步骤为:
断言一次性代码
为了认证由应用生成的一次性代码,我们必须向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
当前名称:如何在SpringWebflux中实现双因素认证
网站网址:http://www.csdahua.cn/qtweb/news1/266501.html
网站建设、网络推广公司-快上网,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等
声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 快上网