如何在Angular 5应用中启用Google Authentication?实现OTP设置及登录验证
在Angular 5中实现Google身份验证与OTP二次验证功能
我之前在Angular 5项目里落地过一模一样的需求,给你梳理下完整的实现步骤,都是经过实际验证的:
1. 安装必要依赖
Google Authenticator基于TOTP(基于时间的一次性密码)标准,我们需要两个库来处理密钥生成、验证码验证和二维码生成:
npm install speakeasy qrcode --save # Angular 5用TypeScript,需要安装类型定义 npm install @types/speakeasy @types/qrcode --save-dev
2. 封装OTP核心处理服务
创建一个AuthService来统一处理OTP的密钥生成和验证码验证逻辑,这样可以复用在多个组件中:
import { Injectable } from '@angular/core'; import * as speakeasy from 'speakeasy'; import * as qrcode from 'qrcode'; @Injectable() export class AuthService { // 生成绑定Google Authenticator的密钥和二维码 generateOTPSecret(userId: string): { secret: string, qrCodeUrl: Promise<string> } { const secretData = speakeasy.generateSecret({ length: 20, // 密钥长度,越长越安全 name: '你的应用名称', // 显示在Google Authenticator里的应用名 account: userId // 关联用户ID,方便用户识别 }); // 生成二维码的Data URL,用户直接扫描就能绑定 const qrCodePromise = qrcode.toDataURL(secretData.otpauth_url); return { secret: secretData.base32, // 要存储到后端的密钥(base32格式) qrCodeUrl: qrCodePromise }; } // 验证用户输入的OTP验证码是否正确 verifyOTP(secret: string, token: string): boolean { return speakeasy.totp.verify({ secret: secret, encoding: 'base32', token: token, window: 1 // 允许前后1个时间步长(约30秒)的误差,提升容错性 }); } }
3. 实现OTP设置页面
用户登录后,通过这个页面生成密钥、扫描二维码绑定,并验证验证码后保存密钥到后端:
import { Component, OnInit } from '@angular/core'; import { AuthService } from '../services/auth.service'; import { UserService } from '../services/user.service'; // 假设你有用户数据服务 import { Router } from '@angular/router'; @Component({ selector: 'app-otp-setup', template: ` <div class="otp-setup-container" *ngIf="qrCodeUrl"> <h3>绑定Google Authenticator</h3> <p>打开Google Authenticator应用,扫描下方二维码</p> <img [src]="qrCodeUrl" alt="OTP绑定二维码" class="qr-code"> <div class="token-input"> <label>输入6位验证码:</label> <input type="text" [(ngModel)]="token" maxlength="6" placeholder="123456"> <button (click)="verifyAndSaveSecret()" [disabled]="!token">验证并保存</button> </div> </div> `, styles: [` .qr-code { width: 200px; height: 200px; margin: 20px 0; } .token-input { margin-top: 20px; } input { padding: 8px; width: 120px; margin: 0 10px; } `] }) export class OtpSetupComponent implements OnInit { qrCodeUrl: string; secret: string; token: string; userId: string = '当前登录用户ID'; // 替换成实际的用户ID(从登录状态中获取) constructor( private authService: AuthService, private userService: UserService, private router: Router ) {} ngOnInit() { // 生成密钥和二维码 const otpData = this.authService.generateOTPSecret(this.userId); this.secret = otpData.secret; otpData.qrCodeUrl.then(url => this.qrCodeUrl = url); } verifyAndSaveSecret() { if (this.authService.verifyOTP(this.secret, this.token)) { // 验证通过,把密钥保存到后端用户表中 this.userService.updateUserOTPSecret(this.userId, this.secret).subscribe(() => { alert('OTP设置成功!下次登录需要输入验证码'); this.router.navigate(['/dashboard']); }); } else { alert('验证码不正确,请重试'); } } }
4. 改造登录流程,加入OTP验证环节
登录时先验证用户名密码,若用户已开启OTP,则跳转到验证码输入界面,否则直接登录成功:
import { Component } from '@angular/core'; import { AuthService } from '../services/auth.service'; import { UserService } from '../services/user.service'; import { Router } from '@angular/router'; @Component({ selector: 'app-login', template: ` <div *ngIf="!showOtpVerify" class="login-form"> <label>用户名:</label> <input type="text" [(ngModel)]="username" placeholder="请输入用户名"> <label>密码:</label> <input type="password" [(ngModel)]="password" placeholder="请输入密码"> <button (click)="submitLogin()">登录</button> </div> <div *ngIf="showOtpVerify" class="otp-verify-form"> <h3>请输入Google Authenticator验证码</h3> <input type="text" [(ngModel)]="otpToken" maxlength="6" placeholder="123456"> <button (click)="verifyOtp()">验证登录</button> </div> ` }) export class LoginComponent { username: string; password: string; showOtpVerify = false; otpToken: string; currentUserSecret: string; constructor( private authService: AuthService, private userService: UserService, private router: Router ) {} submitLogin() { // 第一步:验证用户名密码 this.userService.validateCredentials(this.username, this.password).subscribe(user => { if (user.otpSecret) { // 用户已开启OTP,显示验证码输入界面 this.showOtpVerify = true; this.currentUserSecret = user.otpSecret; } else { // 未开启OTP,直接登录成功 this.router.navigate(['/dashboard']); } }, error => { alert('用户名或密码错误'); }); } verifyOtp() { // 第二步:验证OTP验证码 if (this.authService.verifyOTP(this.currentUserSecret, this.otpToken)) { this.router.navigate(['/dashboard']); } else { alert('验证码不正确,请重试'); } } }
关键注意事项
- 密钥存储安全:绝对不能把OTP密钥存在前端本地存储或cookie里,必须存储在后端数据库中,前端仅在验证环节临时获取使用
- 时间同步:TOTP依赖时间同步,服务器和用户设备的时间差不能超过1分钟,否则会验证失败,建议服务器开启NTP时间同步
- 备用方案:建议给用户生成几个一次性备用验证码,当用户无法使用Google Authenticator时,可以用备用码登录
- 版本兼容:Angular 5版本较老,安装依赖时注意版本适配,
speakeasy建议选v2.x,qrcode建议选v1.x,避免版本冲突
内容的提问来源于stack exchange,提问作者Hamid Aijaz
相关产品推荐
相关产品推荐

