欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

内网实现Google Authenticator二步验证

程序员文章站 2022-04-10 16:29:45
0.概述相关背景参考https://blog.csdn.net/lizhengjava/article/details/76947962,本Demo将调用google api生成二维码改为了com.google.zxing包本地生成。1. pom.xml dependencies部分 commons-codec

0.概述

相关背景参考https://blog.csdn.net/lizhengjava/article/details/76947962,本Demo将调用google api生成二维码改为了com.google.zxing包本地生成。

1. pom.xml dependencies部分

  <dependencies>
    <dependency>
      <groupId>commons-codec</groupId>
      <artifactId>commons-codec</artifactId>
      <version>1.14</version>
    </dependency>
    <dependency>
      <groupId>com.google.zxing</groupId>
      <artifactId>core</artifactId>
      <version>3.4.1</version>
    </dependency>

    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
  </dependencies>

2.GoogleAuthenticatorUtils.java

import org.apache.commons.codec.binary.Base32;
import org.apache.commons.codec.binary.Base64;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;


/**
 * @author alexhu
 *     主要功能:生成密钥、生成二维码内容、校验身份
 *     依赖:
 *     <dependency>
 *       <groupId>commons-codec</groupId>
 *       <artifactId>commons-codec</artifactId>
 *       <version>1.14</version>
 *     </dependency>
 */
public class GoogleAuthenticatorUtils {

    public static final int SECRET_SIZE = 10;

    public static final String SEED = "g8GjEvTbW5oVSV7avLBdwIHqGlUYNzKFI7izOF8GwLDVKs2m0QN7vxRs2im5MDaNCWGmcD2rvcZx";

    public static final String RANDOM_NUMBER_ALGORITHM = "SHA1PRNG";

    /**
     * default 3 - max 17 (from google docs)最多可偏移的时间
     */
    int window_size = 3;

    public void setWindowSize(int s) {
        if (s >= 1 && s <= 17) {
            window_size = s;
        }
    }

    /**
     * 验证身份验证码是否正确
     *
     * @param codes       输入的身份验证码
     * @param savedSecret 密钥
     * @return
     */
    public static Boolean authcode(String codes, String savedSecret) {
        long code = 0;
        try {
            code = Long.parseLong(codes);
        } catch (Exception e) {
            e.printStackTrace();
        }
        long t = System.currentTimeMillis();
        GoogleAuthenticatorUtils ga = new GoogleAuthenticatorUtils();

        // should give 5 * 30 seconds of grace...
        ga.setWindowSize(ga.window_size);

        return ga.check_code(savedSecret, code, t);
    }

    /**
     * 获取密钥
     *
     * @param user 用户
     * @param host 域
     * @return 密钥
     */
    public static String genSecret(String user, String host) {
        String secret = GoogleAuthenticatorUtils.generateSecretKey();
        GoogleAuthenticatorUtils.getQRBarcodeURL(user, host, secret);
        return secret;
    }

    /**
     * 生成密钥
     *
     * @return
     */
    private static String generateSecretKey() {
        SecureRandom sr = null;
        try {
            sr = SecureRandom.getInstance(RANDOM_NUMBER_ALGORITHM);
            sr.setSeed(Base64.decodeBase64(SEED));
            byte[] buffer = sr.generateSeed(SECRET_SIZE);
            Base32 codec = new Base32();
            byte[] bEncodedKey = codec.encode(buffer);
            String encodedKey = new String(bEncodedKey);
            return encodedKey;
        } catch (NoSuchAlgorithmException e) {
            // should never occur... configuration error
        }
        return null;
    }

    /**
     * 获取二维码内容URL
     *
     * @param user   用户
     * @param host   域
     * @param secret 密钥
     * @return 二维码URL
     */
    public static String getQRBarcodeURL(String user, String host, String secret) {
        String format = "otpauth://totp/%s@%s?secret=%s";
        return String.format(format, user, host, secret);
    }

    /**
     * 校验code是否正确
     *
     * @param secret 密钥
     * @param code   动态code
     * @param timeMsec 时间
     * @return
     */
    private boolean check_code(String secret, long code, long timeMsec) {
        Base32 codec = new Base32();
        byte[] decodedKey = codec.decode(secret);
        long t = (timeMsec / 1000L) / 30L;
        for (int i = -window_size; i <= window_size; ++i) {
            long hash;
            try {
                hash = verify_code(decodedKey, t + i);
            } catch (Exception e) {
                e.printStackTrace();
                throw new RuntimeException(e.getMessage());
            }
            if (hash == code) {
                return true;
            }
        }
        return false;
    }

    /**
     * 时间校验密钥与code是否匹配
     *
     * @param key 解密后的密钥
     * @param t 时间
     * @return
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeyException
     */
    private static int verify_code(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, "HmacSHA1");
        Mac mac = Mac.getInstance("HmacSHA1");
        mac.init(signKey);
        byte[] hash = mac.doFinal(data);
        int offset = hash[20 - 1] & 0xF;
        long truncatedHash = 0;
        for (int i = 0; i < 4; ++i) {
            truncatedHash <<= 8;
            truncatedHash |= (hash[offset + i] & 0xFF);
        }
        truncatedHash &= 0x7FFFFFFF;
        truncatedHash %= 1000000;
        return (int) truncatedHash;
    }


}

3.GenerateQRCodeUtils.java

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import javax.imageio.ImageIO;

import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.common.BitMatrix;

/**
 * @author alexhu
 *
 * 主要功能:根据二维码内容生成二维码,并保存在指定位置
 *
 * 依赖:
 *     <dependency>
 *       <groupId>com.google.zxing</groupId>
 *       <artifactId>core</artifactId>
 *       <version>3.4.1</version>
 *     </dependency>
 */
public class GenerateQRCodeUtils {
    /**
     * 二维码颜色
     */
    private static final int BLACK = 0xFF000000;
    private static final int WHITE = 0xFFFFFFFF;

    /**
     * 图片的宽度
     */
    private static int WIDTH = 200;
    /**
     * 图片的高度
     */
    private static int HEIGHT = 200;
    /**
     * 图片的格式
     */
    private static String FORMAT = "png";


    /**
     * 生成二维码
     *
     * @param basePath 配置文件定义的生成二维码存放文件夹
     * @param content 二维码内容
     * @return 文件路径
     */
    public static String generateQRCodeImg(String basePath, String content){
        try {
            Map<EncodeHintType, String> encodeMap = new HashMap<EncodeHintType, String>();
            // 内容编码,生成二维码矩阵
            encodeMap.put(EncodeHintType.CHARACTER_SET, "utf-8");
            BitMatrix bitMatrix = new MultiFormatWriter().encode(content, BarcodeFormat.QR_CODE, WIDTH, HEIGHT, encodeMap);

            File file = new File(basePath);
            if (!file.exists() && !file.isDirectory()){
                file.mkdirs();
            }

            //文件名,默认为时间为名
            String filePath = basePath + System.currentTimeMillis() + "." + FORMAT;

            File outputFile = new File(filePath);
            if (!outputFile.exists()){
                // 生成二维码文件
                writeToFile(bitMatrix, FORMAT, outputFile);
            }
            return filePath;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "";
    }

    /**
     * 把二维码矩阵保存为文件
     *
     * @param matrix 二维码矩阵
     * @param format 文件类型,这里为png
     * @param file  文件句柄
     * @throws IOException
     */
    public static void writeToFile(BitMatrix matrix, String format, File file) throws IOException {
        BufferedImage image = toBufferedImage(matrix);
        if (!ImageIO.write(image, format, file)) {
            throw new IOException("Could not write an image of format " + format + " to " + file);
        }
    }

    /**
     * 生成二维码矩阵(内存)
     *
     * @param matrix 二维码矩阵
     * @return
     */
    public static BufferedImage toBufferedImage(BitMatrix matrix) {
        int width = matrix.getWidth();
        int height = matrix.getHeight();
        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        for (int x = 0; x < width; x++) {
            for (int y = 0; y < height; y++) {
                image.setRGB(x, y, matrix.get(x, y) ? BLACK : WHITE);
            }
        }
        return image;
    }
}

4.GoogleAuthenticatorTest.java

import org.junit.Test;

import static org.example.GenerateQRCodeUtils.generateQRCodeImg;
import static org.example.GoogleAuthenticatorUtils.*;

/**
 * Unit test for Google Authenticator.
 */
public class GoogleAuthenticatorTest {
    /**
     * Rigorous Test :-)
     */
    @Test
    public void genTest() {
        /*
         * 注意:先运行前两步,获取密钥和二维码url。 然后只运行第三步,填写需要验证的验证码,和第一步生成的密钥
         */
        String user = "testUser";
        String host = "test.com";
        // 第一步:获取密钥
        String secret = genSecret(user, host);
        System.out.println("secret:" + secret);
        // 第二步:根据密钥获取二维码图片url(可忽略)
        String url = getQRBarcodeURL(user, host, secret);
        System.out.println("url:" + url);
        // 第三步 生成二维码
        generateQRCodeImg("", url);
    }

    @Test
    public void verifyTest() {
        // 第四步:验证(第一个参数是需要验证的验证码,第二个参数是第一步生成的secret运行)
        boolean result = authcode("105938", "WUH2RO3Q4D53AF5Z");
        System.out.println("result:" + result);
    }
}

本文地址:https://blog.csdn.net/dgatiger/article/details/110196740