1. 程式人生 > 其它 >第五章:Shiro的授權(Authorization)——深入淺出學Shiro細粒度許可權開發框架

第五章:Shiro的授權(Authorization)——深入淺出學Shiro細粒度許可權開發框架

Authorization概述

概述

  授權,又稱作為訪問控制,是對資源的訪問管理的過程。換句話說,控制誰有許可權在應用程式中做什麼。

  授權檢查的例子是:該使用者是否被允許訪問這個網頁,編輯此資料,檢視此按鈕,或列印到這臺印表機?這些都是決定哪些是使用者能夠訪問的。

n授權的三要素

  授權有著三個核心元素:許可權、角色和使用者 。

  我們需要在應用程式中對使用者和許可權建立關聯,通常的做法就是將許可權分配給某個角色,然後將這個角色關聯一個或多個使用者。

許可權

  是Shiro安全機制最核心的元素。它在應用程式中明確聲明瞭被允許的行為和表現。一個格式良好的許可權宣告可以清晰表達出使用者對該資源擁有的許可權。

許可權宣告和粒度

  在shiro中主要通過前面學過的萬用字元表示式來完成。

角色

  角色是一個命名的實體,通常代表一組行為或職責。這些行為演化為你在一個軟體應用中能或者不能做的事情。角色通常是分配給使用者帳戶的,因此,通過分配,使用者能夠“做”的事情可以歸屬於各種角色。

Shiro支援的角色型別

1:隱式角色:一個角色代表著一系列的操作,當需要對某一操作進行授權驗證時,只需判斷是否是該角色即可。這種角色許可權相對簡單、模糊,不利於擴充套件。

2:顯式角色:一個角色擁有一個許可權的集合。授權驗證時,需要判斷當前角色是否擁有該許可權。這種角色許可權可以對該角色進行詳細的許可權描述,適合更復雜的許可權設計。 Shiro官方推薦使用這種方式。

Shiro的三種授權方式

1:編寫程式碼——在Java 程式碼中用像if 和else 塊的結構執行授權檢查。

2:JDK 的註解——你可以新增授權註解給你的Java 方法。

3:JSP/GSP 標籤庫——你可以控制基於角色和許可權的JSP 或者GSP 頁面輸出。

程式設計授權

通過使用subject的方法來實現角色的判斷,常見的api:

hasRole(String roleName) :返回true 如果Subject 被分配了指定的角色

hasRoles(List<String> roleNames) :返回一個與方法引數中目錄一致的hasRole 結果的陣列。

hasAllRoles(Collection<String> roleNames):返回true 如果Subject 被分配了所有的角色

斷言支援

  Shiro還支援以斷言的方式進行授權驗證。斷言成功,不返回任何值,程式繼續執行;斷言失敗時,將丟擲異常資訊。方法大致如下:

checkRole(String roleName) 、checkRoles(Collection<String>roleNames)、checkRoles(String… roleNames)

基於許可權物件的實現

Permission printPermission = new PrinterPermission("laser400n", "print");

相關方法:isPermitted(Permission p)、isPermitted(List<Permission> perms)、isPermittedAll(Collection<Permission> perms)  

基於字串的實現

if (currentUser.isPermitted("printer:print:laserjet4400n"))

相關方法:isPermitted(String perm)、isPermitted(String... perms)、isPermittedAll(String... perms)

當然上述許可權的實現也都可以採用斷言的方式

  相關方法:

checkPermission(Permission p)

checkPermission(String perm)

checkPermissions(Collection<Permission> perms)

checkPermissions(String... perms)

基於註解的授權

需要有AOP框架的支援,這裡選擇spring,先看看怎麼整合配置,看例子:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:context="http://www.springframework.org/schema/context"
  xmlns:aop="http://www.springframework.org/schema/aop"
  xmlns:tx="http://www.springframework.org/schema/tx"
  xsi:schemaLocation="
  http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
  http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
  http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
  http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">
      <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
      <context:component-scan base-package="cn.javass"></context:component-scan>     
      <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
  depends-on="lifecycleBeanPostProcessor" >
  <property name="proxyTargetClass" value="true"/>
  </bean>
  <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
  <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
  <property name="securityManager" ref="securityManager" />
  </bean>
  <bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
  <property name="staticMethod" value="org.apache.shiro.SecurityUtils.setSecurityManager"/>
  <property name="arguments" ref="securityManager"/>
  </bean>
      <bean id="securityManager" class="org.apache.shiro.mgt.DefaultSecurityManager"> 
      <property name="cacheManager" ref="cacheManager"/> 
          <property name="realm" ref="myRealm"/> 
          <property name="sessionManager" ref="sessionManager"/>  
  </bean>
  <bean id="sessionManager" class="org.apache.shiro.session.mgt.DefaultSessionManager">
  </bean>
  <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager" />
  <bean id="myRealm" class="org.apache.shiro.realm.text.IniRealm">
  <property name="resourcePath" value="D:/wp/src/TestShiro.ini"></property> 
  </bean>
</beans>

測試用的HelloAnno

@Service
public class HelloAnno {
  @Autowired
  private org.apache.shiro.mgt.SecurityManager sm = null;
  @RequiresAuthentication
  @RequiresPermissions({"p1"})
  public void t(){
  System.out.println("ok=========");
  }
  public void login(){
  UsernamePasswordToken token = new UsernamePasswordToken("javass","cc"); 
  token.setRememberMe(true);
  SecurityUtils.setSecurityManager(sm);
  Subject currentUser = SecurityUtils.getSubject(); 
  currentUser.login(token);
  }
  public static void main(String[] args) {
   ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
   HelloAnno t = (HelloAnno)ctx.getBean("helloAnno"); 
   t.login();
   t.t();
  }
}

Shiro提供的註解

1:@RequiresAuthentication :要求當前Subject 已經在當前的session 中被驗證通過才能被註解的類/例項/方法訪問或呼叫。

2:@RequiresGuest :要求當前的Subject 是一個“guest”,也就是他們必須是在之前的session中沒有被驗證或記住才能被註解的類/例項/方法訪問或呼叫。

3:@RequiresPermissions:要求當前的Subject 被允許一個或多個許可權,以便執行註解的方法,比如:@RequiresPermissions("account:create")

4:@RequiresRoles:要求當前的Subject 擁有所有指定的角色。如果他們沒有,則該方法將不會被執行,而且AuthorizationException 異常將會被丟擲。比如:@RequiresRoles("administrator")

5:@RequiresUser:需要當前的Subject 是一個應用程式使用者才能被註解的類/例項/方法訪問或呼叫。要麼是通過驗證被確認,或者在之前session 中的'RememberMe'服務被記住。

授權的順序

Step 1:應用程式或框架程式碼呼叫任何Subject的hasRole*, checkRole*, isPermitted*,或者checkPermission*方法的變體,傳遞任何所需的許可權或角色

Step 2:Subject的例項,通常是DelegatingSubject(或子類)代表應用程式的SecurityManager 通過呼叫securityManager的幾乎各自相同的方法。

Step 3:SecurityManager,實現org.apache.shiro.authz.Authorizer 介面,他定義了所有Subject 具體的授權方法 。預設情況下,authorizer 例項是一個ModularRealmAuthorizer 例項,它支援協調任何授權操作過程中的一個或多個Realm 例項。

Step 4:每個配置好的Realm 被檢查是否實現了相同的Authorizer介面。如果是,Realm 各自的hasRole*, checkRole*,isPermitted*,或checkPermission*方法將被呼叫。

理解ModularRealmAuthorizer

ModularRealmAuthorizer 將遍歷其內部的Realm 集合,並按迭代順序與每一個進行互動。每個Realm 的互動功能如下:

1:如果Realm 自己實現了Authorizer 介面,它的各個Authorizer方法將被呼叫。

(1)如果Realm 的方法導致異常,該異常將會以AuthorizationException 的形式傳遞給呼叫者。這將短路授權過程,任何剩餘的Realm 將不會被訪問

(2)如果該Realm 的方法是一個返回布林值的hasRole*或者isPermitted*的變體,並且該返回值為true,真值將會立即被返回,同時任何剩餘的Realm 都將被短路,這種行為能提高效能。

2:如果Realm 不實現Authorizer 介面,它會被忽略

瞭解全域性的PermissionResolver

當執行基於字串的許可權檢查是,大多數Shiro 的預設Realm 實現首先將該字串轉換成一個實際的Permission 例項,用的是內部預設實現的WildcardPermissionResolver

如果你想要支援自己的許可權字串語法,而且你想要所有配置的Realm 例項支援該語法,你可以將你的PermissionResolver 設定為全域性的

如果你想配置一個全域性的PermissionResolver,每個用來接收配置的PermissionResolver 的Realm 必須實現PermissionResolverAware 介面。這樣保證了配置的例項能夠被每個支援該配置的Realm 轉發。

類似的,還有全域性的RolePermissionResolver,但請注意:由於這種轉換角色名到許可權的概念非常特定於應用程式,Shiro 預設Realm 的實現並不使用它們