談談Shiro的原理及在SSM和SpringBoot兩種環境下的使用姿勢(上篇)

FuyunWang發表於2017-12-16

title: 談談Shiro的原理及在SSM和SpringBoot兩種環境下的使用姿勢(上篇) categories:

  • Spring tags:
  • Shiro使用

本篇主要是記錄關於Shiro進行認證和授權的大致原理,然後是單獨在Shiro中實現認證和授權的方式。最後主要說明在傳統SSM的工程中使用Shiro和在SpringBoot的工程中使用Shiro進行整合。關於認證和授權,我這裡採用的是規範的RBAC許可權模型,資料庫的建表語句已經託管github的工程中。

在進行Shiro具體認證和授權的流程介紹之前,首先說一下Shiro中幾個比較重要的概念(其中的介面或者類)。

  1. Subject:含義為主體。Subject作為使用者端(使用Shiro進行授權的一端)的抽象,在Shiro中是通過一個介面來體現的。在使用Shiro的時候,我們也就是通過呼叫Subject的認證和授權的方法來實現具體的認證和授權的。

  2. SecurityManager:含義為安全管理器。SecurityManager是整個Shiro的核心所在,它負責對所有的Subject進行安全管理,我們在通過Subject進行授權和認證的時候,Subject其實是通過SecurityManager來實現具體業務邏輯的。SecurityManager在Shiro中是通過一個介面來體現的,而且它繼承了Authenticator,Authorizer,SessionManager。如下圖:

    Aaron Swartz
    這樣SecurityManager的認證會交給Authenticator定義的業務邏輯完成,授權會交給Authorizer定義的業務邏輯完成,會話管理會交給SessionManager來完成。

  3. Authenticator:含義為認證器,主要是完成對使用者身份的認證。Authenticator在Shiro是一個介面,在Shiro中提供了一個ModularRealmAuthenticator的實現類用於完成認證。ModularRealmAuthenticator已經可以完成大多數的認證需求,如果我們有新的業務,那麼我們通過自定義認證器來完成特殊的業務。

  4. Authorizer:含義為授權器,主要是完成對使用者操作的授權。Authorizer在Shiro中是一個介面,相比Authenticator,Shiro提供了更多的授權器的實現類,其中也包括類似的ModularRealmAuthorizer。這些預設的實現類可以完成大多數需求,如果我們有新的業務,那麼我們通過自定義授權器來完成特殊的業務。

  5. realm:含義為領域。Realm在Shiro中也是一個介面,SecurityManager進行認證和授權的時候,它所需要的資料資訊都是從Realm中獲取的。Realm有多種實現類,代表了Realm可以從多種資料來源中讀取已經配置的資料資訊用於認證和授權。Realm在Shiro中是一個相當關鍵的部分,因為我們的授權器和認證器最終都是通過Realm來實現各自的業務的。

  6. SessionDAO:含義為Session會話.在Shiro中也是作為一個介面來體現的。sessiondao可以實現將session資料持久化。

  7. CacheManager:含義為快取管理器。使用者的認證和授權的資訊可以快取到CacheManager中,從而提升資料的訪問效能。

  8. Cryptography:含義為密碼管理。shiro提供了Cryptography來作為我們資訊的加密和介面的工具。

ok,在說完了Shiro中幾個比較關鍵的概念之後,我們開始看一下在Shiro中是如何進行的認證和授權的。

注:所有的sql都包含在了工程中

程式碼地址: github.com/fuyunwang/S… 認證:

下面這張圖說明了Shiro中進行認證的大致流程。

Aaron Swartz

可以看到,Shiro最終其實通過Realm來完成最終的認證。我們上面也已經提到,Realm其實作為一種資料來源的地位存在,其包含多個實現類代表著從不同的資料來源中進行資料資訊的獲取。我這裡通過使用其中一個實現類IniRealm來實現最簡單的認證流程。(具體程式碼在v0.1tag下。)

	Factory<SecurityManager> factory=new IniSecurityManagerFactory("classpath:inirealm-shiro.ini");
	
	SecurityManager securityManager = factory.getInstance();
	
	SecurityUtils.setSecurityManager(securityManager);
	
	Subject subject = SecurityUtils.getSubject();
	
	UsernamePasswordToken token=new UsernamePasswordToken("beautifulsoup", "password");
	
	try{
		subject.login(token);
	}catch(AuthenticationException e){
		e.printStackTrace();
	}
複製程式碼

介紹完使用inirealm來完成從ini配置檔案中獲取資料之後,我們做一個自定義Realm,來完成從資料庫這一資料來源中獲取資料。 自定義Realm一般採用繼承自AuthorizingRealm的方式,然後重寫其中的認證和授權的方法,核心程式碼如下,完整程式碼在github。

	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(
			AuthenticationToken token) throws AuthenticationException {
		String username=(String) token.getPrincipal();
		ShiroDemoMapper mapper=getShiroMapper();
		User user = mapper.findByUsername(username);
		if(null!=user){
			SimpleAuthenticationInfo authenticationInfo=new SimpleAuthenticationInfo(username, user.getPassword(),TAG);
			return authenticationInfo;
		}
		return null;
	}
複製程式碼

然後進行配置:

	[main]
	#進行自定義realm的配置
	customRealm=com.beautifulsoup.shiro.demo.realm.ShiroDemoRealm
	securityManager.realms=$customRealm
複製程式碼

這樣我們自定義的realm就會生效了,我們可以實現從資料庫中獲取資料,然後校驗我們主體subject的資訊,從而實現判斷是否認證成功的功能。 這裡我們認證的時候,在資料庫中採用明文存取的密碼,這當然是不合理的,所以通常情況下,我們會採用加鹽(salt)的方式,使用雜湊演算法如MD5對我們原有的密碼進行加密然後存入資料庫中。(改進之後的程式碼在v0.2標籤下)

	首先修改配置檔案,定義雜湊演算法和雜湊次數等:
	[main]
	credentialsMatcher=org.apache.shiro.authc.credential.HashedCredentialsMatcher
	credentialsMatcher.hashAlgorithmName=md5
	credentialsMatcher.hashIterations=3
	#進行自定義realm的配置
	customRealm=com.beautifulsoup.shiro.demo.realm.ShiroDemoRealm
	customRealm.credentialsMatcher=$credentialsMatcher
	securityManager.realms=$customRealm
	然後修改realm
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(
			AuthenticationToken token) throws AuthenticationException {
		String username=(String) token.getPrincipal();
		ShiroDemoMapper mapper=getShiroMapper();
		User user = mapper.findByUsername(username);
		if(null!=user){
			SimpleAuthenticationInfo authenticationInfo=new SimpleAuthenticationInfo(username, user.getPassword(),
					ByteSource.Util.bytes(user.getSalt()),TAG);
			return authenticationInfo;
		}
		return null;
	}
複製程式碼

好,說完認證我們接下來說授權:

授權:

同樣,下面這張圖說明了在Shiro中進行授權的大致流程。

Aaron Swartz

可以看到,SecurityManager最終交給Realm進行授權,實際上Realm是會返回一個ModularRealmAuthorizer類,該類得到所有的系統配置的許可權然後呼叫PermissionResolver進行了許可權的匹配。

接上所講,我們還是使用ini的配置檔案來配置shiro實現授權,主要是配置檔案更加方便我們的管理。

這裡我們的許可權資訊定義在配置檔案中,畢竟我們的許可權資訊大多數是固定的,而且對於許可權不多的情況下,這種方式更簡單。對於授權的操作主要包括針對角色的授權和針對資源的授權兩種方式,由於基於角色的許可權控制不如基於資源的許可權控制更加靈活,所以我們採用基於資源的許可權控制為例來介紹。

配置檔案進行配置的方式如下(程式碼在v0.3標籤):

		[users]
		#使用者beautifulsoup具有role1和role3的角色
		beautifulsoup=password,role1,role3
		[roles]
		#許可權role1具有對01使用者訂單的建立許可權和對02訂單資源的修改許可權和對所有訂單的查詢操作。
		role1=item:create:01,item:update:02,item:query
		role2=item:*:01,item:update:02
		role3=item:create:02,item:delete:02
複製程式碼

基本許可權的驗證:

		@Test
		public void testIniAuthorization(){
			Factory<SecurityManager> factory=new IniSecurityManagerFactory("classpath:permission-shiro.ini");
			SecurityManager securityManager = factory.getInstance();
			SecurityUtils.setSecurityManager(securityManager);
			Subject subject = SecurityUtils.getSubject();
			//首先認證,認證通過之後才能授權
			UsernamePasswordToken token=new UsernamePasswordToken("beautifulsoup", "password");
			try{
				subject.login(token);
			}catch(AuthenticationException e){
				e.printStackTrace();
			}
			System.out.println("使用者的認證狀態:"+subject.isAuthenticated());
			boolean isPermitted=subject.isPermittedAll("item:create:01","item:query");
			subject.checkPermissions("item:create:01","item:query");
			System.out.println(isPermitted);
		}
複製程式碼

接下來使用自定義realm來實現使用者的授權。 在認證中已經提到繼承自AuthorizingRealm,其提供了兩個方法,我們現在用第二個方法來實現授權的邏輯。(程式碼在v0.4標籤)

		@Override
		protected AuthorizationInfo doGetAuthorizationInfo(
				PrincipalCollection principals) {
			//得到認證成功之後憑證的身份資訊
			String username=(String) principals.getPrimaryPrincipal();
			//查詢資料庫得到所有的許可權列表
			List<String> permissionList=new ArrayList<String>();
			UserCustomMapper mapper=getUserCustomMapper();
			UserCustom userCustom = mapper.findUserCustomByUsername(username);
			Set<RoleCustom> roles=userCustom.getRoleSet();
			for(RoleCustom role:roles){
				Set<Permission> permissionSet = role.getPermissionSet();
				for (Permission permission:permissionSet) {
					permissionList.add(permission.getPname());
				}
			}
			SimpleAuthorizationInfo authorizationInfo=new SimpleAuthorizationInfo();
			authorizationInfo.addStringPermissions(permissionList);
			return authorizationInfo;
		}		
		同樣我們也需要配置:
		[main]
		credentialsMatcher=org.apache.shiro.authc.credential.HashedCredentialsMatcher
		credentialsMatcher.hashAlgorithmName=md5
		credentialsMatcher.hashIterations=3
		#進行自定義realm的配置
		customRealm=com.beautifulsoup.shiro.demo.realm.ShiroDemoRealm
		customRealm.credentialsMatcher=$credentialsMatcher
		securityManager.realms=$customRealm
複製程式碼

OK,到現在為止上篇已經對Shiro所有認證和授權的基礎知識做過了介紹,下篇開始對SSM和SpringBoot中的Shiro的使用進行整合。 程式碼地址: github.com/fuyunwang/S…

如果對您有過幫助,感謝您的一個star。

相關文章