如何开发两步验证功能

我又来了,,,今天我们来讲一讲两步验证的功能♥♥

简介:

两步验证,是指用户登录账户的时候,除了要输入用户名和密码,还要求用户输入一个动态密码,为帐户添加了一层额外保护。这个动态密码要么是专门的硬件,要么由用户手机APP提供。即使入侵者窃取了用户密码,也会因不能使用用户手机而无法登录帐户。许多游戏客户端和网银采用这种方式。以银行为例,当用户进行转账操作时,第一步输入6位取款密码,第二步输入动态密码器上数字,这个密码器是开户时银行提供的硬件。

动态密码原理:

客户端和服务器事先协商好一个密钥K,用于一次性密码的生成过程,此密钥不被任何第三方所知道。此外,客户端和服务器各有一个计数器C,并且事先将计数值同步。进行验证时,客户端对密钥和计数器的组合(K,C)使用HMAC(Hash-based Message Authentication Code)算法计算一次性密码,公式如下:HOTP(K,C) = Truncate(HMAC-SHA-1(K,C))上面采用了HMAC-SHA-1,当然也可以使用HMAC-MD5等。HMAC算法得出的值位数比较多,不方便用户输入,因此需要截断(Truncate)成为一组不太长十进制数(例如6位)。计算完成之后客户端计数器C计数值加1。用户将这一组十进制数输入并且提交之后,服务器端同样的计算,并且与用户提交的数值比较,如果相同,则验证通过,服务器端将计数值C增加1。如果不相同,则验证失败。

原理:

 第一步:输入常规帐号密码,验证成功后进入二次验证页面。
 第二步:二次验证页面要求用户输入动态密码,用户手机必须先通过APP绑定帐号后才能获取动态密码,APP推荐http://www.pc6.com/az/675682.html
 第三步:页面生成二维码

OK,介绍就到这里了,开始我们的操作了。。。。

第一步:还是先来我们的Pom文件吧:
<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
			<version>${spring.boot.version}</version>
		</dependency>
		<!--二次验证-->
		<dependency>
			<groupId>com.warrenstrange</groupId>
			<artifactId>googleauth</artifactId>
			<version>1.1.2</version>
		</dependency>
		<!--二维码-->
		<dependency>
			<groupId>com.google.zxing</groupId>
			<artifactId>javase</artifactId>
			<version>3.3.3</version>
		</dependency>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<version>1.16.20</version>
			<scope>provided</scope>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-thymeleaf</artifactId>
		</dependency>
	</dependencies>

有没有感觉很简单呢,是的,没多的依赖。。。。

第二步:也是application.yml文件:这些都不多说了吧,,哈哈哈,还是copy一下吧。
server:
  port: 8080

spring:
  application:
    name: two-step-verify-demo

很简短的配置文件

第三步:开始我们的操作,翠花上菜。。。

ActionController:

package com.demo.twostep.controller;
import com.demo.twostep.service.GoogleAuthenticatorService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Slf4j
@RestController
public class ActionController {

    @Autowired
    private GoogleAuthenticatorService googleAuthenticatorService;

    /**
     * 二次验证,生成二维码
     */
    @RequestMapping("/qrcode")
    public void qrcode(String username, HttpServletResponse response) {
        try (ServletOutputStream stream = response.getOutputStream()) {
            googleAuthenticatorService.genQRImage(username, stream);
        } catch (IOException e) {
            log.error("发生错误", e);
        }
    }

    /**
     * 二次验证,输入eagle2fa APP上的6位数字
     */
    @RequestMapping("/verify")
    public String verify(String username, int code) {

        boolean validCode = googleAuthenticatorService.validCode(username, code);

        if (validCode){

            return "index";
        }

        return "error";
    }


}

copy可能会有错误提示,不用管它,下面就开始盘他。。

GoogleAuthenticatorService

package com.demo.twostep.service;
import com.demo.twostep.dao.UserDao;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.WriterException;
import com.google.zxing.client.j2se.MatrixToImageWriter;
import com.google.zxing.common.BitMatrix;
import com.warrenstrange.googleauth.GoogleAuthenticator;
import com.warrenstrange.googleauth.GoogleAuthenticatorKey;
import com.warrenstrange.googleauth.ICredentialRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import javax.servlet.ServletOutputStream;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.util.List;

/**
 * @author yangjiajia
 * @create 2018-09-06 16:42
 */
@Slf4j
@Service
public class GoogleAuthenticatorService {

    private static final GoogleAuthenticator googleAuthenticator = new GoogleAuthenticator();
    private static final String KEY_FORMAT = "otpauth://totp/%s?secret=%s";
    private static final String IMAGE_EXT = "png";
    private static final int WIDTH = 300;
    private static final int HEIGHT = 300;

    /**
     * 由于只是演示,这里的dao没有真的操作数据库,真实的场景必然要持久化的
     */
    @Autowired
    private UserDao userDao;

    @PostConstruct
    public void init() {
        googleAuthenticator.setCredentialRepository(new ICredentialRepository() {
            @Override
            public String getSecretKey(String userName) {
                return userDao.getSecretKey(userName);
            }

            @Override
            public void saveUserCredentials(String userName, String secretKey, int validationCode, List<Integer> scratchCodes) {
                //secretKey要保存在数据库中
                userDao.saveUserCredentials(userName, secretKey);
            }
        });
        log.info("GoogleAuthenticator初始化成功");
    }

    /**
     * 生成二维码链接
     */
    private String getQrUrl(String username) {
        //每次调用createCredentials都会生成新的secretKey
        GoogleAuthenticatorKey key = googleAuthenticator.createCredentials(username);
        log.info("username={},secretKey={}", username, key.getKey());
        return String.format(KEY_FORMAT, username, key.getKey());
    }

    public boolean validCode(String username, int code) {
        return googleAuthenticator.authorizeUser(username, code);
    }


    /**
     * 生成二维码文件
     */
    public static void genQRImage(String content, String filePath) {
        try {
            BitMatrix bm = new MultiFormatWriter().encode(content, BarcodeFormat.QR_CODE, WIDTH, HEIGHT);
            Path file = new File(filePath).toPath();
            MatrixToImageWriter.writeToPath(bm, IMAGE_EXT, file);
        } catch (WriterException | IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 生成二维码
     */
    public void genQRImage(String username, ServletOutputStream stream) {
        try {
            String content = getQrUrl(username);
            BitMatrix bm = new MultiFormatWriter().encode(content, BarcodeFormat.QR_CODE, WIDTH, HEIGHT);
            MatrixToImageWriter.writeToStream(bm, IMAGE_EXT, stream);
        } catch (WriterException | IOException e) {
            e.printStackTrace();
        }
    }
}

UserDao(模拟数据库操作)

package com.demo.twostep.dao;
public interface UserDao {

    String getSecretKey(String userName);

    boolean saveUserCredentials(String userName, String secretKey);
}

UserDaoImpl

package com.demo.twostep.dao;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;

@Component
public class UserDaoImpl implements UserDao {

    private final static Map<String, String> keyMap = new HashMap<>();

    @Override
    public String getSecretKey(String userName) {
        return keyMap.get(userName);
    }

    @Override
    public boolean saveUserCredentials(String userName, String secretKey) {
        keyMap.put(userName, secretKey);
        return true;
    }
}

OK,后台代码就完成了哦,接下来就是我们来测试的时候了,
老板,筷子呢,我要尝菜了。。。

首先创建三个页面:

login.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登陆页面</title>
</head>
<body>

<form action="/verify" method="post">

    用户名:<input type="text" name="username" id="username">
    <br>
    密      码:<input type="password" name="password" id="password">
    <br>
    验证码:<input type="text" name="code"><input type="button" value="获取" id="button">
    <br>
    <input type="submit" value="登陆">
</form>

<script type="application/javascript">

    var button  =document.getElementById("button");

    button.onclick = function () {

        // 获取用户输入的信息
        var username = document.getElementById("username").value;

        var password = document.getElementById("password").value;

        if (username == '' || password == ''){

            alert("请填入信息");

        }else {

            window.location.href = '/qrcode?username=fc';
        }

    }
</script>
</body>
</html>

index.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>成功页面</title>
</head>
<body>
登陆成功。。。。
</body>
</html>

error.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>失败页面</title>
</head>
<body>
登陆失败。。。。
</body>
</html>

开始启动我们的项目跑起来,哈哈哈哈(这是我们的启动类)

package com.demo.twostep;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@EnableAutoConfiguration
public class TwoStepVerifyDemoApplicationMain {

    public static void main(String[] args) {
        SpringApplication.run(TwoStepVerifyDemoApplicationMain.class, args);
    }

}

登陆页面:

得到的二维码:


我们使用App扫描,我们会得到一个验证码(可以设置验证码的过期时间)

得到这个就说明成功了。。

输入对应的验证码,登陆就可以访问这个页面了。。

OKOK,本教程全部完成了,感谢的你的浏览。。。。
有什么问题可以在评论区评论,或者留言

2020-03-16 21:37:30 星期一♥♥

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值