網頁

2021/2/2

Spring Boot 使用Google身分驗證器做TOTP驗證 Google Authenticator TOTP auth

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驗證的整體流程如下。

  1. User:輸入帳號密碼建立帳戶。
  2. Server:建立使用者帳戶。
  3. Server:產生TOTP密鑰。
  4. Server:儲存密鑰(例如資料庫)。
  5. Server:建立帶有Google Authenticator密鑰的QR code url。
  6. Server:回傳QR code url給使用者。
  7. User:掃描QR code取得Google Authenticator帳號密鑰。
  8. User:使用Google Authenticator產生TOTP一次性密碼。
  9. User:登入時輸入帳號、密碼及TOTP一次性密碼。
  10. Server:驗證使用者輸入的帳號密碼。
  11. Server:取出建立帳號時儲存的密鑰。
  12. Server:使用密鑰產生TOTP一次性密碼。
  13. 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密鑰的產生與一次性密碼的驗證。

github


測試

使用(測試)步驟如下:

  1. 手機安裝Google Authenticator。
  2. 啟動Spring Boot專案。
  3. 開啟瀏覽器輸入/auth/qrcode會返回QR code。
  4. 用手機掃描QR code會提示開啟Google Authenticator並新增帳號及密鑰。
  5. 在Google Authenticator點選該帳號取得TOTP一次性密碼,時效為30秒。

  6. 開啟瀏覽器輸入/auth/{code}{code}帶入OTP一次性密碼,例如/auth/024879並得到驗證結果。

2 則留言:

  1. 我最近在研究怎麼用spring boot實作Google Authenticator以及OAuth2.0相關資訊,結果搜尋到滿多你寫的文章,提到我許多有用的方向,特地留言向你說謝謝

    回覆刪除
  2. Hi JAVA吉他手 不客氣,很高興對你有幫助

    回覆刪除