shiro(三)

一,shiro架構


     1)subject

            Subject即主體,外部應用與subject進行交互,subject記錄了當前操作用戶,將用戶的概念理解爲當前操作的主體,可能是一個通過瀏覽器請求的用戶,也可能是一個運行的程序。 Subjectshiro中是一個接口,接口中定義了很多認證授相關的方法,外部程序通過subject進行認證授,而subject是通過SecurityManager安全管理器進行認證授權

 2)securityManager

SecurityManager即安全管理器,對全部的subject進行安全管理,它是shiro的核心,負責對所有的subject進行安全管理。通過SecurityManager可以完成subject的認證、授權等,實質上SecurityManager是通過Authenticator進行認證,通過Authorizer進行授權,通過SessionManager進行會話管理等。

SecurityManager是一個接口,繼承了Authenticator, Authorizer, SessionManager這三個接口。

  3)AuthenticatorAuthenticator即認證器,對用戶身份進行認證,Authenticator是一個接口,shiro提供ModularRealmAuthenticator實現類,通過ModularRealmAuthenticator基本上可以滿足大多數需求,也可以自定義認證器。

  4)AuthorizerAuthorizer即授權器,用戶通過認證器認證通過,在訪問功能時需要通過授權器判斷用戶是否有此功能的操作權限。

  5)realm:Realm即領域,相當於datasource數據源,securityManager進行安全認證需要通過Realm獲取用戶權限數據,比如:如果用戶身份數據在數據庫那麼realm就需要從數據庫獲取用戶身份信息。

注意:不要把realm理解成只是從數據源取數據,在realm中還有認證授權校驗的相關的代碼。

  6)sessionManagersessionManager即會話管理,shiro框架定義了一套會話管理,它不依賴web容器的session,所以shiro可以使用在非web應用上,也可以將分佈式應用的會話集中在一點管理,此特性可使它實現單點登錄。

  7)SessionDAOSessionDAO即會話dao,是對session會話操作的一套接口,比如要將session存儲到數據庫,可以通過jdbc將會話存儲到數據庫。

 8)CacheManager:CacheManager即緩存管理,將用戶權限數據存儲在緩存,這樣可以提高性能。

 9)Cryptography:Cryptography即密碼管理,shiro提供了一套加密/解密的組件,方便開發。比如提供常用的散列、加/解密等功能。

二,shiro的jar包

與其它java開源框架類似,將shirojar包加入項目就可以使用shiro提供的功能了。shiro-core是核心包必須選用,還提供了與web整合的shiro-web、與spring整合的shiro-spring、與任務調度quartz整合的shiro-quartz等,下邊是shirojar包的maven座標。 

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

 也可以通過引入shiro-all包括shiro所有的包:

<dependency>

<groupId>org.apache.shiro</groupId>

<artifactId>shiro-all</artifactId>

<version>1.2.3</version>

</dependency>

三,shiro認證


四,代碼示例

   1)      1、首先準備一些用戶身份/憑據(shiro.ini)

[users]
zhangsan=123
lisi=22222

     2.登錄退出

// 用戶登陸、用戶退出
	@Test
	public void testLoginLogout() {

		// 構建SecurityManager工廠,IniSecurityManagerFactory可以從ini文件中初始化SecurityManager環境
		Factory<SecurityManager> 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) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		// 用戶認證狀態

		Boolean isAuthenticated = subject.isAuthenticated();

		System.out.println("用戶認證狀態:" + isAuthenticated);

		// 用戶退出

		subject.logout();

		isAuthenticated = subject.isAuthenticated();

		System.out.println("用戶認證狀態:" + isAuthenticated);

	}

2)執行流程

   1、 創建token令牌,token中有用戶提交的認證信息即賬號和密碼

   2、 執行subject.login(token),最終由securityManager通過Authenticator進行認證

   3、 Authenticator的實現ModularRealmAuthenticator調用realmini配置文件取用戶真實的賬號和密碼,這裏使用的是IniRealmshiro自帶)

   4、 IniRealm先根據token中的賬號去ini中找該賬號,如果找不到則給ModularRealmAuthenticator返回null,如果找到則匹配密碼,匹配密碼成功則認證通過。

  3)常見的異常

UnknownAccountException 賬號不存在異常如下:

org.apache.shiro.authc.UnknownAccountException: No account found for user。。。。

IncorrectCredentialsException 當輸入密碼錯誤會拋此異常,如下:

org.apache.shiro.authc.IncorrectCredentialsException: Submitted credentials for token [org.apache.shiro.authc.UsernamePasswordToken - zhangsan, rememberMe=false] did not match the expected credentials.

DisabledAccountException(帳號被禁用)

LockedAccountException(帳號被鎖定)

ExcessiveAttemptsException(登錄失敗次數過多)

ExpiredCredentialsException(憑證過期)等

五,自定義realm


package com.example.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.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;


public class CustomRealm extends AuthorizingRealm {

	// 設置realm的名稱
	@Override
	public void setName(String name) {
		super.setName("customRealm");
	}

	// 用於認證
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(
			AuthenticationToken token) throws AuthenticationException {

		// token是用戶輸入的
		// 第一步從token中取出身份信息
		String userCode = (String) token.getPrincipal();
		//String username = (String)token.getPrincipal();  //得到用戶名  
	   // String password = new String((char[])token.getCredentials()); //得到密碼 

		// 第二步:根據用戶輸入的userCode從數據庫查詢
		// ....
	

		// 如果查詢不到返回null
		//數據庫中用戶賬號是zhangsansan
		/*if(!userCode.equals("zhangsansan")){//
			return null;
		}*/
		
		
		// 模擬從數據庫查詢到密碼
		String password = "111112";

		// 如果查詢到返回認證信息AuthenticationInfo

		SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(
				userCode, password, this.getName());

		return simpleAuthenticationInfo;
	}

	// 用於授權
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(
			PrincipalCollection principals) {
		// TODO Auto-generated method stub
		return null;
	}

}

            ini配置文件指定自定義Realm實現(shiro-realm.ini)  

#聲明一個realm  
customRealm=com.example.shiro.realm.CustomRealm
 #指定securityManager的realms實現  
securityManager.realms=$customRealm
  3測試

// 自定義realm
	@Test
	public void testCustomRealm() {

		// 創建securityManager工廠,通過ini配置文件創建securityManager工廠
		Factory<SecurityManager> factory = new IniSecurityManagerFactory(
				"classpath:shiro-realm.ini");

		// 創建SecurityManager
		SecurityManager securityManager = factory.getInstance();

		// 將securityManager設置當前的運行環境中
		SecurityUtils.setSecurityManager(securityManager);

		// 從SecurityUtils裏邊創建一個subject
		Subject subject = SecurityUtils.getSubject();

		// 在認證提交前準備token(令牌)
		// 這裏的賬號和密碼 將來是由用戶輸入進去
		UsernamePasswordToken token = new UsernamePasswordToken("zhangsan",
				"111111");

		try {
			// 執行認證提交
			subject.login(token);
		} catch (AuthenticationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		// 是否認證通過
		boolean isAuthenticated = subject.isAuthenticated();

		System.out.println("是否認證通過:" + isAuthenticated);
		subject.logout();
		System.out.println("是否認證通過:" + subject.isAuthenticated());
	}

2)配置多個realm

#聲明一個realm  
myRealm1=com.github.zhangkaitao.shiro.chapter2.realm.MyRealm1  
myRealm2=com.github.zhangkaitao.shiro.chapter2.realm.MyRealm2  
#指定securityManager的realms實現  
securityManager.realms=$myRealm1,$myRealm2   

六,散列算法

   自定義realm

package com.example.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.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;

public class CustomRealmMd5 extends AuthorizingRealm {

	// 設置realm的名稱
	@Override
	public void setName(String name) {
		super.setName("customRealmMd5");
	}

	// 用於認證
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(
			AuthenticationToken token) throws AuthenticationException {

		// token是用戶輸入的
		// 第一步從token中取出身份信息
		String userCode = (String) token.getPrincipal();

		// 第二步:根據用戶輸入的userCode從數據庫查詢
		// ....

		// 如果查詢不到返回null
		// 數據庫中用戶賬號是zhangsansan
		/*
		 * if(!userCode.equals("zhangsansan")){// return null; }
		 */

		// 模擬從數據庫查詢到密碼,散列值
		String password = "36f2dfa24d0a9fa97276abbe13e596fc";
		// 從數據庫獲取salt
		String salt = "qwerty";
		//上邊散列值和鹽對應的明文:111111

		// 如果查詢到返回認證信息AuthenticationInfo
		SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(
				userCode, password, ByteSource.Util.bytes(salt), this.getName());

		return simpleAuthenticationInfo;
	}

	// 用於授權
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(
			PrincipalCollection principals) {
		// TODO Auto-generated method stub
		return null;
	}

}

  2)配置文件(shiro-realm-md5.ini)

[main]
#定義憑證匹配器
credentialsMatcher=org.apache.shiro.authc.credential.HashedCredentialsMatcher
#散列算法
credentialsMatcher.hashAlgorithmName=md5
#散列次數
credentialsMatcher.hashIterations=2

#將憑證匹配器設置到realm
customRealm=com.example.shiro.realm.CustomRealmMd5
customRealm.credentialsMatcher=$credentialsMatcher
securityManager.realms=$customRealm

3)測試

	// 自定義realm實現散列值匹配
	@Test
	public void testCustomRealmMd5() {

		// 創建securityManager工廠,通過ini配置文件創建securityManager工廠
		Factory<SecurityManager> factory = new IniSecurityManagerFactory(
				"classpath:shiro-realm-md5.ini");

		// 創建SecurityManager
		SecurityManager securityManager = factory.getInstance();

		// 將securityManager設置當前的運行環境中
		SecurityUtils.setSecurityManager(securityManager);

		// 從SecurityUtils裏邊創建一個subject
		Subject subject = SecurityUtils.getSubject();

		// 在認證提交前準備token(令牌)
		// 這裏的賬號和密碼 將來是由用戶輸入進去
		UsernamePasswordToken token = new UsernamePasswordToken("zhangsan",
				"111111");

		try {
			// 執行認證提交
			subject.login(token);
		} catch (AuthenticationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		// 是否認證通過
		boolean isAuthenticated = subject.isAuthenticated();

		System.out.println("是否認證通過:" + isAuthenticated);

	}







發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章