1. 程式人生 > 其它 >第四章:Shiro的身份認證(Authentication)——深入淺出學Shiro細粒度許可權開發框架

第四章:Shiro的身份認證(Authentication)——深入淺出學Shiro細粒度許可權開發框架

Authentication概述

概述

  Authentication 是指身份驗證的過程——即證明一個使用者實際上是不是他們所說的他們是誰。也就是說通過提交使用者的身份和憑證給Shiro,以判斷它們是否和應用程式預期的相匹配。

基本概念

1:Principals(身份):是Subject 的‘identifying attributes(標識屬性)’。比如我們登入提交的使用者名稱。

2:Credentials(憑證):通常是隻被Subject 知道的祕密值,它用來作為一種起支援作用的證據,此證據事實上包含著所謂的身份證明。比如我們登入提供的密碼

認證的基本步驟

1. 收集Subjects 提交的Principals(身份)和Credentials(憑證);

2. 提交Principals(身份)和Credentials(憑證)進行身份驗證;

3. 如果提交成功,則允許訪問,否則重新進行身份驗證或者阻止訪問。

認證樣例

使用使用者名稱/密碼的樣例

UsernamePasswordToken token = new UsernamePasswordToken(username, password);

token.setRememberMe(true);

  樣例使用UsernamePasswordToken 來支援最常見的使用者名稱/密碼的身份驗證方法。這是Shiro的org.apache.shiro.authc.AuthenticationToken 的介面,是Shiro 代表提交的Principals(身份)和Credentials(憑證)的身份驗證系統所使用的基本介面的一個實現。

提交使用者名稱/密碼進行認證

Subject currentUser = SecurityUtils.getSubject();

currentUser.login(token);

處理認證成功和失敗

  如果認證成功,會沒有返回,也沒有例外,通過。

  如果認證失敗,會丟擲例外,你可以在程式中捕獲並處理,如下示例:

try {
  currentUser.login(token);
} catch ( UnknownAccountException uae ) { …
} catch ( IncorrectCredentialsException ice ) { …
} catch (LockedAccountException lae ) { …
} catch (ExcessiveAttemptsException eae ) { …
} … catch your own …
 logout(登出)
  currentUser.logout();

  當你呼叫logout,任何現有的Session 都將會失效,而且任何身份都將會失去關聯(例如,在Web 應用程式中,RememberMe cookie 也將被刪除)。在Subject 登出後,該Subject的例項被再次認為是匿名的,當然,除了Web 應用程式。

  注意:由於在Web 應用程式記住身份往往是依靠Cookies,然而Cookies 只能在Response 被committed 之前被刪除,所以強烈建議在呼叫subject.logout()後立即將終端使用者重定向到一個新的檢視或頁面。

  這樣能夠保證任何與安全相關的Cookies 都能像預期的一樣被刪除。這是HTTP cookies 的功能限制,而不是Shiro的。

Remembered和Authenticated

nRemembered(記住我)

  一個記住我的Subject 不是匿名的,是有一個已知的身份ID(也就是subject.getPrincipals()是非空的)。但是這個被記住的身份ID 是在之前的session 中被認證的。如果subject.isRemembered()返回true,則Subject 被認為是被記住的。

Remembered(記住我)已認證)

  一個已認證的Subject 是指在當前Session 中被成功地驗證過了(也就是說,login方法被呼叫並且沒有丟擲異常)。如果subject.isAuthenticated()返回true 則認為Subject 已通過驗證。

注意他們是互斥的

  Remembered 和Authenticated 是互斥的——若其中一個為真則另一個為假,反之亦然

認證順序

Step 1:應用程式程式碼呼叫Subject.login 方法,傳遞建立好的包含終端使用者的Principals(身份)和Credentials(憑證)的AuthenticationToken 例項。

Step 2:Subject例項,通常是DelegatingSubject(或子類)委託應用程式的SecurityManager通過呼叫securityManager.login(token)開始真正的驗證。

nStep3:SubjectManager 接收token 以及簡單地委託給內部的Authenticator 例項通過呼叫authenticator.authenticate(token)。這通常是一個ModularRealmAuthenticator 例項,支援在身份驗證中協調一個或多個Realm 例項。

Step 4:如果應用程式中配置了一個以上的Realm,ModularRealmAuthenticator 例項將利用配置好的AuthenticationStrategy 來啟動Multi-Realm 認證嘗試。在Realms 被身份驗證呼叫之前,期間和以後,AuthenticationStrategy 被呼叫使其能夠對每個Realm 的結果作出反應。

Step 5:每個配置的Realm 用來幫助看它是否支援提交的AuthenticationToken。如果支援,那麼支援Realm 的getAuthenticationInfo 方法將會伴隨著提交的token 被呼叫。getAuthenticationInfo 方法有效地代表一個特定Realm 的單一的身份驗證嘗試。

初識自定義 Realm

這裡先來個例子,認識一下:

public class MyRealm extends AuthorizingRealm{

  protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {

  String userName = (String) getAvailablePrincipal(principals);

  //通過使用者名稱去獲得使用者的所有資源,並把資源存入info中

  SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();

  Set<String> s = new HashSet<String>();

  s.add("p1");  s.add("p2"); info.setStringPermissions(s);

  Set<String> r = new HashSet<String>();

  r.add("r1"); r.add("r2"); info.setRoles(r);

  return info;}

  protected AuthenticationInfo doGetAuthenticationInfo(

  AuthenticationToken token) throws AuthenticationException {

  //token中儲存著輸入的使用者名稱和密碼

  UsernamePasswordToken upToken = (UsernamePasswordToken)token;

  String username = upToken.getUsername();

  String password = String.valueOf(upToken.getPassword());

  //通常是與資料庫中使用者名稱和密碼進行比對,這裡就省略了

  //比對成功則返回info,比對失敗則丟擲對應資訊的異常AuthenticationException

  SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(username, password .toCharArray(),getName());

  return info;  }}

配置多個Realm

上面的例子可以作為第一個Realm

再複製一份,定義為MyRealm2,在返回user前新增丟擲一個例外,表示認真沒有通過,如下:

if(username.equals("javass")){

  throw new AuthenticationException("MyRealm2 認證失敗");

}

SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(username, password .toCharArray(),getName());

在配置檔案裡面新增Realm的定義

myRealm1=cn.javass.hello.MyRealm

myRealm2=cn.javass.hello.MyRealm2

由於有多個realm,一般就需要配置AuthenticationStrategy了,而AuthenticationStrategy是跟Authenticator(認證器)相關的。

最後把Authenticator設定給securityManagerategy

authenticator = org.apache.shiro.authc.pam.ModularRealmAuthenticator

authcStrategy = org.apache.shiro.authc.pam.AllSuccessfulStrategy

authenticator.authenticationStrategy = $authcStrategy

authenticator.realms=$myRealm2,$myRealm1

當然,你可以擴充套件並實現自己的Authenticator,一般沒有必要

後把Authenticator設定給securityManager

 securityManager.authenticator = $authenticator

關於AuthenticationStrategy的配置,有三種:

AtLeastOneSuccessfulStrategy :如果一個(或更多)Realm 驗證成功,則整體的嘗試被認為是成功的。如果沒有一個驗證成功,則整體嘗試失敗。

FirstSuccessfulStrategy 只有第一個成功地驗證的Realm 返回的資訊將被使用。所有進一步的Realm 將被忽略。如果沒有一個驗證成功,則整體嘗試失敗

AllSucessfulStrategy 為了整體的嘗試成功,所有配置的Realm 必須驗證成功。如果沒有一個驗證成功,則整體嘗試失敗。

ModularRealmAuthenticator 預設的是AtLeastOneSuccessfulStrategy

n自定義自己的AuthenticationStrategy,通常是擴充套件自AbstractAuthenticationStrategy,示例如下:

public class MyAuthenticationStrategy extends AbstractAuthenticationStrategy{
  public AuthenticationInfo afterAttempt(Realm realm, AuthenticationToken token, AuthenticationInfo singleRealmInfo, AuthenticationInfo aggregateInfo, Throwable t) throws AuthenticationException {
  if(realm.getName().equals("myRealm2")){
  if(singleRealmInfo==null || singleRealmInfo.getPrincipals()==null){
  throw new AuthenticationException("主戰認證未通過");
  }
  }
return super.afterAttempt(realm, token, singleRealmInfo, aggregateInfo, t);
  }
}

至於具體覆蓋擴充套件什麼方法,需要根據你具體的策略來定。

多個Realm的驗證順序

概述

  非常重要的一點是:ModularRealmAuthenticator 將與Realm 例項以迭代的順序進行互動。

  在SecurityManager 中已經配置好了ModularRealmAuthenticator 對Realm例項的訪問。當執行一個認證嘗試時,它將會遍歷該集合,並對每一個支援提交AuthenticationToken 的Realm 呼叫Realm 的getAuthenticationInfo 方法

隱式排列

  當你配置多個realm的時候,處理的順序預設就是你配置的順序。

  這種情況通常就是隻定義了realm,而沒有配置securityManager的realms

n顯示排列

  也就是顯示的配置securityManager.realms,那麼執行的順序就是你配置該值的realm的順序。

  通常更推薦顯示排列。