兩步驗證是指用戶登錄賬戶的時候,除了要輸入用戶名和密碼,還要求用戶輸入一個動態密碼,為帳戶添加了一層額外保護。這個動態密碼要麼是專門的硬體,要麼由用戶手機APP提供。即使入侵者竊取了用戶密碼,也會因不能使用用戶手機而無法登錄帳戶。許多游戲客戶端和網銀採用這種方式。以銀行為例,當用戶進行轉賬操作時,第... ...
什麼是兩步驗證
兩步驗證,是指用戶登錄賬戶的時候,除了要輸入用戶名和密碼,還要求用戶輸入一個動態密碼,為帳戶添加了一層額外保護。這個動態密碼要麼是專門的硬體,要麼由用戶手機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