在作完預備的初始化和session測試後,到了作為一個權鑒別框架的核心功能部分,確認你是誰--身份認證(Authentication)。 本文涉及到token的構建,框架結構下認證行為的調用,realm中授權數據的獲取、登錄信息比較,login過程中對已有有subject、session的處理 ...
在作完預備的初始化和session測試後,到了作為一個權鑒別框架的核心功能部分,確認你是誰--身份認證(Authentication)。
通過提交給shiro身份信息來驗證是否與儲存的安全信息數據是否相符來判斷用戶身份的真實性
本文涉及到token的構建,框架結構下認證行為的調用,realm中授權數據的獲取、登錄信息比較,login過程中對已有有subject、session的處理
同樣,本篇本文使用的是shiro 1.3.2版本,配合源碼最佳~
官方例子代碼流程如下
if (!currentUser.isAuthenticated()) { UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa"); token.setRememberMe(true); try { currentUser.login(token); } catch (UnknownAccountException uae) { log.info("There is no user with username of " + token.getPrincipal()); } catch (IncorrectCredentialsException ice) { log.info("Password for account " + token.getPrincipal() + " was incorrect!"); } catch (LockedAccountException lae) { log.info("The account for username " + token.getPrincipal() + " is locked. " + "Please contact your administrator to unlock it."); } // ... catch more exceptions here (maybe custom ones specific to your application? catch (AuthenticationException ae) { //unexpected condition? error? } }
3.1首先構造token,其中 UsernamePasswordToken 實現了HostAuthenticationToken和RememberMeAuthenticationToken介面,他們都繼承自AuthenticationToken介面
HostAuthenticationToken介面指定了token的域,RememberMeAuthenticationToken介面指定了token是否實現RememberMe(認證跨session)
ps:shiro 認證狀態區分Authenticated和Remembered,Authenticated指當前session(或流程)下已經過認證,Remembered指曾經獲得認證,如果是web狀態下RememberMe相關信息保存在cookie中
二者可以調用 subject.isAuthenticated() 及subject.isRemembered()區分
其中UsernamePasswordToken 的Principal(身份)概念即Username,Credentials(憑證)概念即Password
在token 中設置用戶名和密碼
UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
設置是否使用RememberMe(此案例中不具體起作用,只是作為示例在token中設置
token.setRememberMe(true);
案例中不對host進行設置
3.2完成對token的構建設置後,便進行認證(Authentication)操作
currentUser.login(token);
首先先借用官方的例圖來瞭解一下整個的流程
1-調用subject的login入口傳入token
2-subject實際調用SecurityManager的login
3-securityManager再調用authenticator實例的doAuthenticate方法,一般這個是ModularRealmAuthenticator的實例
4-判斷realm的狀況,如果是單realm則直接調用realm,如果是多realm則要判斷認證策略(AuthenticationStrategy)
5-調用對應realm中的getAuthenticationInfo實現進行認證
DelegatingSubject.login代碼如下,下麵對認證流程進行詳細的解讀
public void login(AuthenticationToken token) throws AuthenticationException { clearRunAsIdentitiesInternal(); Subject subject = securityManager.login(this, token); PrincipalCollection principals; String host = null; if (subject instanceof DelegatingSubject) { DelegatingSubject delegating = (DelegatingSubject) subject; //we have to do this in case there are assumed identities - we don't want to lose the 'real' principals: principals = delegating.principals; host = delegating.host; } else { principals = subject.getPrincipals(); } if (principals == null || principals.isEmpty()) { String msg = "Principals returned from securityManager.login( token ) returned a null or " + "empty value. This value must be non null and populated with one or more elements."; throw new IllegalStateException(msg); } this.principals = principals; this.authenticated = true; if (token instanceof HostAuthenticationToken) { host = ((HostAuthenticationToken) token).getHost(); } if (host != null) { this.host = host; } Session session = subject.getSession(false); if (session != null) { this.session = decorate(session); } else { this.session = null; } }
3.2.1首先是初始化
clearRunAsIdentitiesInternal();
如果subject已含session則獲取session並清除其中參數
3.2.2調用securityManager中的()login()開始認證
Subject subject = securityManager.login(this, token);
這裡調用的是DefaultSecurityManager中的login
public Subject login(Subject subject, AuthenticationToken token) throws AuthenticationException { AuthenticationInfo info; try { info = authenticate(token);//調用AbstractAuthenticator.authenticate, } catch (AuthenticationException ae) { try { onFailedLogin(token, ae, subject);//認證失敗,清除remenberMe的信息使之失效 } catch (Exception e) { if (log.isInfoEnabled()) { log.info("onFailedLogin method threw an " + "exception. Logging and propagating original AuthenticationException.", e); } } throw ae; //propagate } Subject loggedIn = createSubject(token, info, subject); onSuccessfulLogin(token, info, loggedIn); return loggedIn; }
3.2.2.1
調用AbstractAuthenticator.authenticate(),其中調用ModularRealmAuthenticator.doAuthenticate
protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException { assertRealmsConfigured(); Collection<Realm> realms = getRealms(); if (realms.size() == 1) { return doSingleRealmAuthentication(realms.iterator().next(), authenticationToken); } else { return doMultiRealmAuthentication(realms, authenticationToken); } }
判斷realms可用由於是配置的是單realm
則調用ModularRealmAuthenticator.doSingleRealmAuthentication(),其中調用realm實現判斷token類型是否支持,然後執行realm實現中的getAuthenticationInfo方法
protected AuthenticationInfo doSingleRealmAuthentication(Realm realm, AuthenticationToken token) { if (!realm.supports(token)) { String msg = "Realm [" + realm + "] does not support authentication token [" + token + "]. Please ensure that the appropriate Realm implementation is " + "configured correctly or that the realm accepts AuthenticationTokens of this type."; throw new UnsupportedTokenException(msg); } AuthenticationInfo info = realm.getAuthenticationInfo(token); if (info == null) { String msg = "Realm [" + realm + "] was unable to find account data for the " + "submitted AuthenticationToken [" + token + "]."; throw new UnknownAccountException(msg); } return info; }
從下圖中看出這裡的realm是配置後的iniRealm實例,其方法在AuthenticatingRealm中實現,由於inirealm直接在初始化onInit()時就在記憶體載入了所有授權數據信息,例子並沒有使用cache。
public final AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { AuthenticationInfo info = getCachedAuthenticationInfo(token); if (info == null) { //otherwise not cached, perform the lookup: 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) { assertCredentialsMatch(token, info); } else { log.debug("No AuthenticationInfo found for submitted AuthenticationToken [{}]. Returning null.", token); } return info; }
3.2.2.1.1調用SimpleAccountRealm.doGetAuthenticationInfo通過用戶名從Map users 取出對應的SimpleAccount,並判斷是否被鎖,是否過期
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { UsernamePasswordToken upToken = (UsernamePasswordToken) token; SimpleAccount account = getUser(upToken.getUsername()); if (account != null) { if (account.isLocked()) { throw new LockedAccountException("Account [" + account + "] is locked."); } if (account.isCredentialsExpired()) { String msg = "The credentials for account [" + account + "] are expired"; throw new ExpiredCredentialsException(msg); } } return account; }
對應getUser方法使用了讀鎖保證了map讀操作的原子性
protected SimpleAccount getUser(String username) { USERS_LOCK.readLock().lock(); try { return this.users.get(username); } finally { USERS_LOCK.readLock().unlock(); } }
3.2.2.1.2調用AuthenticatingRealm.assertCredentialsMatch,獲取密碼比較器(CredentialsMatcher),其中調用doCredentialsMatch獲取對比token和info(上面從realm通過用戶名獲取的AuthenticationInfo實例)中的密碼(核心的一步),並返回結果
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) { Object tokenCredentials = getCredentials(token); Object accountCredentials = getCredentials(info); return equals(tokenCredentials, accountCredentials); }
3.2.2.2隨後通知認證監聽器AuthenticationListener(本例未設置)層層返回info至DefaultSecurityManager創建新的有登錄信息的subject
Subject loggedIn = createSubject(token, info, subject);
並將現有subject中的信息賦予新的subject
protected Subject createSubject(AuthenticationToken token, AuthenticationInfo info, Subject existing) { SubjectContext context = createSubjectContext(); context.setAuthenticated(true); context.setAuthenticationToken(token); context.setAuthenticationInfo(info); if (existing != null) { context.setSubject(existing); } return createSubject(context); }
3.2.2.3設置rememberMe(本例未實現
onSuccessfulLogin(token, info, loggedIn);
3.2.3 繼續獲取信息principals,host(本例無),設置session到subject,
if (subject instanceof DelegatingSubject) { DelegatingSubject delegating = (DelegatingSubject) subject; //we have to do this in case there are assumed identities - we don't want to lose the 'real' principals: principals = delegating.principals; host = delegating.host; } else { principals = subject.getPrincipals(); } if (principals == null || principals.isEmpty()) { String msg = "Principals returned from securityManager.login( token ) returned a null or " + "empty value. This value must be non null and populated with one or more elements."; throw new IllegalStateException(msg); } this.principals = principals; this.authenticated = true; if (token instanceof HostAuthenticationToken) { host = ((HostAuthenticationToken) token).getHost(); } if (host != null) { this.host = host; } Session session = subject.getSession(false); if (session != null) { this.session = decorate(session); } else { this.session = null; }
3.3完成認證後測試一下Principal(用戶名)是否正確
log.info("User [" + currentUser.getPrincipal() + "] logged in successfully.");
可見用戶信息正確
至此完成了一個簡單的Authentication過程
參考:
http://shiro.apache.org/10-minute-tutorial.html
http://shiro.apache.org/authentication.html
http://www.apache.org/dyn/closer.cgi/shiro/1.3.2/shiro-root-1.3.2-source-release.zip
轉載請註明作者及來源:https://www.cnblogs.com/codflow/