首页 >邮件操作 > 内容

两因素身份验证增强您的Spring Security

2023年9月30日 20:33

通过要求用户提供第二种身份验证,双重身份验证为您的Web应用程序增加了一层额外的安全保护。 常见的第二个因素包括:

  • 验证码生物识别电子邮件或短信代码

让我们探讨如何利用Nexmo向现有的Web应用程序中添加双重身份验证。

Before you begin

为了跟随本教程,您将需要以下内容:

Get the Code

克隆入门科。

git clone https://github.com/cr0wst/demo-twofactor.git -b getting-startedcd demo-twofactor

Let’s See What We’re Working With

The example application is built using Spring Boot. If you have Gradle installed on your system, you should be able to execute the bootRun task to start the application.

如果没有,则无后顾之忧; 仓库包含一个Gradle包装器,仍然可以让您执行任务。

./gradlew bootRun

这将下载所有依赖项,编译应用程序,然后启动嵌入式服务器。

Once the server has been started, you should be able to navigate to http://localhost:8080 to see the sample application.

共三页:

  • The home page - accessible by everybody.
  • The login page - allows users to enter a username and password (default is demo/demo).
  • The secret page - accessible only by users with the Role.USERS role.

Adding Two-Factor Authentication

用户登录时,我们唯一的接受标准是他们提供了用户名和密码。 如果此信息被盗怎么办? 我们可以使用的什么东西实际上位于用户附近?

我保证您和我们的用户中有将近90%会触手可及。 一部手机。

运作方式如下:

  1. 用户将照常登录到您的应用程序。他们将被提示输入四位数的验证码。同时,四位数的验证码将发送到他们帐户中的电话号码。 如果他们的帐户上没有电话号码,我们将允许他们绕过两因素身份验证。他们输入的代码将被检查以确保与我们发送给他们的代码相同。

We are going to utilize the Nexmo Verify API to generate the code and to check and see if the code they entered is valid.

Creating a New Role

第一步将是创建一个新角色。 该角色将用于使经过身份验证的用户处于炼狱状态,直到我们验证了他们的身份。

添加PRE_VERIFICATION_USER角色角色枚举。

// src/main/net/smcrow/demo/twofactor/user/Role.javapublic enum Role implements GrantedAuthority {    USER, PRE_VERIFICATION_USER;    // ...}

为了将其用作默认角色,我们需要更新getAuthorities()的方法标准用户详细信息类。

// src/main/net/smcrow/demo/twofactor/user/StandardUserDetails.java@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {    Set<GrantedAuthority> authorities = new HashSet<>();    authorities.add(Role.PRE_VERIFICATION_USER);    return authorities;}

Handling Verification Information

Nexmo将为我们提供要求编号确认用户提供的代码时将使用该代码。 我们可以通过多种方式存储此信息。 在本教程中,我们将其持久化到数据库中。

Storing Verification Information

首先,建立一个验证中的课程校验包。

// src/main/net/smcrow/demo/twofactor/verify/Verification.java@Entitypublic class Verification {    @Id    @Column(unique = true, nullable = false)    private String phone;    @Column(nullable = false)    private String requestId;    @Column(nullable = false)    private Date expirationDate;    @PersistenceConstructor    public Verification() {        // Empty constructor for JPA    }    public Verification(String phone, String requestId, Date expirationDate) {        this.phone = phone;        this.requestId = requestId;        this.expirationDate = expirationDate;    }    // ... Getters and Setters}

注意,我们还存储了截止日期。 默认情况下,Verify API请求仅有效五分钟。 在以下情况之一时,它们将从表中删除:

  • 用户已成功验证其身份。它们已过期。

我们将利用Spring Scheduler定期清理它们。

Working with the Verification Information

创建验证库界面校验包。

// src/mainnet/smcrow/demo/twofactor/verify/VerificationRepository.java@Repositorypublic interface VerificationRepository extends JpaRepository<Verification, String> {    Optional<Verification> findByPhone(String phone);    void deleteByExpirationDateBefore(Date date);}

Deleting Expired Requests

在里面两个因素包,创建以下配置类。

// src/main/net/smcrow/demo/twofactor/ScheduleConfiguration.java@Configuration@EnableSchedulingpublic class ScheduleConfiguration {    @Autowired    private VerificationRepository verificationRepository;    @Scheduled(fixedDelay = 1000)    @Transactional    public void purgeExpiredVerifications() {        verificationRepository.deleteByExpirationDateBefore(new Date());    }}

这将设置一个计划的命令,使其每秒钟执行一次,以查询是否已过期验证实体并删除它们。

Setting Up the Nexmo Client

We will be using the nexmo-java client for interacting with Nexmo.

Declare the Dependency

首先,在build.gradle文件。

dependencies {    // .. other dependencies    compile('com.nexmo:client:3.3.0')}

Provide Information

现在,在application.properties文件。

# Add your nexmo credentialsnexmo.api.key=your-api-keynexmo.api.secret=your-api-secret

Define the Beans

接下来,我们将定义NexmoClient和验证客户作为豆。 这将允许Spring将它们作为依赖项注入到我们的NexmoVerificationService。

将以下定义添加到双重因素应用类。

// src/main/net/smcrow/demo/twofactor/TwofactorApplication.java@Beanpublic NexmoClient nexmoClient(Environment environment) {    AuthMethod auth = new TokenAuthMethod(            environment.getProperty("nexmo.api.key"),            environment.getProperty("nexmo.api.secret")    );    return new NexmoClient(auth);}@Beanpublic VerifyClient nexmoVerifyClient(NexmoClient nexmoClient) {    return nexmoClient.getVerifyClient();}

Create the NexmoVerificationService

我们将创建一个服务,该服务将允许我们指示客户提出请求。

添加NexmoVerificationService到校验包。

// src/main/net/smcrow/demo/twofactor/verify/NexmoVerificationService.java@Servicepublic class NexmoVerificationService {    private static final String APPLICATION_BRAND = "2FA Demo";    private static final int EXPIRATION_INTERVALS = Calendar.MINUTE;    private static final int EXPIRATION_INCREMENT = 5;    @Autowired    private VerificationRepository verificationRepository;    @Autowired    private UserRepository userRepository;    @Autowired    private VerifyClient verifyClient;    public Verification requestVerification(String phone) throws VerificationRequestFailedException {        Optional<Verification> matches = verificationRepository.findByPhone(phone);        if (matches.isPresent()) {            return matches.get();        }        return generateAndSaveNewVerification(phone);    }    public boolean verify(String phone, String code) throws VerificationRequestFailedException {        try {            Verification verification = retrieveVerification(phone);            if (verifyClient.check(verification.getRequestId(), code).getStatus() == 0) {                verificationRepository.delete(phone);                return true;            }            return false;        } catch (VerificationNotFoundException e) {            requestVerification(phone);            return false;        } catch (IOException | NexmoClientException e) {            throw new VerificationRequestFailedException(e);        }    }    private Verification retrieveVerification(String phone) throws VerificationNotFoundException {        Optional<Verification> matches = verificationRepository.findByPhone(phone);        if (matches.isPresent()) {            return matches.get();        }        throw new VerificationNotFoundException();    }    private Verification generateAndSaveNewVerification(String phone) throws VerificationRequestFailedException {        try {            VerifyResult result = verifyClient.verify(phone, APPLICATION_BRAND);            if (StringUtils.isBlank(result.getErrorText())) {                String requestId = result.getRequestId();                Calendar now = Calendar.getInstance();                now.add(EXPIRATION_INTERVALS, EXPIRATION_INCREMENT);                Verification verification = new Verification(phone, requestId, now.getTime());                return verificationRepository.save(verification);            }        } catch (IOException | NexmoClientException e) {            throw new VerificationRequestFailedException(e);        }        throw new VerificationRequestFailedException();    }}

此类中有两种主要方法:

  • requestVerficiation用来请求验证。校验 which is used to 校验 the provided code provided by the user.
The requestVerification Method

该方法首先检查看看我们是否已经有针对该用户电话号码的待定验证请求。 如果用户尝试再次登录到应用程序,这可以使我们向用户提供相同的请求ID。

如果没有任何先前的验证,则需要一个新的验证码并将其保存到数据库中。 如果由于某种原因,我们无法为他们分配新的代码,验证RequestFailedException被抛出。

将此例外添加到校验包。

// src/main/net/smcrow/demo/twofactor/verify/VerificationRequestFailedException.javapublic class VerificationRequestFailedException extends Throwable {    public VerificationRequestFailedException() {        this("Failed to verify request.");    }    public VerificationRequestFailedException(String message) {        super(message);    }    public VerificationRequestFailedException(Throwable cause) {        super(cause);    }}
The verify Method

的校验方法将请求ID和代码发送到Nexmo进行验证。 如果验证成功,Nexmo将返回零状态。 成功验证后,验证实体已从数据库中删除,并且真正返回。

如果我们找不到验证实体,也许它已过期,我们请求一个新的实体并返回false。 如果有任何问题需要验证,我们会抛出一个验证RequestFailedException。

的检索验证方法会抛出一个验证NotFoundException如果验证找不到。

将此例外添加到校验包。

// src/main/net/smcrow/demo/twofactor/verify/VerificationNotFoundException.javapublic class VerificationNotFoundException extends Throwable {    public VerificationNotFoundException() {        this("Failed to find verification.");    }    public VerificationNotFoundException(String message) {        super(message);    }}

Using the NexmoVerificationService

我们将使用该服务发送代码和验证代码。 验证成功后,便会发送代码。

Triggering the Request for Verification

让我们实施一个自定义AuthenticationSuccessHandler用户成功通过身份验证后将被调用。

添加TwoFactorAuthenticationSuccessHandler到校验包。

// src/main/net/smcrow/demo/twofactor/verify/TwoFactorAuthenticationSuccessHandler.java@Componentpublic class TwoFactorAuthenticationSuccessHandler implements AuthenticationSuccessHandler {    private static final String VERIFICATION_URL = "/verify";    private static final String INDEX_URL = "/";    @Autowired    private NexmoVerificationService verificationService;    @Autowired    private UserRepository userRepository;    @Override    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {        String phone = ((StandardUserDetails) authentication.getPrincipal()).getUser().getPhone();        if (phone == null || !requestAndRegisterVerification(phone)) {            bypassVerification(request, response, authentication);            return;        }        new DefaultRedirectStrategy().sendRedirect(request, response, VERIFICATION_URL);    }    private boolean requestAndRegisterVerification(String phone) {        try {            return verificationService.requestVerification(phone) != null;        } catch (VerificationRequestFailedException e) {            return false;        }    }    private void bypassVerification(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {        verificationService.updateAuthentication(authentication);        new DefaultRedirectStrategy().sendRedirect(request, response, INDEX_URL);    }}

用户成功通过身份验证后,我们将检查他们是否具有电话号码。

如果他们有电话号码,则将密码发送到他们的设备。 如果他们没有电话号码,或者我们无法发送代码,则允许他们绕过验证。

的绕过验证方法依赖于updateAuthentication的方法NexmoVerificationService。

将此添加到NexmoVerificationService:

// src/main/net/smcrow/demo/twofactor/verify/NexmoVerificationService.javapublic void updateAuthentication(Authentication authentication) {    Role role = retrieveRoleFromDatabase(authentication.getName());    List<GrantedAuthority> authorities = new ArrayList<>();    authorities.add(role);    Authentication newAuthentication = new UsernamePasswordAuthenticationToken(            authentication.getPrincipal(),            authentication.getCredentials(),            authorities    );    SecurityContextHolder.getContext().setAuthentication(newAuthentication);}private Role retrieveRoleFromDatabase(String username) {    Optional<User> match = userRepository.findByUsername(username);    if (match.isPresent()) {        return match.get().getRole();    }    throw new UsernameNotFoundException("Username not found.");}

此方法用于分配角色在数据库中为当前用户定义并删除PRE_VERIFICATION_USER 角色.

Prompting The User for a Code

Once the user has been sent a code, they are forwarded to the verification page. Let’s work on creating that page next.

创建一个新的HTML文件,名为verify.html在里面资源/模板目录。

<!DOCTYPE html><html lang="en"      xmlns:th="http://www.thymeleaf.org"      xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"      layout:decorator="default"><head>    <meta charset="UTF-8" />    <title>Two Factor Authorization Demo</title></head><body><div layout:fragment="content" class="container">    <div class="col-lg-12 alert alert-danger text-center" th:if="${param.error}">There was an error with your login.</div>    <div class="col-lg-4 offset-lg-4 text-left">        <form th:action="@{/verify}" method="post">            <h1>Verify</h1>            <p>A text message has been sent to your mobile device. Please enter the code below:</p>            <div class="form-group">                <label for="code">Verification Code</label>                <input type="text" class="form-control" id="code" name="code" placeholder="4-Digit Code" />            </div>            <button type="submit" class="btn btn-primary">Verify</button>        </form>    </div></div></body></html>

我们还需要一个控制器来将页面提供给用户。 创建验证控制器在里面校验包。

// src/main/net/smcrow/demo/twofactor/verify/VerificationController.java@Controllerpublic class VerificationController {    @Autowired    private NexmoVerificationService verificationService;    @PreAuthorize("hasRole('PRE_VERIFICATION_USER')")    @GetMapping("/verify")    public String index() {        return "verify";    }    @PreAuthorize("hasRole('PRE_VERIFICATION_USER')")    @PostMapping("/verify")    public String verify(@RequestParam("code") String code, Authentication authentication) {        User user = ((StandardUserDetails) authentication.getPrincipal()).getUser();        try {            if (verificationService.verify(user.getPhone(), code)) {                verificationService.updateAuthentication(authentication);                return "redirect:/";            }            return "redirect:verify?error";        } catch (VerificationRequestFailedException e) {            // Having issues generating keys let them through.            verificationService.updateAuthentication(authentication);            return "redirect:/";        }    }}

该控制器通过指数方法,并通过来处理表单提交校验方法。

只有拥有以下内容的用户才能访问此页面:PRE_VERIFICATION_USER角色。 成功验证后,updateAuthentication方法再次被用来将其保留为原来的角色。

Finishing Up the Verification Chain

最后一步是更新AppSecurityConfiguration使用我们的TwoFactorAuthenticationSuccessHandler。

修改AppSecurityConfiguration连接我们的处理程序并通过successHandler方法。

// src/main/net/smcrow/demo/twofactor/AppSecurityConfiguration.java@Autowiredprivate TwoFactorAuthenticationSuccessHandler twoFactorAuthenticationSuccessHandler;@Overrideprotected void configure(HttpSecurity httpSecurity) throws Exception {    // Webjar resources    httpSecurity.authorizeRequests().antMatchers("/webjars/**").permitAll()    .and().formLogin().loginPage("/login").permitAll()            .successHandler(twoFactorAuthenticationSuccessHandler)    .and().logout().permitAll();}

Try it Out!

您需要将您的电话号码添加到data.sql文件。

We aren’t going to be doing any validation on the phone number, and it needs to be in E.164 format.

INSERT INTO user (username, password, role, phone) VALUES    ('phone', 'phone', 'USER', 15555555555);

您现在应该启动并运行了。 启动应用程序,然后尝试登录。假设您的API密钥,API机密和种子电话号码正确; 您应该会收到一条包含四位数代码的短信。

What Did We Do?

我们做了一个很多东西的。

简而言之,我们实施了两因素身份验证以更好地保护我们的应用程序。 我们这样做是:

  • 创建一个自定义AuthenticationSuccessHandler在向他们提供代码后将用户转发到验证页面。使用nexmo-java库,将其包装在NexmoVerificationService,以向我们的用户发送验证码。利用Spring Scheduler删除过期的验证码。建立一个页面供用户输入他们的验证码。

Check out the final code from this tutorial on GitHub.

Looking Ahead

有两种方法可以实现两因素身份验证。 如果您对示例代码中使用的任何框架和技术感到好奇,请参考以下清单:

Don’t forget that you can be a Nexmo contributor to the nexmo-java client.

from: https://dev.to//cr0wst/beefing-up-your-spring-security-with-two-factor-authentication-4m5p


参考文章:https://blog.csdn.net/cunxiedian8614/article/details/105698326

郑重声明:本文版权归原作者所有,转载文章仅为传播更多信息之目的,如作者信息标记有误,请第一时候联系我们修改或删除,在此表示感谢。

特别提醒:

1、请用户自行保存原始数据,为确保安全网站使用完即被永久销毁,如何人将无法再次获取。

2、如果上次文件较大或者涉及到复杂运算的数据,可能需要一定的时间,请耐心等待一会。

3、请按照用户协议文明上网,如果发现用户存在恶意行为,包括但不限于发布不合适言论妄图

     获取用户隐私信息等行为,网站将根据掌握的情况对用户进行限制部分行为、永久封号等处罚。

4、如果文件下载失败可能是弹出窗口被浏览器拦截,点击允许弹出即可,一般在网址栏位置设置

5、欢迎将网站推荐给其他人,网站持续更新更多功能敬请期待,收藏网站高效办公不迷路。

      



登录后回复

共有0条评论