驗證身份的對象元素
在shiro中,用戶需要提供principals (身份)和credentials(證明)給shiro,從而應用能驗證用戶身份:
principals:身份,即主體的標識屬性,可以是任何東西,如用戶名、郵箱等,唯一即可。一個主體可以有多個principals,但只有一個Primary principals,一般是用戶名/密碼/手機號。
credentials:證明/憑證,即只有主體知道的安全值,如密碼/數字證書等。
認證流程
securiyManager是驗證開始的地方,但從數據源取數據并作比較的工作是由Realm來進行的
ModularRealmAuthenticator:
protected AuthenticationInfo doMultiRealmAuthentication(Collection<Realm> realms, AuthenticationToken token) { //獲取認證策略 AuthenticationStrategy strategy = getAuthenticationStrategy(); AuthenticationInfo aggregate = strategy.beforeAllAttempts(realms, token); if (log.isTraceEnabled()) { log.trace("Iterating through {} realms for PAM authentication", realms.size()); } //循環realm for (Realm realm : realms) { aggregate = strategy.beforeAttempt(realm, token, aggregate); if (realm.supports(token)) { log.trace("Attempting to authenticate token [{}] using realm [{}]", token, realm); AuthenticationInfo info = null; Throwable t = null; try { //關鍵:調用realm的getAuthenticationInfo進行認證,token為用戶的token info = realm.getAuthenticationInfo(token); } catch (Throwable throwable) { t = throwable; if (log.isDebugEnabled()) { String msg = "Realm [" + realm + "] threw an exception during a multi-realm authentication attempt:"; log.debug(msg, t); } } aggregate = strategy.afterAttempt(realm, token, info, aggregate, t); } else { log.debug("Realm [{}] does not support token {}. Skipping realm.", realm, token); } } aggregate = strategy.afterAllAttempts(token, aggregate); return aggregate; }
?在Realm開始處理驗證的邏輯之前,Authenticator將調用Realm的 supports 方法去驗證當前Realm是否支持獲得的AuthenticationToken。
boolean supports (AuthenticationToken token);
通常,Realm檢查的是token的類型,比如在 AuthenticatingRealm 中檢查類型是否相同。
public boolean supports(AuthenticationToken token) { return token != null && getAuthenticationTokenClass().isAssignableFrom(token.getClass()); }
?
另外,AuthenticatingRealm的constructor中類型默認為
authenticationTokenClass = UsernamePasswordToken .class;
如果當前Realm支持提交過來的token,authenticator則調用 getAuthenticationInfo (token) 方法。
?以 AuthenticatingRealm 為例(注意是final):
public final AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { //先從緩存中取 //token為需驗證的用戶信息(前臺傳來的需要驗證的用戶) //info為根據需驗證的用戶信息(前臺傳來的需要驗證的用戶)獲得校驗的用戶驗證信息(數據源中獲得的正確的用戶信息) AuthenticationInfo info = getCachedAuthenticationInfo(token); if (info == null) { //otherwise not cached, perform the lookup: //自定義Realm的擴展點 //根據需要驗證的用戶信息獲得正確的用戶信息(獲得方式:數據庫,配置文件等) info = doGetAuthenticationInfo(token); log.debug("Looked up AuthenticationInfo [{}] from doGetAuthenticationInfo", info); if (token != null && info != null) { cacheAuthenticationInfoIfPossible(token, info); } } else { log.debug("Using cached authentication info [{}] to perform credentials matching.", info); } if (info != null) { //真正的驗證,token,info //此驗證內,如果驗證不對,則拋出異常 assertCredentialsMatch(token, info); } else { log.debug("No AuthenticationInfo found for submitted AuthenticationToken [{}]. Returning null.", token); } return info; }
?有些人直接在doGetAuthenticationInfo該方法中完成也驗證,驗證通過時返回SimpleAuthenticationInfo實例,失敗則拋出相應的驗證異常。
但下面有個 assertCredentialsMatch ,說明doGetAuthenticationInfo本沒有打算這樣用,這種使用方式會讓 CredentialMatcher 失去意義。
protected void assertCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) throws AuthenticationException { //獲得CredentialsMatcher,有多種實現 CredentialsMatcher cm = getCredentialsMatcher(); if (cm != null) { //擴展點 //認證失敗拋出異常 if (!cm.doCredentialsMatch(token, info)) { //not successful - throw an exception to indicate this: String msg = "Submitted credentials for token [" + token + "] did not match the expected credentials."; throw new IncorrectCredentialsException(msg); } } else { throw new AuthenticationException("A CredentialsMatcher must be configured in order to verify " + "credentials during authentication. If you do not wish for credentials to be examined, you " + "can configure an " + AllowAllCredentialsMatcher.class.getName() + " instance."); } }
?AuthenticatingRealm的默認CredentialMatcher是SimpleCredentialsMatcher
public AuthenticatingRealm() { this(null, new SimpleCredentialsMatcher()); }
?
protected boolean equals(Object tokenCredentials, Object accountCredentials) { if (log.isDebugEnabled()) { log.debug("Performing credentials equality check for tokenCredentials of type [" + tokenCredentials.getClass().getName() + " and accountCredentials of type [" + accountCredentials.getClass().getName() + "]"); } if (isByteSource(tokenCredentials) && isByteSource(accountCredentials)) { if (log.isDebugEnabled()) { log.debug("Both credentials arguments can be easily converted to byte arrays. Performing " + "array equals comparison"); } byte[] tokenBytes = toBytes(tokenCredentials); byte[] accountBytes = toBytes(accountCredentials); return Arrays.equals(tokenBytes, accountBytes); } else { return accountCredentials.equals(tokenCredentials); } }
?當然,實現類還有HashedCredentialsMatcher,使用密碼加密的方式提高安全性
<!--EndFragment-->
?
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
