1. 程式人生 > >Shiro的學習(二)——Shiro認證

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