SHIRO源碼解讀——Authenticator身份認(rèn)證

身份認(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核心邏輯的類圖:


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)證流程

身份認(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)證分析到此為止,祝工作順利,天天開心!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容