身份認(rèn)證,即在應(yīng)用中證明他就是他本人。一般提供如他們的身份ID一些標(biāo)識信息來 表明他就是他本人,如提供身份證,用戶名/密碼來證明。在 shiro 中,用戶需要提供 principals (身份)和 credentials(證明)給 shiro,從而應(yīng)用能 驗證用戶身份。本文重點關(guān)注利用shiro進(jìn)行身份認(rèn)證時,shiro的內(nèi)部工作流程。
一、使用shiro身份認(rèn)證demo
使用shiro進(jìn)行身份認(rèn)證的最簡單的demo如下:
public void testHelloworld() {
//1、獲取 SecurityManager 工廠,此處使用 Ini 配置文件初始化SecurityManager
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
//2、得到 SecurityManager 實例 并綁定給 SecurityUtils
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
//3、得到 Subject 及創(chuàng)建用戶名/密碼身份驗證 Token(即用戶身份/憑證)
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("zhang", "123");
try {
//4、登錄,即身份驗證
subject.login(token);
} catch (AuthenticationException e) {
//5、身份驗證失敗
}
Assert.assertEquals(true, subject.isAuthenticated()); //斷言用戶已經(jīng)登錄
//6、退出
subject.logout();
}
從如上代碼可總結(jié)出身份驗證的步驟:
1、收集用戶身份/憑證,即如用戶名/密碼;
2、調(diào)用 Subject.login 進(jìn)行登錄,如果失敗將得到相應(yīng)的 AuthenticationException 異常,根 據(jù)異常提示用戶錯誤信息;否則登錄成功;
3、最后調(diào)用 Subject.logout 進(jìn)行退出操作。
那么SHIRO內(nèi)部流程是怎樣的了,在創(chuàng)建完SecurityManager后,接下來的步驟內(nèi)部做了什么工作了?
二、創(chuàng)建Subject
創(chuàng)建完SecurityManager后,首先將SecurityManager綁定給了securityUtils,然后是獲取Subject。
public static Subject getSubject() {
Subject subject = ThreadContext.getSubject();
if (subject == null) {
subject = (new Subject.Builder()).buildSubject();
ThreadContext.bind(subject);
}
return subject;
}
這步首先構(gòu)造Subject.Builder,Subject采用的Builder模式,然后調(diào)用Builder的buildeSubject()方法創(chuàng)建一個Subject對象,具體過程如下:
public Builder() {
this(SecurityUtils.getSecurityManager());
}
public Builder(SecurityManager securityManager) {
this.securityManager = securityManager;
this.subjectContext = newSubjectContextInstance();
this.subjectContext. setSecurityManager(securityManager);
}
其中SubjectContext為新建一個默認(rèn)的DefaultSubjectContext();
protected SubjectContext newSubjectContextInstance() {
return new DefaultSubjectContext();
}
創(chuàng)建好Builder后,調(diào)用builderSubject方法,其內(nèi)部是委托給SecurityManager創(chuàng)建Subject的。
public Subject buildSubject() {
return this.securityManager.createSubject(this.subjectContext);
}
SecurityManager創(chuàng)建過程是:首先創(chuàng)建一個默認(rèn)的DefaultSecurityManager,然后根據(jù)配置再更新其內(nèi)部屬性和成員,DefaultSecurityManager中創(chuàng)建Subject方法如下:
public Subject createSubject(SubjectContext subjectContext) {
SubjectContext context = copy(subjectContext);
context = ensureSecurityManager(context);
context = resolveSession(context);
context = resolvePrincipals(context);
Subject subject = doCreateSubject(context);
save(subject);
return subject;
}
其中創(chuàng)建Subject的方法為Subject subject = doCreateSubject(context),方法定義如下:
protected Subject doCreateSubject(SubjectContext context) {
return getSubjectFactory().createSubject(context);
}
方法內(nèi)部使用的是SubjectFactory創(chuàng)建的,具體使用的是DefaultSubjectFactory,然后調(diào)用其createSubject方法,方法定義如下:
public Subject createSubject(SubjectContext context) {
SecurityManager securityManager = context.resolveSecurityManager();
Session session = context.resolveSession();
boolean sessionCreationEnabled = context.isSessionCreationEnabled();
PrincipalCollection principals = context.resolvePrincipals();
boolean authenticated = context.resolveAuthenticated();
String host = context.resolveHost();
return new DelegatingSubject(principals, authenticated, host, session, sessionCreationEnabled, securityManager);
}
從上述方法可以看到,方法返回的是一個DelegatingSubject,DelegatingSubject是Subject子類,其源碼注釋如下:
/**
* Implementation of the {@code Subject} interface that delegates
* method calls to an underlying {@link org.apache.shiro.mgt.SecurityManager SecurityManager} instance for security checks.
* It is essentially a {@code SecurityManager} proxy.
* <p/>
**/
至此,Subject創(chuàng)建完畢!
三、身份認(rèn)證
首先看看Shiro核心邏輯的類圖:

后面的分析均是基于此類圖,下面進(jìn)行詳細(xì)分析。
代碼subject.login(token)即進(jìn)行身份認(rèn)證,首先看看login方法:
public void login(AuthenticationToken token) throws AuthenticationException {
Subject subject = securityManager.login(this, token);
PrincipalCollection principals;
String host = null;
if (subject instanceof DelegatingSubject) {
DelegatingSubject delegating = (DelegatingSubject) subject;
principals = delegating.principals;
host = delegating.host;
} else {
principals = subject.getPrincipals();
}
...
}
在上面代碼的第三行:Subject subject = securityManager.login(this, token); 注意到其調(diào)用了SecurityManager的login方法,login方法最終的實現(xiàn)在類DefaultSecurityManager中,方法如下:
public Subject login(Subject subject, AuthenticationToken token) throws AuthenticationException {
AuthenticationInfo info;
try {
info = authenticate(token);
} catch (AuthenticationException ae) {
onFailedLogin(token, ae, subject);
}
Subject loggedIn = createSubject(token, info, subject);
onSuccessfulLogin(token, info, loggedIn);
return loggedIn;
}
身份校驗authenticate方法是在DefaultSecurityManager的父類AuthenticatingSecurityManager定義的,AuthenticatingSecurityManager.authenticate方法如下:
public AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {
return this.authenticator.authenticate(token);
}
其中委托給Authenticator,AbstractAuthenticator中authenticate(token)方法定義如下:
public final AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {
AuthenticationInfo info;
try {
info = doAuthenticate(token);
}
} catch (Throwable t) {
notifyFailure(token, exception);
}
notifySuccess(token, info);
return info;
}
真正身份校驗邏輯是在ModularRealmAuthenticator中,ModularRealmAuthticator的doAuthenticate(token)方法如下:
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);
}
}
當(dāng)只有一個Realm時,就執(zhí)行doSingleRealmAuthentication,當(dāng)有多個Realm時,就執(zhí)行doMultiRealmAuthentication。我們看看doSingleRealmAuthentication中干了什么:
protected AuthenticationInfo doSingleRealmAuthentication(Realm realm, AuthenticationToken token) {
AuthenticationInfo info = realm.getAuthenticationInfo(token);
return info;
}
上面代碼:AuthenticationInfo info = realm.getAuthenticationInfo(token); realm為Realm接口,實際上調(diào)用的是其實現(xiàn)類AuthenticatingRealm中的getAuthenticationInfo方法,方法如下:
public final AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
AuthenticationInfo info = this.getCachedAuthenticationInfo(token);
if (info == null) {
info = this.doGetAuthenticationInfo(token);
if (token != null && info != null) {
this.cacheAuthenticationInfoIfPossible(token, info);
}
}
...
return info;
}
其中的this.doGetAuthenticationInfo(token)是一個抽象方法,真正的實現(xiàn)在我們自己定義的Realm。
三、總結(jié)

身份認(rèn)證流程如下:
1)如果沒有創(chuàng)建Subject,創(chuàng)建一個Subject;
2)調(diào)用 Subject.login(token)進(jìn)行登錄,其會自動委托給 Security Manager,調(diào)用之前必須通過 SecurityUtils. setSecurityManager()設(shè)置;
3)SecurityManager 負(fù)責(zé)真正的身份驗證邏輯;它會委托給 Authenticator 進(jìn)行身份驗證;
4)Authenticator 才是真正的身份驗證者,Shiro API 中核心的身份認(rèn)證入口點,此處可以自定義插入自己的實現(xiàn);
5)Authenticator 可能會委托給相應(yīng)的 AuthenticationStrategy 進(jìn)行多 Realm 身份驗證,默認(rèn) ModularRealmAuthenticator 會調(diào)用 AuthenticationStrategy 進(jìn)行多 Realm 身份驗證;
6)Authenticator 會把相應(yīng)的 token 傳入 Realm,從 Realm 獲取身份驗證信息,如果沒有返 回/拋出異常表示身份驗證失敗了。此處可以配置多個 Realm,將按照相應(yīng)的順序及策略進(jìn) 行訪問。
Shiro身份認(rèn)證分析到此為止,祝工作順利,天天開心!