ToB企服应用市场:ToB评测及商务社交产业平台

标题: SpringBoot项目添加2FA双因素身份认证 [打印本页]

作者: 王海鱼    时间: 2024-5-18 07:49
标题: SpringBoot项目添加2FA双因素身份认证
什么是 2FA(双因素身份验证)?

双因素身份验证(2FA)是一种安全系统,要求用户提供两种不同的身份验证方式才能访问某个系统或服务。国内普遍做短信验证码这种的用的比力少,不过在国外的网站中使用双因素身份验证的照旧许多的。用户通过使用验证器扫描二维码,就能在app上获取登录的动态口令,进一步加强了账户的安全性。
重要步调

pom.xml中增加依赖
  1. <dependency>
  2.     <groupId>commons-codec</groupId>
  3.     <artifactId>commons-codec</artifactId>
  4.     <version>1.15</version>
  5. </dependency>
  6. <dependency>
  7.     <groupId>org.iherus</groupId>
  8.     <artifactId>qrext4j</artifactId>
  9.     <version>1.3.1</version>
  10. </dependency>
复制代码
用户表中增加secretKey列

为用户绑定secretKey字段,用以生成二维码及后期校验

工具类

谷歌身份验证器工具类

[code]/** * 谷歌身份验证器工具类 */public class GoogleAuthenticator {    /**     * 时间前后偏移量     * 用于防止客户端时间不精确导致生成的TOTP与服务器端的TOTP一直不同等     * 如果为0,当前时间为 10:10:15     * 则表明在 10:10:00-10:10:30 之间生成的TOTP 能校验通过     * 如果为1,则表明在     * 10:09:30-10:10:00     * 10:10:00-10:10:30     * 10:10:30-10:11:00 之间生成的TOTP 能校验通过     * 以此类推     */    private static int WINDOW_SIZE = 0;    /**     * 加密方式,HmacSHA1、HmacSHA256、HmacSHA512     */    private static final String CRYPTO = "HmacSHA1";    /**     * 生成密钥,每个用户独享一份密钥     *     * @return     */    public static String getSecretKey() {        SecureRandom random = new SecureRandom();        byte[] bytes = new byte[20];        random.nextBytes(bytes);        Base32 base32 = new Base32();        String secretKey = base32.encodeToString(bytes);        // make the secret key more human-readable by lower-casing and        // inserting spaces between each group of 4 characters        return secretKey.toUpperCase();    }    /**     * 生成二维码内容     *     * @param secretKey 密钥     * @param account   账户名     * @param issuer    网站地址(可不写)     * @return     */    public static String getQrCodeText(String secretKey, String account, String issuer) {        String normalizedBase32Key = secretKey.replace(" ", "").toUpperCase();        try {            return "otpauth://totp/"                    + URLEncoder.encode((!StringUtils.isEmpty(issuer) ? (issuer + ":") : "") + account, "UTF-8").replace("+", "%20")                    + "?secret=" + URLEncoder.encode(normalizedBase32Key, "UTF-8").replace("+", "%20")                    + (!StringUtils.isEmpty(issuer) ? ("&issuer=" + URLEncoder.encode(issuer, "UTF-8").replace("+", "%20")) : "");        } catch (UnsupportedEncodingException e) {            throw new IllegalStateException(e);        }    }    /**     * 获取验证码     *     * @param secretKey     * @return     */    public static String getCode(String secretKey) {        String normalizedBase32Key = secretKey.replace(" ", "").toUpperCase();        Base32 base32 = new Base32();        byte[] bytes = base32.decode(normalizedBase32Key);        String hexKey = Hex.encodeHexString(bytes);        long time = (System.currentTimeMillis() / 1000) / 30;        String hexTime = Long.toHexString(time);        return TOTP.generateTOTP(hexKey, hexTime, "6", CRYPTO);    }    /**     * 检验 code 是否正确     *     * @param secret 密钥     * @param code   code     * @param time   时间戳     * @return     */    public static boolean checkCode(String secret, long code, long time) {        Base32 codec = new Base32();        byte[] decodedKey = codec.decode(secret);        // convert unix msec time into a 30 second "window"        // this is per the TOTP spec (see the RFC for details)        long t = (time / 1000L) / 30L;        // Window is used to check codes generated in the near past.        // You can use this value to tune how far you're willing to go.        long hash;        for (int i = -WINDOW_SIZE; i  0; value >>>= 8) {            data = (byte) value;        }        SecretKeySpec signKey = new SecretKeySpec(key, CRYPTO);        Mac mac = Mac.getInstance(CRYPTO);        mac.init(signKey);        byte[] hash = mac.doFinal(data);        int offset = hash[20 - 1] & 0xF;        // We're using a long because Java hasn't got unsigned int.        long truncatedHash = 0;        for (int i = 0; i < 4; ++i) {            truncatedHash




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4