Shiro的學習(二)——Shiro認證
一、環境
使用maven進行管理,pom.xml檔案:
<dependencies> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.2.3</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-web</artifactId> <version>1.2.3</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.2.3</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-ehcache</artifactId> <version>1.2.3</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-quartz</artifactId> <version>1.2.3</version> </dependency> <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.1.3</version> </dependency> <!-- 單元測試 --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.2</version> <scope>test</scope> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.2</version> </dependency> </dependencies>
也可以直接:
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-all</artifactId>
<version>1.2.3</version>
</dependency>
通過Shiro.ini配置檔案初始化SecurityManager環境。
為了方便測試將使用者名稱和密碼配置在shiro.ini配置檔案中:
[users]
zhang=123
lisi=123
二、測試用例:
/** * 使用者登入退出 */ @Test public void test1() { // 建SecurityManager工廠,IniSecurityManagerFactory可以從ini檔案中初始化SecurityManager環境 IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro.ini"); // 通過工廠建立SecurityManager SecurityManager securityManager = factory.getInstance(); // 將securityManager設定到執行環境中 SecurityUtils.setSecurityManager(securityManager); // 建立一個Subject例項,該例項認證要使用上邊建立的securityManager進行 Subject subject = SecurityUtils.getSubject(); // 得到token令牌,記錄使用者認證的身份和憑證即賬號和密碼 UsernamePasswordToken token = new UsernamePasswordToken("zhang", "123"); try { // 使用者登陸 subject.login(token); } catch (AuthenticationException e) { e.printStackTrace(); } // 使用者認證狀態 Boolean isAuthenticated = subject.isAuthenticated(); System.out.println("使用者認證狀態:" + isAuthenticated); // 使用者退出 subject.logout(); isAuthenticated = subject.isAuthenticated(); System.out.println("使用者認證狀態:" + isAuthenticated); }
執行流程
Ⅰ首先通過new IniSecurityManagerFactory並指定一個ini配置檔案來建立一個SecurityManager工廠;
Ⅱ接著獲取SecurityManager並繫結到SecurityUtils,這是一個全域性設定,設定一次即可;
Ⅲ通過SecurityUtils得到Subject,其會自動繫結到當前執行緒;如果在web環境在請求結束時需要解除繫結;然後獲取身份驗證的Token,如使用者名稱/密碼
Ⅳ呼叫subject.login方法進行登入,其會自動委託給SecurityManager.login方法進行登入;
Ⅴ如果身份驗證失敗請捕獲AuthenticationException或其子類,常見的如: DisabledAccountException(禁用的帳號)、LockedAccountException(鎖定的帳號)、UnknownAccountException(錯誤的帳號)、ExcessiveAttemptsException(登入失敗次數過多)、IncorrectCredentialsException (錯誤的憑證)、ExpiredCredentialsException(過期的憑證)等,具體請檢視其繼承關係;對於頁面的錯誤訊息展示,最好使用如“使用者名稱/密碼錯誤”而不是“使用者名稱錯誤”/“密碼錯誤”,防止一些惡意使用者非法掃描帳號庫;
Ⅵ最後可以呼叫subject.logout退出,其會自動委託給SecurityManager.logout方法退出。
小結:
從如上程式碼可總結出身份驗證的步驟:
1、收集使用者身份/憑證,即如使用者名稱/密碼;
2、呼叫Subject.login進行登入,如果失敗將得到相應的AuthenticationException異常,根據異常提示使用者錯誤資訊;否則登入成功;
3、最後呼叫Subject.logout進行退出操作。
如上測試的幾個問題:
1、使用者名稱/密碼硬編碼在ini配置檔案,以後需要改成如資料庫儲存,且密碼需要加密儲存;
2、使用者身份Token可能不僅僅是使用者名稱/密碼,也可能還有其他的,如登入時允許使用者名稱/郵箱/手機號同時登入
認證流程:
流程如下:
1、首先呼叫Subject.login(token)進行登入,其會自動委託給Security Manager,呼叫之前必須通過SecurityUtils. setSecurityManager()設定;
2、SecurityManager負責真正的身份驗證邏輯;它會委託給Authenticator進行身份驗證;
3、Authenticator才是真正的身份驗證者,Shiro API中核心的身份認證入口點,此處可以自定義插入自己的實現;
4、Authenticator可能會委託給相應的AuthenticationStrategy進行多Realm身份驗證,預設ModularRealmAuthenticator會呼叫AuthenticationStrategy進行多Realm身份驗證;
5、Authenticator會把相應的token傳入Realm,從Realm獲取身份驗證資訊,如果沒有返回/丟擲異常表示身份驗證失敗了。此處可以配置多個Realm,將按照相應的順序及策略進行訪問。 此處目前使用配置檔案代替,後期改為資料庫!
三、Realm
Realm:域,Shiro從從Realm獲取安全資料(如使用者、角色、許可權),就是說SecurityManager要驗證使用者身份,那麼它需要從Realm獲取相應的使用者進行比較以確定使用者身份是否合法;也需要從Realm得到使用者相應的角色/許可權進行驗證使用者是否能進行操作;可以把Realm看成DataSource,即安全資料來源。如我們之前的ini配置方式將使用org.apache.shiro.realm.text.IniRealm。
最基礎的是Realm介面,CachingRealm負責快取處理,AuthenticationRealm負責認證,AuthorizingRealm負責授權,通常自定義的realm繼承AuthorizingRealm。
自定義Realm
public class CustomRealm1 extends AuthorizingRealm {
// 返回一個唯一的Realm名字
@Override
public String getName() {
return "customRealm1";
}
// 根據Token獲取認證資訊
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String username = (String) token.getPrincipal(); // 得到使用者名稱
String password = new String((char[]) token.getCredentials()); // 得到密碼
if (!"zhang".equals(username)) { //模擬從資料庫查詢 沒有這個賬號
throw new UnknownAccountException(); // 如果使用者名稱錯誤
}
if (!"123".equals(password)) { //模擬查詢出來的的密碼不是 123
throw new IncorrectCredentialsException(); // 如果密碼錯誤
}
// 如果身份認證驗證成功,返回一個AuthenticationInfo實現;
return new SimpleAuthenticationInfo(username, password, getName());
}
// 用於授權
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection arg0) {
return null;
}
}