Google 身份验证器 Google Authenticator 是谷歌推出的基于时间的一次性密码 (Time-based One-time Password,简称 TOTP),只需要在手机上安装该 APP,就可以生成一个随着时间变化的一次性密码,用于帐户验证。
Google 身份验证器是一款基于时间与哈希的一次性密码算法的两步验证软件令牌,此软件用于 Google 的认证服务。此项服务所使用的算法已列于 RFC 6238 和 RFC 4226 中。

1、安装谷歌身份验证器

  • 苹果用户
在App Store搜索google authenticator
  • 安卓用户
http://s.downpp.com//apk9/googlesfyzq_5.10_2265.com.apk

2、引入 maven 依赖

<!-- 加密工具 -->
<dependency><groupId>top.lrshuai.encryption</groupId><artifactId>encryption-tools</artifactId><version>1.0.0</version>
</dependency>
<!-- 二维码依赖 -->
<dependency><groupId>org.iherus</groupId><artifactId>qrext4j</artifactId><version>1.3.1</version>
</dependency>
  • 谷歌身份验证器工具类
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 {/*** 时间前后偏移量* 用于防止客户端时间不精确导致生成的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 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 charactersreturn 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 <= WINDOW_SIZE; ++i) {try {hash = verifyCode(decodedKey, t + i);} catch (Exception e) {// Yes, this is bad form - but// the exceptions thrown would be rare and a static// configuration problem// e.printStackTrace();throw new RuntimeException(e.getMessage());}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 void main(String[] args) {for (int i = 0; i < 100; i++) {String secretKey = getSecretKey();System.out.println("secretKey:" + secretKey);String code = getCode(secretKey);System.out.println("code:" + code);boolean b = checkCode(secretKey, Long.parseLong(code), System.currentTimeMillis());System.out.println("isSuccess:" + b);}}
}
  • 验证码生成工具类
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.lang.reflect.UndeclaredThrowableException;
import java.math.BigInteger;
import java.security.GeneralSecurityException;/*** 验证码生成工具类*/
public class TOTP {// 0 1 2 3 4 5 6 7 8private static final int[] DIGITS_POWER = {1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000};/*** This method uses the JCE to provide the crypto algorithm. HMAC computes a* Hashed Message Authentication Code with the crypto hash algorithm as a* parameter.** @param crypto   : the crypto algorithm (HmacSHA1, HmacSHA256, HmacSHA512)* @param keyBytes : the bytes to use for the HMAC key* @param text     : the message or text to be authenticated*/private static byte[] hmac_sha(String crypto, byte[] keyBytes, byte[] text) {try {Mac hmac;hmac = Mac.getInstance(crypto);SecretKeySpec macKey = new SecretKeySpec(keyBytes, "RAW");hmac.init(macKey);return hmac.doFinal(text);} catch (GeneralSecurityException gse) {throw new UndeclaredThrowableException(gse);}}/*** This method converts a HEX string to Byte[]** @param hex : the HEX string* @return: a byte array*/private static byte[] hexStr2Bytes(String hex) {// Adding one byte to get the right conversion// Values starting with "0" can be convertedbyte[] bArray = new BigInteger("10" + hex, 16).toByteArray();// Copy all the REAL bytes, not the "first"byte[] ret = new byte[bArray.length - 1];System.arraycopy(bArray, 1, ret, 0, ret.length);return ret;}/*** This method generates a TOTP value for the given set of parameters.** @param key          : the shared secret, HEX encoded* @param time         : a value that reflects a time* @param returnDigits : number of digits to return* @param crypto       : the crypto function to use* @return: a numeric String in base 10 that includes*/public static String generateTOTP(String key, String time, String returnDigits, String crypto) {int codeDigits = Integer.decode(returnDigits);String result = null;// Using the counter// First 8 bytes are for the movingFactor// Compliant with base RFC 4226 (HOTP)while (time.length() < 16)time = "0" + time;// Get the HEX in a Byte[]byte[] msg = hexStr2Bytes(time);byte[] k = hexStr2Bytes(key);byte[] hash = hmac_sha(crypto, k, msg);// put selected bytes into result intint offset = hash[hash.length - 1] & 0xf;int binary = ((hash[offset] & 0x7f) << 24)| ((hash[offset + 1] & 0xff) << 16)| ((hash[offset + 2] & 0xff) << 8) | (hash[offset + 3] & 0xff);int otp = binary % DIGITS_POWER[codeDigits];result = Integer.toString(otp);while (result.length() < codeDigits) {result = "0" + result;}return result;}
}

因为谷歌身份验证器支持两种方式添加,扫码和输入密钥添加,所以两种方式我们都实现了

3、项目启动类

import com.asurplus.common.google.GoogleAuthenticator;
import com.asurplus.common.utils.QRCodeUtil;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;import javax.servlet.http.HttpServletResponse;/*** 项目启动类** @Author Asurplus*/
@RestController
@SpringBootApplication
public class AsurplusApplication {public static void main(String[] args) {SpringApplication.run(AsurplusApplication.class, args);}/*** 生成 Google 密钥,两种方式任选一种*/@GetMapping("getSecret")public String getSecret() {return GoogleAuthenticator.getSecretKey();}/*** 生成二维码,APP直接扫描绑定,两种方式任选一种*/@GetMapping("getQrcode")public void getQrcode(String name, HttpServletResponse response) throws Exception {// 生成二维码内容String qrCodeText = GoogleAuthenticator.getQrCodeText(GoogleAuthenticator.getSecretKey(), name, "");// 生成二维码输出new SimpleQrcodeGenerator().generate(qrCodeText).toStream(response.getOutputStream());}/*** 获取code*/@GetMapping("getCode")public String getCode(String secretKey) {return GoogleAuthenticator.getCode(secretKey);}/*** 验证 code 是否正确*/@GetMapping("checkCode")public String checkCode(String secret, String code) {boolean b = GoogleAuthenticator.checkCode(secret, Long.parseLong(code), System.currentTimeMillis());if (b) {return "success";}return "error";}
}

4、测试

http://localhost:8080/getSecret

5、设计优化

我们在实际的开发过程中,每一个用户需要对应唯一的密钥,在 user 表中应该有一个 secret 字段,保存该用户的密钥,用户需要绑定该密钥,从而实现二步验证

springboot整合谷歌身份验证相关推荐

  1. 【SpringBoot】61、SpringBoot中使用谷歌身份验证器(Google Authenticator)实现二步身份验证

    Google 身份验证器 Google Authenticator 是谷歌推出的基于时间的一次性密码 (Time-based One-time Password,简称 TOTP),只需要在手机上安装该 ...

  2. 使用谷歌身份验证器增强SSH安全

    一般大家都是使用账号和密码远程SSH登录管理服务器.但SSH账号和密码很容易泄露,或者经常遭遇暴力破解.咨询过前同事赛赛,他们目前使用了谷歌身份验证器.查看了谷歌身份验证器的github和其它网上文档 ...

  3. 如何为SSH登录建立双因子验证机制(谷歌身份验证器)?

    前言 默认情况下,SSH已经在远程机器之间使用安全的数据通信;但是如果你想为自己的SSH连接添加另外某种安全层,可以添加谷歌身份验证器(Google Authenticator)双因子验证模块,该模块 ...

  4. 使用谷歌身份验证器(Google Authenticator)保护你的后台

    为何要使用谷歌身份验证器 普通的网站只使用账号.密码.图形验证码进行后台登录.根据我(作为站长)多年的经验来看,这种方式安全性很低,尤其是使用 http 协议,明文的帐号和密码相当于在网络上裸奔.如果 ...

  5. 谷歌身份验证器 手表_6条使您的三星手表更加Google-y的提示

    谷歌身份验证器 手表 Samsung 三星 Samsung Galaxy watches are, arguably, the best smartwatches for Android phones ...

  6. 使用C++实现谷歌身份验证器(Google Authenticator)

    使用C++实现谷歌身份验证器(Google Authenticator) 本机环境: windows10 x64位运行环境 1.进入网站:http://slproweb.com/products/Wi ...

  7. 二次验证码小程序与谷歌身份验证器不同点是?

    名称1[二次验证码]小程序 名称2 谷歌身份验证器(Google Authenticator) 粗略对比两个产品异同 [二次验证码]小程序 搜索:微信搜索.微信目前65个小程序入口,倒是容易找到它 使 ...

  8. Google Authenticator windows client 谷歌身份验证器 windows 电脑端

    谷歌身份验证器现在有安卓客户端和ios客户端,本人开发了一个windows客户端,基于 .NETFramework v4.7 开发,已在 github 上开源,可以在 github 上直接下载. gi ...

  9. 动态令牌 (谷歌身份验证器)的实现

    动态令牌-(OTP,HOTP,TOTP)-基本原理:  https://www.cnblogs.com/navysummer/p/11943319.html 实现原理: https://blog.cs ...

  10. 谷歌身份验证器代码实现

    手机下载谷歌身份验证器,无需联网也可以用. 工具类 public class GoogleAuthenticator{// 生成的key长度( Generate secret key length)p ...

最新文章

  1. 编程之美 3.10 分层遍历二叉树
  2. 设计模式(四)OkHttp的责任链模式
  3. ubuntu mysql 中午_Ubuntu更改MySQL字符集UTF-8,正确显示中文
  4. 【给中高级开发者】构建高性能ASP.NET应用的几点建议
  5. 【华为云技术分享】技术干货丨通过wrap malloc定位C/C++的内存泄漏问题
  6. STM32学习第二课:STM32c语言基础2
  7. 在Macbook M1上安装Parallels Desktop 17虚拟机和Originlab 2021
  8. 详解 YUV 格式(I420/YUV420/NV12/NV12/YUV422)
  9. 广播系统可以跨服务器控制吗,数字广播系统技术参数和要求.doc
  10. mac下bin格式文件解压
  11. 手机端android app崩溃的常见类型
  12. C语言的字符数组(字符串)的定义与初始化
  13. 需求分析挑战之旅(疯狂的订餐系统)(2)——需求分析的大道理
  14. 2023最新SSM计算机毕业设计选题大全(附源码+LW)之java康健医药公司进销存管理22jao
  15. Python求助攻,哥哥姐姐们帮忙一下,网上下载的这个程序需要什么数据参数读入吗?帮忙写个主程序调用一下SPAC出结果,必有重谢
  16. 2022年69 个经典 Spring 面试题和答案,值得收藏
  17. java castor_Castor以及Java和XML数据绑定
  18. HttpURLConnection接口使用案例-图片下载
  19. 每日自动发邮件(Python +QQ邮箱 + Windows 10定时任务)
  20. 为什么划分内核态、用户态?

热门文章

  1. 最全面计算机英语核心单词分享
  2. 解决MySQL数据库登录报错原因以及修改MySQL数据库登录密码;
  3. 计算机232 数据传输,RS232串口通信的传输格式和接收过程
  4. 警惕分布式常见的这十大坑
  5. ‘Staring Tomcat v8.0 Server at localhost ’ has encountered a problem 问题解决
  6. 如何批量打印 带图片名字的图片?Word 宏命令
  7. 如何给华硕笔记本在光驱位加装另一块linux系统固态硬盘?
  8. 公网与私网地址转换——NAT技术的使用小技巧,超简单!!!
  9. AOP切面之实现计算器加减乘除--基于注解的方式
  10. Android 十大布局