Google Authenticator身分驗證器在Spring Boot中進行TOTP驗證。
簡介
首先介紹Google Authenticator的基本概念。
Google Authenticator是一個裝在手機上的app,根據輸入的密鑰(secret key)來產生一次性密碼(One-time password, OTP),是雙重要素驗證(2FA)的一種方式。
雙要素驗證(Two-Factor Authentication)簡稱2FA,是指除了使用者輸入原本的帳號密碼(要素一)再額外多一種方式進行驗證(要素二)。而Google Authenticator就是一種額外的要素驗證。
TOTP(Time-Based One-Time Password Algorithm)「基於時間的一次性密碼演算法」(RFC 6238),為HOTP(HMAC-Based One-Time Password Algorithm)「基於雜湊訊息的一次性密碼演算法」(RFC 4226)的變型。TOTP根據輸入的密鑰(secret key)與時間要素(time-factor)產生一個有時效性驗證碼。而Google Authenticator就是使用TOTP演算法產生一次性密碼。
+-------------+
| secret(key) |
+------+------+
| +----------------+
+---------->| | +-------------------------+
| TOTP Algorithm |---->| OTP (One-time password) |
+---------->| | +-------------------------+
| +----------------+
+------+------+
| time factor |
+-------------+
使用Google Authenticator驗證的整體流程如下。
- User:輸入帳號密碼建立帳戶。
- Server:建立使用者帳戶。
- Server:產生TOTP密鑰。
- Server:儲存密鑰(例如資料庫)。
- Server:建立帶有Google Authenticator密鑰的QR code url。
- Server:回傳QR code url給使用者。
- User:掃描QR code取得Google Authenticator帳號密鑰。
- User:使用Google Authenticator產生TOTP一次性密碼。
- User:登入時輸入帳號、密碼及TOTP一次性密碼。
- Server:驗證使用者輸入的帳號密碼。
- Server:取出建立帳號時儲存的密鑰。
- Server:使用密鑰產生TOTP一次性密碼。
- Server:比對使用者輸入的TOTP及密鑰產生的TOTP來驗證。
+------------+ +------------+
| User | | Server |
+------+-----+ +-----+------+
| |
| |
| 1:Sign up with account, password |
|------------------------------------------>+
| |2:Create account
| |3:Generate TOTP key
| |4:Save key to repository
| |5:Create Google Authenticator
| 6:Return QR code url | key QR code url
+<------------------------------------------+
7:Scan QR code | |
to enter secret key | |
in Goolge Authenticator | |
| |
8:Google Authenticator | |
generate OTP code | |
by TOTP algorithm | |
| 9:Login with account, password and OTP |
+------------------------------------------>+
| |10:Verify account and password
| |11:Get key from repository
| |12:Generate OTP code with key
| | by TOTP algorithem
| |13:Compare input OTP code
| | and genereted OTP code
| | to verify idenity
| |
v v
範例
本範例使用com.warrenstrange:googleauth
套件產生TOTP密鑰及一次性密碼
範例環境:
- Java 8
- Spring Boot 2.3.2.RELEASE
- Maven
建立Spring Boot專案,並在pom.xml
加入com.warrenstrange:googleauth
依賴如下。
<dependency>
<groupId>com.warrenstrange</groupId>
<artifactId>googleauth</artifactId>
<version>1.4.0</version>
</dependency>
新增Controller API如下:
/auth/qrcode
:用來產生並回傳Google Authenticator密鑰uri的QR code url;/auth/{code}
:用來做OTP驗證。
Google Authenticator接收的uri格式為otpauth://TYPE/LABEL?PARAMETERS
,見Key Uri Format。
產生QR code圖型則利用Google Charts的QR Codes API https://chart.googleapis.com/chart?
(目前已經deprecated,或改用zxing)。
DemoController
package com.abc.demo.controller;
import com.warrenstrange.googleauth.GoogleAuthenticator;
import com.warrenstrange.googleauth.GoogleAuthenticatorKey;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.view.RedirectView;
@Controller
public class DemoController {
private static String secret; // 儲存的密鑰
/**
* 取得帶有Google Authenticator驗證器密鑰的qrcode
*
* @return 帶有Google Authenticator驗證氣密鑰的qrcode url
*/
@GetMapping("/auth/qrcode")
public RedirectView getSecretQrCode() {
secret = genSecretKey(); // 產生並儲存密鑰
String qrCodeData = createGoogleAuthenticatorKeyUri(secret);
System.out.println(qrCodeData); // otpauth://totp/ABC:john@@abc.com?secret=CIIHTWBCP7AA6TXT&issuer=ABC
String googleChartsQrCodeFormat = "https://www.google.com/chart?chs=200x200&cht=qr&chl=%s";
String qrCodeUrl = String.format(googleChartsQrCodeFormat, qrCodeData);
System.out.println(qrCodeUrl); // https://www.google.com/chart?chs=200x200&cht=qr&chl=otpauth://totp/ABC:john@@abc.com?secret=CIIHTWBCP7AA6TXT&issuer=ABC
return new RedirectView(qrCodeUrl); // 重新導向到指定的url
}
/**
* 產生密鑰字串
*
* @return 密鑰字串
*/
private String genSecretKey() {
GoogleAuthenticator gAuth = new GoogleAuthenticator();
final GoogleAuthenticatorKey key = gAuth.createCredentials();
return key.getKey();
}
/**
* 建立Google Authenticator密鑰uri
*
* @param secret 密鑰字串
* @return Google Authenticator密鑰uri
*/
private String createGoogleAuthenticatorKeyUri(String secret) {
String otpType = "totp";
String account = "ABC:john@abc.com";
String issuer = "ABC";
String googleAuthenticatorKeyUriFormat = "otpauth://%s/%s?secret=%s&issuer=%s";
return String.format(googleAuthenticatorKeyUriFormat, otpType, account, secret, issuer);
}
/**
* Google Authenticator TOTP驗證
*
* @param code 一次性驗證碼
* @return 驗證結果
*/
@GetMapping("/auth/{code}")
@ResponseBody
public String googleAuthenticatorAuth(@PathVariable("code") int code) {
GoogleAuthenticator gAuth = new GoogleAuthenticator();
boolean isCodeValid = gAuth.authorize(secret, code); // 驗證
return isCodeValid ? "pass" : "fail";
}
}
上面省略建立使用者帳號並簡化儲存TOTP密鑰及驗證身分的過程,僅保留TOTP密鑰的產生與一次性密碼的驗證。
測試
使用(測試)步驟如下:
- 手機安裝Google Authenticator。
- 啟動Spring Boot專案。
- 開啟瀏覽器輸入
/auth/qrcode
會返回QR code。 - 用手機掃描QR code會提示開啟Google Authenticator並新增帳號及密鑰。
- 在Google Authenticator點選該帳號取得TOTP一次性密碼,時效為30秒。
- 開啟瀏覽器輸入
/auth/{code}
,{code}
帶入OTP一次性密碼,例如/auth/024879
並得到驗證結果。
我最近在研究怎麼用spring boot實作Google Authenticator以及OAuth2.0相關資訊,結果搜尋到滿多你寫的文章,提到我許多有用的方向,特地留言向你說謝謝
回覆刪除Hi JAVA吉他手 不客氣,很高興對你有幫助
回覆刪除