1. 程式人生 > >如何開發兩步驗證功能

如何開發兩步驗證功能

什麼是兩步驗證

  兩步驗證,是指使用者登入賬戶的時候,除了要輸入使用者名稱和密碼,還要求使用者輸入一個動態密碼,為帳戶添加了一層額外保護。這個動態密碼要麼是專門的硬體,要麼由使用者手機APP提供。即使入侵者竊取了使用者密碼,也會因不能使用使用者手機而無法登入帳戶。許多遊戲客戶端和網銀採用這種方式。以銀行為例,當用戶進行轉賬操作時,第一步輸入6位取款密碼,第二步輸入動態密碼器上數字,這個密碼器是開戶時銀行提供的硬體。

動態密碼原理

  客戶端和伺服器事先協商好一個金鑰K,用於一次性密碼的生成過程,此金鑰不被任何第三方所知道。此外,客戶端和伺服器各有一個計數器C,並且事先將計數值同步。進行驗證時,客戶端對金鑰和計數器的組合(K,C)使用HMAC(Hash-based Message Authentication Code)演算法計算一次性密碼,公式如下:HOTP(K,C) = Truncate(HMAC-SHA-1(K,C))

上面採用了HMAC-SHA-1,當然也可以使用HMAC-MD5等。HMAC演算法得出的值位數比較多,不方便使用者輸入,因此需要截斷(Truncate)成為一組不太長十進位制數(例如6位)。計算完成之後客戶端計數器C計數值加1。使用者將這一組十進位制數輸入並且提交之後,伺服器端同樣的計算,並且與使用者提交的數值比較,如果相同,則驗證通過,伺服器端將計數值C增加1。如果不相同,則驗證失敗。

業務流程

  如何開發這個功能呢?我們先理清它的流程:

  1. 第一步:輸入常規帳號密碼,驗證成功後進入二次驗證頁面。
  2. 第二步:二次驗證頁面要求使用者輸入動態密碼,使用者手機必須先通過APP繫結帳號後才能獲取動態密碼,APP推薦eagle2fa。
  3. 第三步:頁面生成二維碼,內容是URI地址otpauth://totp/賬號?secret=金鑰,用eagle2fa掃碼後繫結帳號,把金鑰儲存在客戶端,如下圖所示:
  4. 第四步:輸入APP上的6位數字,驗證通過進入使用者中心頁面。
    最重要的功能是生成二維碼和驗證動態密碼。

元件選型

googleauth是Google Authenticator的開源實現

    <dependency>
        <groupId>com.warrenstrange</groupId>
        <artifactId>googleauth</artifactId>
        <version>1.1.2</version>
   </dependency>

zxing用於生成二維碼圖片

    <dependency>
        <groupId>com.google.zxing</groupId>
        <artifactId>javase</artifactId>
        <version>3.3.3</version>
    </dependency>

也可以使用其他網站提供的的WEB API,譬如:

http://qr.liantu.com/api.php?text=x
x必須用UTF8編碼格式,x內容出現&符號時用%26代替,換行符使用%0A

關鍵程式碼

以下程式碼演示怎麼使用GoogleAuthenticator包:

    private static final GoogleAuthenticator googleAuthenticator = new GoogleAuthenticator();

    /**
     * 由於只是演示,dao沒有操作資料庫,真實的場景必然要持久化
     */
    @Autowired
    private UserDao userDao;

    @PostConstruct
    public void init() {
        googleAuthenticator.setCredentialRepository(new ICredentialRepository() {
            @Override
            public String getSecretKey(String userName) {
                //根據帳號查詢secretKey
                return userDao.getSecretKey(userName);
            }

            @Override
            public void saveUserCredentials(String userName, String secretKey, int validationCode, List<Integer> scratchCodes) {
                //secretKey要儲存在資料庫中
                userDao.saveUserCredentials(userName, secretKey);
            }
        });
        log.info("GoogleAuthenticator初始化成功");
    }

以下程式碼是生成二維碼,uri的格式不能寫錯。

    // 必須按照這個格式,APP才能正常繫結
    private static final String KEY_FORMAT = "otpauth://totp/%s?secret=%s";

    /**
     * 生成二維碼連結
     */
    private String getQrUrl(String username) {
        //每次呼叫createCredentials都會生成新的secretKey
        GoogleAuthenticatorKey key = googleAuthenticator.createCredentials(username);
        log.info("username={},secretKey={}", username, key.getKey());
        return String.format(KEY_FORMAT, username, key.getKey());
    }

以下是二次驗證方法:

    // 驗證動態密碼  username 帳號, code  app上的6位數字
    public boolean validCode(String username, int code) {
        return googleAuthenticator.authorizeUser(username, code);
    }

點選獲取完整程式碼

參考(部分摘抄的文字版權屬於原作者)

https://blog.seetee.me/post/2011/google-two-step-verification/
https://www.zhihu.com/question/20462696/answer/19670