1. 程式人生 > >【Apache Shiro】學習筆記——Authentication基礎

【Apache Shiro】學習筆記——Authentication基礎

從Authentication一步步學習。 j_0003.gif

wKioL1N8fCLgEZtQAABxbR1PGeM813.jpg

先從程式碼開始,執行後再慢慢研究。

以下是我新增的dependecies:

<!-- shiro -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupId>
org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>${shiro.version}</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-web</artifactId> <version>${shiro.version}</version
>
</dependency>

在資源目錄下建立shiro.ini,檔案內容為:

[users]
king=t;stmdtkg
package pac.testcase.shiro;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.ExcessiveAttemptsException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.LockedAccountException;
import
org.apache.shiro.authc.UnknownAccountException; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.config.IniSecurityManagerFactory; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.subject.Subject; import org.apache.shiro.util.Factory; public class TestAuthen { public static void main(String[] args) { Factory<SecurityManager> factory = new IniSecurityManagerFactory(); SecurityManager manager = factory.getInstance(); SecurityUtils.setSecurityManager(manager); UsernamePasswordToken token = new UsernamePasswordToken("king", "t;stmdtkg"); token.setRememberMe(true); Subject currentUser = SecurityUtils.getSubject(); try { currentUser.login(token); } catch ( UnknownAccountException e ) { System.out.println("你是誰?"); } catch ( IncorrectCredentialsException e ) { System.out.println("密碼錯誤!!"); } catch ( LockedAccountException e ) { System.out.println("該賬戶不可用~"); } catch ( ExcessiveAttemptsException e ) { System.out.println("別再試了!!"); } currentUser.logout(); } }

程式碼非常好懂。

如果 IniSecurityManagerFactory 沒有指定配置檔案,則DEFAULT_INI_RESOURCE_PATH = "classpath:shiro.ini" 。

另外, SecurityUtils.setSecurityManager(manager);

該方法是一個全域性設定,設定整個VM單例的SecurityManager,一般開發中很少用到該方法。

另外,Shiro提供了豐富的Exception,我們可以根據不同的catch響應不同的提示。

關於身份驗證,有兩個重要的概念需要解釋:

・ Principals :身份(暫且這樣翻譯吧),Subject的唯一標識,可以是任何東西,比如使用者名稱或者e-mail地址。

・ Credentials :憑證,用於證明身份的東西,可以簡單理解為密碼。

一些類和方法的命名中如果出現這些詞彙不至於太陌生。

記錄一下身份驗證的具體步驟,用5個步驟簡單概括一下:

Step 1.

將代表使用者身份和憑證的token物件作為引數呼叫login方法。

Step 2.

在上面的程式碼中Subject物件實際上是作為一個用於委派(delegate)任務的物件――DelegatingSubject,以呼叫 login(token) 將驗證的任務委託給SecurityManager。

SecurityManager呼叫

    Subject login (Subject subject, AuthenticationToken authenticationToken)

這裡是驗證真正開始的地方。

Step 3.

作為一個'umbrella'元件(這個比喻很流行嗎...),接收token並將任務委託給內部的Authenticator(ps: SecurityManager extends henticator, Authorizer, SessionManager )並呼叫 authenticate (token) 方法。

通常情況下,這個authenticator是 ModularRealmAuthenticator ,ModularRealmAuthenticator可以在驗證時與多個realm進行交流。

Step 4.

如果配置了多個Realm,ModularRealmAuthenticator會用配置的驗證策略(AuthenticationStrategy )去進行多Realm驗證。驗證策略與每一個Realm的結果互動。

如果只配置了一個Realm,驗證策略則沒有任何意義。

Step 5.

呼叫 Realm 的 boolean supports (AuthenticationToken token) 確認Realm是否支援提交過來的token。

如果Realm支援提交過來的token,token將作為引數並呼叫:

    AuthenticationInfo getAuthenticationInfo (AuthenticationToken token)throws AuthenticationException ;

根據不同的token響應特定的驗證資訊(AuthenticationInfo)。

當一個應用配置了多個Realm,ModularRealmAuthenticator通過其內部的AuthenticationStrategy去定義驗證策略(好像說了句廢話....命名確實很棒...)。

舉個例子:

如果一個Realm成功驗證了一個token,而其他的Realm全部失敗了,此時我們應該如何認定驗證是成功還是失敗?

是全部通過才算成功?還是說其中一個通過即可?或者說其中特定幾個通過後是否有必要繼續考慮其他?

接下來,試著用程式碼體驗multi-Realm與AuthenticationStrategy。

Step 1.

實現org.apache.shiro.realm.Realm來自定義幾個Realm。

在getAuthenticationInfo方法中直接返回驗證資訊根本看不出驗證策略是否生效,於是我在一個Realm裡丟擲一個異常。

package pac.testcase.shiro.realm;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.realm.Realm;

public class MyRealm1 implements Realm {

public String getName() {
return this.getClass().getName();
}

public boolean supports(AuthenticationToken token) {
return true;
}

public AuthenticationInfo getAuthenticationInfo(AuthenticationToken token)
throws AuthenticationException {
if(!new String((char[])token.getCredentials()).equals("t;stmdtkg"))
throw new IncorrectCredentialsException();

return new SimpleAuthenticationInfo(token.getPrincipal(),token.getCredentials(),this.getName());
}
}

Step 2.

繼續使用本文開始時的程式碼,將自定義的幾個Realm配置到shiro.ini中。

realm0=pac.testcase.shiro.realm.MyRealm0
realm1=pac.testcase.shiro.realm.MyRealm1

Step 3.

在shiro.ini中配置驗證策略。

realm0=pac.testcase.shiro.realm.MyRealm0
realm1=pac.testcase.shiro.realm.MyRealm1

authcStrategy = org.apache.shiro.authc.pam.AllSuccessfulStrategy
securityManager.authenticator.authenticationStrategy = $authcStrategy

Step 4.

執行程式,輸出"密碼錯誤!!"

AuthenticationStrategy 是無狀態的,處理一個驗證請求時AuthenticationStrategy會一共互動4次。

分別是:

・任何一個Realm執行之前。

・每個Realm的getAuthenticationInfo方法被呼叫之前。

・每個Realm的getAuthenticationInfo方法被呼叫之後。

・所有的Realm執行之後。

另外,從所有成功的Realm中聚集結果並將其繫結到一個AuthenticationInfo也是AuthenticationStrategy的工作。

這個最後聚集起來的驗證資訊則是Shiro用來表示Subject的標誌,也就是所謂Principal。

具體體現在 org.apache.shiro.authc.pam.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());
}

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 {
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;
}

Shiro預設提供了三種 AuthenticationStrategy 實現:

・ AtLeastOneSuccessfulStrategy :其中一個通過則成功。

・ FirstSuccessfulStrategy :其中一個通過則成功,但只返回第一個通過的Realm提供的驗證資訊。

・ AllSuccessfulStrategy :凡是配置到應用中的Realm都必須全部通過。

ModularRealmAuthenticator預設採用AtLeastOneSuccessfulStrategy,如果想用其他的驗證策略則需要自行配置。


轉自:http://www.tuicool.com/articles/MVFRr2N