王海鱼 发表于 2024-5-18 07:49:07

SpringBoot项目添加2FA双因素身份认证

什么是 2FA(双因素身份验证)?

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

pom.xml中增加依赖

<dependency>
    <groupId>commons-codec</groupId>
    <artifactId>commons-codec</artifactId>
    <version>1.15</version>
</dependency>

<dependency>
    <groupId>org.iherus</groupId>
    <artifactId>qrext4j</artifactId>
    <version>1.3.1</version>
</dependency>用户表中增加secretKey列

为用户绑定secretKey字段,用以生成二维码及后期校验
https://img2024.cnblogs.com/blog/1249408/202404/1249408-20240424110009246-100171532.png
工具类

谷歌身份验证器工具类

/** * 谷歌身份验证器工具类 */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;      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; i0; 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 & 0xF;      // We're using a long because Java hasn't got unsigned int.      long truncatedHash = 0;      for (int i = 0; i < 4; ++i) {            truncatedHash
页: [1]
查看完整版本: SpringBoot项目添加2FA双因素身份认证