摘要:本文討論一個問題:存儲token時,token與對應用戶id誰來作爲key? 問題起源問題起源於要給公司的後臺管理系統添加權限管理,選用的是開源框架shiro,而原本系統上是採用token做了登錄校驗的。
本文分享自華爲雲社區《討論兩種Redis中Token的存儲方式》,作者:洛葉飄。
問題起源
問題起源於要給公司的後臺管理系統添加權限管理,選用的是開源框架shiro,而原本系統上是採用token做了登錄校驗的。
我所採用的shiro驗證方式是,每次接口請求,根據token來獲取用戶id,然後通過shiro中的登錄驗證機制來進行權限校驗。因此,“根據token獲取用戶id”就要求在存儲用戶token時,以token爲鍵值key,以用戶ID爲value值。
然而此時面臨一個問題是,系統原本的token存儲方式如下,我們稱之爲第一種:用戶ID爲key。
cache.set(TOKEN_PREFIX + userid, token);
這就需要我做出判斷,需不需要修改token的存儲方式爲下面的形式:我們稱之爲第二種:token爲key。
cache.set(TOKEN_PREFIX + token, userid);
思考
第一個問題,兩種方式是否都能夠實現需求功能?
我們需要實現的功能包括:
- 登錄驗證
- shiro中的權限驗證
登錄驗證
對於"用戶ID爲key"的方式,需要前端傳遞用戶id+token兩個值,驗證登錄狀態需要我們根據前端傳遞的用戶ID,獲取數據庫中存儲的token,與前端傳遞的token進行校驗,如果一致,則校驗通過,否則返回錯誤信息,提示用戶需要重新登錄等等。
對於“token爲key”的方式,前端至少需要傳遞token一個值,根據前端傳遞的token,獲取數據庫中存儲的用戶ID,如果能獲取到,則校驗通過,否則提示用戶token已過期,需要用戶重新登錄等等。
shiro中的權限驗證
shiro中的權限驗證,涉及到具體的實現機制,以token爲key的方式,就以我們的真實實現爲例:
// shiro登錄代碼: Subject s = SecurityUtils.getSubject(); JWTToken jwtToken = new JWTToken(token); subject.login(jwtToken); // 實現AuthenticationToken的類: import org.apache.shiro.authc.AuthenticationToken; public class JWTToken implements AuthenticationToken { private static final long serialVersionUID = 1L; // 密鑰 private String token; public JWTToken(String token) { this.token = token; } @Override public Object getPrincipal() { return token; } @Override public Object getCredentials() { return token; } } /** * 自定義的登錄驗證類: */ public class ShiroDbRealm extends AuthorizingRealm{ /** * 重寫shiro的token */ @Override public boolean supports(AuthenticationToken token) { return token instanceof JWTToken; } /** * 角色,權限認證 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); //這裏可以連接數據庫根據用戶賬戶進行查詢用戶角色權限等信息 return simpleAuthorizationInfo; } /** * 自定義認證 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken auth) throws AuthenticationException { String token = (String) auth.getCredentials(); // 解密獲得userid,用於和數據庫進行對比 // getUserId實際就是通過token,在數據庫中取對應的userid Integer userid = JwtUtils.getUserId(token); if (tuserid == null) { throw new AuthenticationException("token 校驗失敗"); } return new SimpleAuthenticationInfo(token, token, getName()); } }
如果採用userid爲key的方式,不難實現,也修改其實現方式,
第二個問題,兩種方式哪一種傳輸的數據量更少?
第一種方式需要前端每次請求都傳遞token+userid;而第二種實際上可以只傳遞token,後臺根據token解密(或數據庫查找)來獲取用戶信息。
第三個問題,兩種方式哪種更安全?
兩種方式的安全應該是一樣的,核心是後臺通過數據庫保存token與userid的對應信息。
個人意見
個人比較細化第二種,以token爲key的方式,首先,前端傳遞簡單,只需要傳遞token即可;二是後端通過這種方式,可以統一當前登錄人的獲取方式,而不是每次在接口中獲取header中的用戶id。