实现一个Google身份验证代替短信验证

最近才知道公司还在做国外的业务,要实现一个登陆辅助验证系统。咱们国内是用手机短信做验证,当然 这个google身份验证只是一个辅助验证登陆方式。看一下演示

看到了嘛。 手机下载一个谷歌身份验证器就可以 。

谷歌身份验证器,我本身是一个基于时间做加密计算然后得出相同结果 本身很简单。

下边在网上查的 可以做一下了解:谷歌身份验证就是基于TOTP算法

TOTP算法,全称为"Time-based One-time Password algorithm",中文译为基于时间的一次性密码算法。它是一种从共享密钥和当前时间计算一次性密码的算法,已被采纳为Internet工程任务组标准RFC 6238。TOTP是开放身份验证计划(OATH)的基石,并被用于许多双因素身份验证系统。详细的可以百度一下搜索原理,我们这里只是介绍一下使用。

下边是代码 :

复制代码
import org.apache.commons.codec.binary.Base32;
import org.apache.commons.codec.binary.Hex;
import org.springframework.util.StringUtils;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;

public class GoogleAuthenticator {
    /**
     * 时间前后偏移量 目的解决30秒内有计算有误差不一致的发生
 
     */
    private static int WINDOW_SIZE = 0;

    /**
     * 加密方式,HmacSHA1、HmacSHA256、HmacSHA512
     */
    private static final String CRYPTO = "HmacSHA1";

 


    /**
     * 生成二维码内容
     *
     * @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);
        }
    }

    /**
     * 检验 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 <= WINDOW_SIZE; ++i) {
            try {
                hash = verifyCode(decodedKey, t + i);
            } catch (Exception e) {

                return false;

            }
            if (hash == code) {
                return true;
            }
        }
        return false;
    }

    /**
     * 根据时间偏移量计算
     *
     * @param key
     * @param t
     * @return
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeyException
     */
    private static long verifyCode(byte[] key, long t) throws NoSuchAlgorithmException, InvalidKeyException {
        byte[] data = new byte[8];
        long value = t;
        for (int i = 8; i-- > 0; value >>>= 8) {
            data[i] = (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 <<= 8;
            // We are dealing with signed bytes:
            // we just keep the first byte.
            truncatedHash |= (hash[offset + i] & 0xFF);
        }
        truncatedHash &= 0x7FFFFFFF;
        truncatedHash %= 1000000;
        return truncatedHash;
    }


    public static String getkeyBase32() {
        // 生成一个随机的密钥字节数组
        SecureRandom random = new SecureRandom();
        byte[] keyBytes = new byte[20]; // 一般长度为16、20或32字节
        random.nextBytes(keyBytes);

        // 将密钥转换成Base32格式以便用户显示或扫描二维码
        Base32 base32 = new Base32();
        String secretKeyBase32 = base32.encodeToString(keyBytes);
        return secretKeyBase32;
    }



    public static void main(String[] args) {
//        String secretKeyBase32 = getkeyBase32();
        String secretKeyBase32 = "YR3TEMNWNWOVMFPFK3BB2SLM2P3IV6MF";
        System.out.println("加密信息》》》" + secretKeyBase32);
        System.out.println("拿到这个字符串 二维码工具:去生成二维码图片就可以了" + getQrCodeText(secretKeyBase32, "jxd", "hdjz"));
        System.out.println(checkCode(secretKeyBase32, Long.parseLong("034944"), System.currentTimeMillis()));
    }
}

我这个方法,都是基于现成的,不需要额外引入j2totp 等类库 很方便可以拿去用

相关推荐
qq_441996054 分钟前
Mybatis官方生成器使用示例
java·mybatis
巨大八爪鱼10 分钟前
XP系统下用mod_jk 1.2.40整合apache2.2.16和tomcat 6.0.29,让apache可以同时访问php和jsp页面
java·tomcat·apache·mod_jk
码上一元2 小时前
SpringBoot自动装配原理解析
java·spring boot·后端
计算机-秋大田2 小时前
基于微信小程序的养老院管理系统的设计与实现,LW+源码+讲解
java·spring boot·微信小程序·小程序·vue
魔道不误砍柴功4 小时前
简单叙述 Spring Boot 启动过程
java·数据库·spring boot
失落的香蕉4 小时前
C语言串讲-2之指针和结构体
java·c语言·开发语言
枫叶_v4 小时前
【SpringBoot】22 Txt、Csv文件的读取和写入
java·spring boot·后端
wclass-zhengge4 小时前
SpringCloud篇(配置中心 - Nacos)
java·spring·spring cloud
路在脚下@4 小时前
Springboot 的Servlet Web 应用、响应式 Web 应用(Reactive)以及非 Web 应用(None)的特点和适用场景
java·spring boot·servlet
黑马师兄4 小时前
SpringBoot
java·spring