如何開發兩步驗證功能
- 2019 年 10 月 3 日
- 筆記
什麼是兩步驗證
兩步驗證,是指用戶登錄賬戶的時候,除了要輸入用戶名和密碼,還要求用戶輸入一個動態密碼,為帳戶添加了一層額外保護。這個動態密碼要麼是專門的硬體,要麼由用戶手機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。如果不相同,則驗證失敗。
業務流程
如何開發這個功能呢?我們先理清它的流程:
- 第一步:輸入常規帳號密碼,驗證成功後進入二次驗證頁面。
- 第二步:二次驗證頁面要求用戶輸入動態密碼,用戶手機必須先通過APP綁定帳號後才能獲取動態密碼,APP推薦eagle2fa。
- 第三步:頁面生成二維碼,內容是URI地址
otpauth://totp/帳號?secret=密鑰
,用eagle2fa掃碼後綁定帳號,把密鑰保存在客戶端,如下圖所示:
- 第四步:輸入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/19670601