身份認證伟件,即在應(yīng)用中證明他就是他本人。一般提供如他們的身份ID一些標識信息來 表明他就是他本人议经,如提供身份證郊尝,用戶名/密碼來證明掘宪。在 shiro 中,用戶需要提供 principals (身份)和 credentials(證明)給 shiro道宅,從而應(yīng)用能 驗證用戶身份嗓袱。本文重點關(guān)注利用shiro進行身份認證時籍救,shiro的內(nèi)部工作流程。
一渠抹、使用shiro身份認證demo
使用shiro進行身份認證的最簡單的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 進行登錄,如果失敗將得到相應(yīng)的 AuthenticationException 異常吨铸,根 據(jù)異常提示用戶錯誤信息;否則登錄成功;
3行拢、最后調(diào)用 Subject.logout 進行退出操作。
那么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為新建一個默認的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)建一個默認的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)建完畢以躯!
三、身份認證
首先看看Shiro核心邏輯的類圖:
后面的分析均是基于此類圖啄踊,下面進行詳細分析忧设。
代碼subject.login(token)即進行身份認證,首先看看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);
}
}
當只有一個Realm時吏砂,就執(zhí)行doSingleRealmAuthentication,當有多個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é)
身份認證流程如下:
1)如果沒有創(chuàng)建Subject缀匕,創(chuàng)建一個Subject;
2)調(diào)用 Subject.login(token)進行登錄碰逸,其會自動委托給 Security Manager乡小,調(diào)用之前必須通過 SecurityUtils. setSecurityManager()設(shè)置;
3)SecurityManager 負責真正的身份驗證邏輯;它會委托給 Authenticator 進行身份驗證;
4)Authenticator 才是真正的身份驗證者,Shiro API 中核心的身份認證入口點饵史,此處可以自定義插入自己的實現(xiàn);
5)Authenticator 可能會委托給相應(yīng)的 AuthenticationStrategy 進行多 Realm 身份驗證满钟,默認 ModularRealmAuthenticator 會調(diào)用 AuthenticationStrategy 進行多 Realm 身份驗證;
6)Authenticator 會把相應(yīng)的 token 傳入 Realm,從 Realm 獲取身份驗證信息胳喷,如果沒有返 回/拋出異常表示身份驗證失敗了湃番。此處可以配置多個 Realm,將按照相應(yīng)的順序及策略進 行訪問吭露。
Shiro身份認證分析到此為止吠撮,祝工作順利,天天開心讲竿!