快速接入Google两步认证Google Authenticator

article/2025/10/6 3:22:11

(一)介绍

 

 既然来看该文章就应该知道Google的两步认证是干什么的,这边再提供一次app的下载链接

(apkpure搜索谷歌身份验证器)

 验证原理讲解:

  1. 在数据库中查找该登陆用户之前绑定的32位随机码(该码一般会存入数据库)
  2. 调用API传入32位随机码,生成正确的6位验证码(每隔1min会变化)
  3. 根据用户输入的6位验证码和正确的6位验证码做匹配,相同则登陆成功,不同则验证码时间失效或错误

用户绑定讲解:

  1. 调用API生成32位随机码,准备绑定给用户
  2. 调用API生成二维码QR字符串,需要传入用户信息(比如邮箱,id,昵称等),标题,以及生成的32位随机码
  3. 调用API将二维码QR字符串转化为图片后以Base64的方式展现到前端页面上
  4. 用户使用app扫码添加后,在前端页面点击确认绑定,输入本次看到的6位验证码
  5. 后端根据本次获得的32位随机码,用户信息(用来确定数据库中用户记录),以及输入6位验证码,通过API传入32位随机码获得正确的6位验证码,当其与输入的验证码相同时,则绑定成功,把32位随机码持久化到数据库中对应用户记录上

(二)准备工作

 导入一下Maven依赖

         <!--google两步认证相关--><dependency><groupId>de.taimos</groupId><artifactId>totp</artifactId><version>1.0</version></dependency><dependency><groupId>commons-codec</groupId><artifactId>commons-codec</artifactId><version>1.10</version></dependency><dependency><groupId>com.google.zxing</groupId><artifactId>javase</artifactId><version>3.2.1</version></dependency>

 导入工具类GoogleAuthenticationTool

import com.google.zxing.BarcodeFormat;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.WriterException;
import com.google.zxing.client.j2se.MatrixToImageWriter;
import com.google.zxing.common.BitMatrix;
import de.taimos.totp.TOTP;
import org.apache.commons.codec.binary.Base32;
import org.apache.commons.codec.binary.Hex;
import sun.misc.BASE64Encoder;import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.*;
import java.net.URLEncoder;
import java.security.SecureRandom;/*** @Author bilibili-nanoda* @Date 2021/8/13 10:33* @Version 1.0*/
public class GoogleAuthenticationTool {public static String generateSecretKey() {SecureRandom random = new SecureRandom();byte[] bytes = new byte[20];random.nextBytes(bytes);Base32 base32 = new Base32();return base32.encodeToString(bytes);}/*** 根据32位随机码获得正确的6位数字** @param secretKey* @return*/public static String getTOTPCode(String secretKey) {Base32 base32 = new Base32();byte[] bytes = base32.decode(secretKey);String hexKey = Hex.encodeHexString(bytes);return TOTP.getOTP(hexKey);}/*** 生成绑定二维码(字符串)** @param account   账户信息(展示在Google Authenticator App中的)* @param secretKey 密钥* @param title     标题 (展示在Google Authenticator App中的)* @return*/public static String spawnScanQRString(String account, String secretKey, String title) {try {return "otpauth://totp/"+ URLEncoder.encode(title + ":" + account, "UTF-8").replace("+", "%20")+ "?secret=" + URLEncoder.encode(secretKey, "UTF-8").replace("+", "%20")+ "&issuer=" + URLEncoder.encode(title, "UTF-8").replace("+", "%20");} catch (UnsupportedEncodingException e) {throw new IllegalStateException(e);}}/*** 生成二维码(文件)【返回图片的base64,若指定输出路径则同步输出到文件中】** @param barCodeData 二维码字符串信息* @param outPath     输出地址* @param height* @param width* @throws WriterException* @throws IOException*/public static String createQRCode(String barCodeData, String outPath, int height, int width)throws WriterException, IOException {BitMatrix matrix = new MultiFormatWriter().encode(barCodeData, BarcodeFormat.QR_CODE,width, height);BufferedImage bufferedImage = MatrixToImageWriter.toBufferedImage(matrix);ByteArrayOutputStream bof = new ByteArrayOutputStream();ImageIO.write(bufferedImage, "png", bof);String base64 = imageToBase64(bof.toByteArray());if(outPath!=null&&!outPath.equals("")) {try (FileOutputStream out = new FileOutputStream(outPath)) {MatrixToImageWriter.writeToStream(matrix, "png", out);}}return base64;}/*** 将图片文件转换成base64字符串,参数为该图片的路径** @param dataBytes* @return java.lang.String*/private static String imageToBase64(byte[] dataBytes) {// 对字节数组Base64编码BASE64Encoder encoder = new BASE64Encoder();if (dataBytes != null) {return "data:image/jpeg;base64," + encoder.encode(dataBytes);// 返回Base64编码过的字节数组字符串}return null;}}

(三)使用流程

Tips:其实看工具类就已经知道怎么使用了,但我这边还是贴出我的代码以供参考

  • 首次绑定逻辑判断

UserController的login中判断该登陆用户是否存在32位随机码

    //登陆逻辑@PostMapping("/login")public String login(WebLoginDTO webLoginDTO, HttpSession httpSession, Model model, HttpServletRequest httpServletRequest,RedirectAttributes redirectAttributes) {System.out.println("尝试登录:" + webLoginDTO.getEmail() + ":" + webLoginDTO.getEmail());Subject subject = SecurityUtils.getSubject();UsernamePasswordToken token = new UsernamePasswordToken(webLoginDTO.getEmail(), webLoginDTO.getPassword());try {subject.login(token);} catch (IncorrectCredentialsException e) {e.printStackTrace();model.addAttribute("msg", "密码错误");return "error/systemError";} catch (AuthenticationException e) {e.printStackTrace();model.addAttribute("msg", "账户不存在");return "error/systemError";}//说明登录成功ActiveUser activeUser = (ActiveUser) subject.getPrincipal();if (activeUser.isLokced()) {model.addAttribute("msg", "账户被封锁");return "error/systemError";}//没有32位随机码的情况if(activeUser.getTwoFactorCode()==null||activeUser.getTwoFactorCode().equals(""))            {//前往code绑定页面redirectAttributes.addAttribute("userId",activeUser.getUser_id());//todo 处理设计该页面绑定谷歌认证码(QR二维码)return "redirect:/user/bindingGoogleTwoFactorValidate";}

  不存在则定向到 绑定页面(要携带用户信息,如id)

    /*** 前往谷歌两步验证绑定页面* @param userId* @return*/@GetMapping("/bindingGoogleTwoFactorValidate")public String toBindingGoogleTwoFactorValidate(@RequestParam("userId")int userId,Model model){String randomSecretKey = GoogleAuthenticationTool.generateSecretKey();User user = userService.getUserByUserId(userId);//此步设置的参数就是App扫码后展示出来的参数String qrCodeString = GoogleAuthenticationTool.spawnScanQRString(user.getEmail(),randomSecretKey,"pilipili2333");String qrCodeImageBase64 = null;try {qrCodeImageBase64 = GoogleAuthenticationTool.createQRCode(qrCodeString,null,512,512);} catch (WriterException | IOException e) {e.printStackTrace();}model.addAttribute("randomSecretKey",randomSecretKey);model.addAttribute("qrCodeImageBase64",qrCodeImageBase64);return "bindingGoogleTwoFactorValidate";}

前端页面发起ajax执行绑定,且输入本次的6位验证码做校验

function confirmBinding() {var googleRegex =/\d{6}/;var inputGoogleCode = window.prompt("请输入6位google验证码");if(googleRegex.test(inputGoogleCode)){$.ajax({url:"[[@{/user/bindingGoogleTwoFactorValidate}]]",type:"post",data:{"userId":"[[${param.userId}]]","randomSecretKey":"[[${randomSecretKey}]]","inputGoogleCode":inputGoogleCode},dataType:"json",success:function (data) {if(data.state==='success'){window.alert("绑定成功");}else if(data.state==='fail'){window.alert("操作失败:"+data.msg);}}});}else {window.alert("请正确输入6位google验证码")}}

 后端对执行绑定再做一次6位验证码是否正确的校验

     /*** 执行谷歌两步验证绑定* @return*/@PostMapping("/bindingGoogleTwoFactorValidate")@ResponseBodypublic String bindingGoogleTwoFactorValidate(@RequestParam("userId")int userId,@RequestParam("randomSecretKey")String randomSecretKey,@RequestParam("inputGoogleCode")String inputGoogleCode){JSONObject respJsonObj =new JSONObject();User user = userService.getUserByUserId(userId);if(user.getTwoFactorCode()!=null&&!user.getTwoFactorCode().equals("")){respJsonObj.put("state","fail");respJsonObj.put("msg","该用户已经绑定了,不可重复绑定,若不慎删除令牌,请联系管理员重置");return respJsonObj.toString();}String rightCode =GoogleAuthenticationTool.getTOTPCode(randomSecretKey);if(!rightCode.equals(inputGoogleCode)){respJsonObj.put("state","fail");respJsonObj.put("msg","验证码失效或错误,请重试");return respJsonObj.toString();}user.setTwoFactorCode(randomSecretKey);int res = userService.updateUserByUser(user);if(res>0){respJsonObj.put("state","success");}else {respJsonObj.put("state","fail");respJsonObj.put("msg","数据库操作失败");}return respJsonObj.toString();}
  • 登陆时校验6位验证码的逻辑

UserController的login方法中处理

@PostMapping("/login")public String login(WebLoginDTO webLoginDTO, HttpSession httpSession, Model model, HttpServletRequest httpServletRequest,RedirectAttributes redirectAttributes) {System.out.println("尝试登录:" + webLoginDTO.getEmail() + ":" + webLoginDTO.getEmail());/*shiro认证相关代码。。。*///注意:1min内有效String rightGoogleCode = GoogleAuthenticationTool.getTOTPCode(activeUser.getTwoFactorCode());if(!webLoginDTO.getGoogleCode().equals(rightGoogleCode)){model.addAttribute("msg","谷歌验证码不正确或已超时");return "error/systemError";}/*后续逻辑*/}

需要注意:

与短信验证,邮件验证不同,验证码的生成与刷新是由我们自己控制的,而对于这种谷歌两步认证,他是1min刷新一次,对于同时刻,我们事先约定好了一套加密解密规则。因此在进行输入的6位验证码验证时,应当在输入之后再去获得此刻正确的6位CODE,而不是事先生成好正确的Code,再等用户输入。后者可能会因为延时问题(用户动作很摸,app上的已经更新了,但系统保留的还是上一次),导致经常性的验证码失效

更多教程,可见我的官方网站:最咔酷线上教程:www.zuikakuedu.cn


http://chatgpt.dhexx.cn/article/DgVGQ5Vr.shtml

相关文章

两步验证 非双重认证

Two-factor authentication must be turned on for your Apple ID. After you turn it on, signing into your developer account will require both your password and access to your trusted devices or trusted phone number. 今天Xcode 真机调试, 突然不正常了. 本着热爱…

google账号异步新设备登录需要两次两步验证问题

备注&#xff1a;华为手机&#xff0c;在谷歌三件套插件已经下载的情况下&#xff0c;还是无法收到数字点击验证 先说下现象&#xff0c;比如你的谷歌账号从别人那购买的&#xff0c;然后辅助电话与邮箱已经全部替换成了自己的信息&#xff0c;一般为了安全我们会开启两步验证&…

谷歌两步验证器身份怎么开Authenticator安卓app下载安装方法教程

国内互联网公司一般采取手机收验证码的方式对账号进行身份验证&#xff0c;增强账号的安全性。但是在国外通常采取使用谷歌两步身份验证器 (Google Authenticator&#xff09;&#xff0c;谷歌谷歌两步身份验证器的方便之处主要体现在&#xff1a; 1.在无网络的情况下也可以使用…

如何开发两步验证功能

什么是两步验证 两步验证&#xff0c;是指用户登录账户的时候&#xff0c;除了要输入用户名和密码&#xff0c;还要求用户输入一个动态密码&#xff0c;为帐户添加了一层额外保护。这个动态密码要么是专门的硬件&#xff0c;要么由用户手机APP提供。即使入侵者窃取了用户密码&a…

兩步验证的原理

被盗号 “您的账号密码有误,请重新输入” 小卢盯着电脑屏幕看了5分钟,心里纳闷,昨天还能登录,怎么今天就密码错误了,难不成我被盗号了?想到这里,小卢赶紧给自己的程序员好友小王打电话。 小卢:“小王,我在XX网站的账号被盗了!” 小王:“确定被盗了?赶紧把密码找…

(01)Webrtc::Fec与Nack的二三事

写在前面&#xff1a;要理解Fec与Nack逻辑&#xff0c;我喜欢先从接受端看&#xff0c; 理解了Fec与Nack是如何被使用的&#xff0c;才能更好的明白不同的机制应该怎么用&#xff0c;在什么场合用。 更新丢包逻辑 void PacketBuffer::UpdateMissingPackets(uint16_t seq_num)…

Channel closed; cannot ack/nack

再一次用rabbmitmq的时候遇到了 Channel closed&#xff1b; cannot ack/nack的异常信息&#xff0c;这个可能是因为rabbmitmq默认的模式是自动ack&#xff0c;我没有配置手动ack 然后在代码里又basicack了。 MessageProperties properties message.getMessageProperties();l…

简述WebRTC中的丢包重传Nack的实现

一 简述 接收端发现序列号不连续&#xff0c;发送RTCP FB Nack包&#xff0c;发送端从历史队列中查找该包&#xff0c;再发送RTP包&#xff0c;但WebRTC用的RTX重发该包&#xff0c;ssrc和原视频流不同&#xff0c;pt也不同。 artpmap:96 H264/90000 artcp-fb:96 goog-remb a…

WEBRTC浅析(五)视频Nack包的发送判断逻辑以及数据流

这篇文章是对webrtc 中Nack包发送机制的梳理&#xff0c;主要包括三个部分&#xff1a; 第一部分&#xff0c;介绍RTCP包中&#xff0c;Nack包的规范。 第二部分&#xff0c;介绍在WEBRTC中&#xff0c;Nack发送机制的数据流程图。 第三部分&#xff0c;介绍在WEBRTC中&#xf…

RabbitMQ的ack和nack机制

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、ACK机制二、主动ACK三、手动ACK四、Nack机制五、MQ unack的影响总结 前言 本文主要讨论RabbitMQ消费者的ack和nack机制&#xff0c;并且关注ack和nack使用…

RTCP 协议的 NACK 报文

接收方定时把所有未收到的包序号通过反馈报文通知到发送方进行重传。 相对 ARQ带来的改进&#xff1a;减少的反馈包的频率和带宽占用&#xff0c;同时也能比较及时地通知发送方进行丢包重传。 NACK 报文的定义在 [rfc4585] 文档中定义。 RTCP 的反馈报文包头定义如下&#x…

webrtc nack实现原理

1.nack 简介 webrtc 中nack是最基本的QOS策略&#xff0c;与ack机制不同的地方是nack是接收端检测到丢包时&#xff0c;告知发送端具体丢包的序号&#xff0c;接收端收到nack后从缓存中找到对应的包并发送出去。 2. nack实现 nack rtcp报文格式如上图所示&#xff0c;pt205。P…

webrtc QOS方法一(NACK实现)

一&#xff1a;概述 NACK则在接收端检测到数据丢包后&#xff0c;发送NACK报文到发送端&#xff1b;发送端根据NACK报文中的序列号&#xff0c;在发送缓冲区找到对应的数据包&#xff0c;重新发送到接收端。NACK需要发送端发送缓冲区的支持&#xff0c;RFC5104[2]定义NACK数据包…

认识网络通信中的 ACK、NACK 和 REX

ACK、NACK、 REX在面试或者网络通信的时候&#xff0c;我们可能经常听到和遇到。今天就来详细介绍一下ACK、NACK、 REX。 认识ACK、NACK、 REX ACK&#xff1a;Acknowledgement&#xff0c;它是一种正向反馈&#xff0c;接收方收到数据后回复消息告知发送方。NACK&#xff1a…

WebRTC之NACK、RTX 在什么时机判断丢包发送NACK请求和RTX丢包重传

WebRTC之NACK、RTX 在什么时机判断丢包发送NACK请求和RTX丢包重传 WebRTC之NACK、RTX 在什么时机判断丢包发送NACK请求和RTX丢包重传 WebRTC之NACK、RTX 在什么时机判断丢包发送NACK请求和RTX丢包重传前言一、NACK与RTX的作用1、NACK/RTX的工作机制的流程图2、NACK/RTX涉及到的…

WebRTC 的音频弱网对抗之 NACK

本文梳理 WebRTC 的音频弱网对抗中的 NACK 机制的实现。音频的 NACK 机制在 WebRTC 中默认是关闭的&#xff0c;本文会介绍开启 NACK 机制的方法。 在网络数据传输中&#xff0c;NACK (NAK&#xff0c;negative acknowledgment&#xff0c;not acknowledged) 是数据接收端主动…

流媒体弱网优化之路(NACK)——纯NACK方案的优化探索

流媒体弱网优化之路(NACK)——纯NACK方案的优化探索 —— 我正在的github给大家开发一个用于做实验的项目 —— github.com/qw225967/Bifrost目标&#xff1a;可以让大家熟悉各类Qos能力、带宽估计能力&#xff0c;提供每个环节关键参数调节接口并实现一个json全配置&#xff…

WebRTC源码分析 nack详解

1、Nack过程 1.1 nack是什么 丢包重传(NACK)是抵抗网络错误的重要手段。NACK在接收端检测到数据丢包后&#xff0c;发送NACK报文到发送端&#xff1b;发送端根据NACK报文中的序列号&#xff0c;在发送缓冲区找到对应的数据包&#xff0c;重新发送到接收端。NACK需要发送端&am…

谈谈网络通信中的 ACK、NACK 和 REX

目录 名词解释 问题 1&#xff1a; 接收方如何判断数据包是否丢失&#xff1f; 问题 2&#xff1a;发送方如何确认数据包已经丢失&#xff1f; 问题 3&#xff1a;重传超时的计算规则&#xff1f; 问题 4&#xff1a;发送方的数据包要缓存多久&#xff1f; 问题 5&#x…

费马定理_高数_1元微积分

定理 设 f(x) 在 x0 点处满足&#xff1a;1、可导 2、取得极值&#xff0c;则有 f ’ (x0)0 证明 不妨假设 f(x) 在点 x0 处取得极大值&#xff0c;则存在 x0 的邻域 U( x0 )&#xff0c;对任意的 x属于U( x0 )&#xff0c;都有 根据导数定义与极限的保号性有 又 f(x) 在点…