亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Spring 整合Shiro 并擴(kuò)展使用EL表達(dá)式的實(shí)例詳解

 更新時(shí)間:2018年03月16日 15:53:32   作者:Elim的博客  
Shiro是一個(gè)輕量級(jí)的權(quán)限控制框架,應(yīng)用非常廣泛。本文的重點(diǎn)是介紹Spring整合Shiro,并通過(guò)擴(kuò)展使用Spring的EL表達(dá)式。需要的朋友可以參考下

Shiro是一個(gè)輕量級(jí)的權(quán)限控制框架,應(yīng)用非常廣泛。本文的重點(diǎn)是介紹Spring整合Shiro,并通過(guò)擴(kuò)展使用Spring的EL表達(dá)式,使@RequiresRoles等支持動(dòng)態(tài)的參數(shù)。對(duì)Shiro的介紹則不在本文的討論范圍之內(nèi),讀者如果有對(duì)shiro不是很了解的,可以通過(guò)其官方網(wǎng)站了解相應(yīng)的信息。infoq上也有一篇文章對(duì)shiro介紹比較全面的,也是官方推薦的,其地址是https://www.infoq.com/articles/apache-shiro。

Shiro整合Spring

首先需要在你的工程中加入shiro-spring-xxx.jar,如果是使用Maven管理你的工程,則可以在你的依賴中加入以下依賴,筆者這里是選擇的當(dāng)前最新的1.4.0版本。

<dependency>
 <groupId>org.apache.shiro</groupId>
 <artifactId>shiro-spring</artifactId>
 <version>1.4.0</version>
</dependency>

接下來(lái)需要在你的web.xml中定義一個(gè)shiroFilter,應(yīng)用它來(lái)攔截所有的需要權(quán)限控制的請(qǐng)求,通常是配置為/*。另外該Filter需要加入最前面,以確保請(qǐng)求進(jìn)來(lái)后最先通過(guò)shiro的權(quán)限控制。這里的Filter對(duì)應(yīng)的class配置的是DelegatingFilterProxy,這是Spring提供的一個(gè)Filter的代理,可以使用Spring bean容器中的一個(gè)bean來(lái)作為當(dāng)前的Filter實(shí)例,對(duì)應(yīng)的bean就會(huì)取filter-name對(duì)應(yīng)的那個(gè)bean。所以下面的配置會(huì)到bean容器中尋找一個(gè)名為shiroFilter的bean。

<filter>
 <filter-name>shiroFilter</filter-name>
 <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
 <init-param>
  <param-name>targetFilterLifecycle</param-name>
  <param-value>true</param-value>
 </init-param>
</filter>
<filter-mapping>
 <filter-name>shiroFilter</filter-name>
 <url-pattern>/*</url-pattern>
</filter-mapping>

獨(dú)立使用Shiro時(shí)通常會(huì)定義一個(gè)org.apache.shiro.web.servlet.ShiroFilter來(lái)做類似的事。

接下來(lái)就是在bean容器中定義我們的shiroFilter了。如下我們定義了一個(gè)ShiroFilterFactoryBean,其會(huì)產(chǎn)生一個(gè)AbstractShiroFilter類型的bean。通過(guò)ShiroFilterFactoryBean我們可以指定一個(gè)SecurityManager,這里使用的DefaultWebSecurityManager需要指定一個(gè)Realm,如果需要指定多個(gè)Realm則通過(guò)realms指定。這里簡(jiǎn)單起見就直接使用基于文本定義的TextConfigurationRealm。通過(guò)loginUrl指定登錄地址、successUrl指定登錄成功后需要跳轉(zhuǎn)的地址,unauthorizedUrl指定權(quán)限不足時(shí)的提示頁(yè)面。filterChainDefinitions則定義URL與需要使用的Filter之間的關(guān)系,等號(hào)右邊的是filter的別名,默認(rèn)的別名都定義在org.apache.shiro.web.filter.mgt.DefaultFilter這個(gè)枚舉類中。

<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
 <property name="securityManager" ref="securityManager"/>
 <property name="loginUrl" value="/login.jsp"/>
 <property name="successUrl" value="/home.jsp"/>
 <property name="unauthorizedUrl" value="/unauthorized.jsp"/>
 <property name="filterChainDefinitions">
  <value>
   /admin/** = authc, roles[admin]
   /logout = logout
   # 其它地址都要求用戶已經(jīng)登錄了
   /** = authc,logger
  </value>
 </property>
</bean>
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
 <property name="realm" ref="realm"/>
</bean>
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
<!-- 簡(jiǎn)單起見,這里就使用基于文本的Realm實(shí)現(xiàn) -->
<bean id="realm" class="org.apache.shiro.realm.text.TextConfigurationRealm">
 <property name="userDefinitions">
  <value>
   user1=pass1,role1,role2
   user2=pass2,role2,role3
   admin=admin,admin
  </value>
 </property>
</bean>

如果需要在filterChainDefinitions定義中使用自定義的Filter,則可以通過(guò)ShiroFilterFactoryBean的filters指定自定義的Filter及其別名映射關(guān)系。比如下面這樣我們新增了一個(gè)別名為logger的Filter,并在filterChainDefinitions中指定了/**需要應(yīng)用別名為logger的Filter。

<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
 <property name="securityManager" ref="securityManager"/>
 <property name="loginUrl" value="/login.jsp"/>
 <property name="successUrl" value="/home.jsp"/>
 <property name="unauthorizedUrl" value="/unauthorized.jsp"/>
 <property name="filters">
  <util:map>
   <entry key="logger">
    <bean class="com.elim.chat.shiro.filter.LoggerFilter"/>
   </entry>
  </util:map>
 </property>
 <property name="filterChainDefinitions">
  <value>
   /admin/** = authc, roles[admin]
   /logout = logout
   # 其它地址都要求用戶已經(jīng)登錄了
   /** = authc,logger
  </value>
 </property>
</bean>

其實(shí)我們需要應(yīng)用的Filter別名定義也可以不直接通過(guò)ShiroFilterFactoryBean的setFilters()來(lái)指定,而是直接在對(duì)應(yīng)的bean容器中定義對(duì)應(yīng)的Filter對(duì)應(yīng)的bean。因?yàn)槟J(rèn)情況下,ShiroFilterFactoryBean會(huì)把bean容器中的所有的Filter類型的bean以其id為別名注冊(cè)到filters中。所以上面的定義等價(jià)于下面這樣。

<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
 <property name="securityManager" ref="securityManager"/>
 <property name="loginUrl" value="/login.jsp"/>
 <property name="successUrl" value="/home.jsp"/>
 <property name="unauthorizedUrl" value="/unauthorized.jsp"/>
 <property name="filterChainDefinitions">
  <value>
   /admin/** = authc, roles[admin]
   /logout = logout
   # 其它地址都要求用戶已經(jīng)登錄了
   /** = authc,logger
  </value>
 </property>
</bean>
<bean id="logger" class="com.elim.chat.shiro.filter.LoggerFilter"/>

經(jīng)過(guò)以上幾步,Shiro和Spring的整合就完成了,這個(gè)時(shí)候我們請(qǐng)求工程的任意路徑都會(huì)要求我們登錄,且會(huì)自動(dòng)跳轉(zhuǎn)到loginUrl指定的路徑讓我們輸入用戶名/密碼登錄。這個(gè)時(shí)候我們應(yīng)該提供一個(gè)表單,通過(guò)username獲得用戶名,通過(guò)password獲得密碼,然后提交登錄請(qǐng)求的時(shí)候請(qǐng)求需要提交到loginUrl指定的地址,但是請(qǐng)求方式需要變?yōu)镻OST。登錄時(shí)使用的用戶名/密碼是我們?cè)赥extConfigurationRealm中定義的用戶名/密碼,基于我們上面的配置則可以使用user1/pass1、admin/admin等。登錄成功后就會(huì)跳轉(zhuǎn)到successUrl參數(shù)指定的地址了。如果我們是使用user1/pass1登錄的,則我們還可以試著訪問(wèn)一下/admin/index,這個(gè)時(shí)候會(huì)因?yàn)闄?quán)限不足跳轉(zhuǎn)到unauthorized.jsp。

啟用基于注解的支持

基本的整合需要我們把URL需要應(yīng)用的權(quán)限控制都定義在ShiroFilterFactoryBean的filterChainDefinitions中。這有時(shí)候會(huì)沒(méi)那么靈活。Shiro為我們提供了整合Spring后可以使用的注解,它允許我們?cè)谛枰M(jìn)行權(quán)限控制的Class或Method上加上對(duì)應(yīng)的注解以定義訪問(wèn)Class或Method需要的權(quán)限,如果是定義中Class上的,則表示調(diào)用該Class中所有的方法都需要對(duì)應(yīng)的權(quán)限(注意需要是外部調(diào)用,這是動(dòng)態(tài)代理的局限)。要使用這些注解我們需要在Spring的bean容器中添加下面兩個(gè)bean定義,這樣才能在運(yùn)行時(shí)根據(jù)注解定義來(lái)判斷用戶是否擁有對(duì)應(yīng)的權(quán)限。這是通過(guò)Spring的AOP機(jī)制來(lái)實(shí)現(xiàn)的,關(guān)于Spring Aop如果有不是特別了解的,可以參考筆者寫在iteye的《Spring Aop介紹專欄》。下面的兩個(gè)bean定義,AuthorizationAttributeSourceAdvisor是定義了一個(gè)Advisor,其會(huì)基于Shiro提供的注解配置的方法進(jìn)行攔截,校驗(yàn)權(quán)限。DefaultAdvisorAutoProxyCreator則是提供了為標(biāo)注有Shiro提供的權(quán)限控制注解的Class創(chuàng)建代理對(duì)象,并在攔截到目標(biāo)方法調(diào)用時(shí)應(yīng)用AuthorizationAttributeSourceAdvisor的功能。當(dāng)攔截到了用戶的一個(gè)請(qǐng)求,而該用戶沒(méi)有對(duì)應(yīng)方法或類上標(biāo)注的權(quán)限時(shí),將拋出org.apache.shiro.authz.AuthorizationException異常。

<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" 
 depends-on="lifecycleBeanPostProcessor"/>
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
 <property name="securityManager" ref="securityManager"/>
</bean>

如果我們的bean容器中已經(jīng)定義了<aop:config/>或<aop:aspectj-autoproxy/>,則可以不再定義DefaultAdvisorAutoProxyCreator。因?yàn)榍懊鎯煞N情況都會(huì)自動(dòng)添加與DefaultAdvisorAutoProxyCreator類似的bean。關(guān)于DefaultAdvisorAutoProxyCreator的更多介紹也可以參考筆者的Spring Aop自動(dòng)創(chuàng)建代理對(duì)象的原理這篇博客。

Shiro提供的權(quán)限控制注解如下:

RequiresAuthentication:需要用戶在當(dāng)前會(huì)話中是被認(rèn)證過(guò)的,即需要通過(guò)用戶名/密碼登錄過(guò),不包括RememberMe自動(dòng)登錄。

RequiresUser:需要用戶是被認(rèn)證過(guò)的,可以是在本次會(huì)話中通過(guò)用戶名/密碼登錄認(rèn)證,也可以是通過(guò)RememberMe自動(dòng)登錄。

RequiresGuest:需要用戶是未登錄的。
RequiresRoles:需要用戶擁有指定的角色。
RequiresPermissions:需要用戶擁有指定的權(quán)限。

前面三個(gè)都很好理解,而后面兩個(gè)是類似的。筆者這里拿@RequiresPermissions來(lái)做個(gè)示例。首先我們把上面定義的Realm改一下,給role添加權(quán)限。這樣我們的user1將擁有perm1、perm2和perm3的權(quán)限,而user2將擁有perm1、perm3和perm4的權(quán)限。

<bean id="realm" class="org.apache.shiro.realm.text.TextConfigurationRealm">
 <property name="userDefinitions">
  <value>
   user1=pass1,role1,role2
   user2=pass2,role2,role3
   admin=admin,admin
  </value>
 </property>
 <property name="roleDefinitions">
  <value>
   role1=perm1,perm2
   role2=perm1,perm3
   role3=perm3,perm4
  </value>
 </property>
</bean>

@RequiresPermissions可以添加在方法上,用來(lái)指定調(diào)用該方法時(shí)需要擁有的權(quán)限。下面的代碼我們就指定了在訪問(wèn)/perm1時(shí)必須擁有perm1這個(gè)權(quán)限。這個(gè)時(shí)候user1和user2都能訪問(wèn)。

@RequestMapping("/perm1")
@RequiresPermissions("perm1")
public Object permission1() {
 return "permission1";
}

如果需要指定必須同時(shí)擁有多個(gè)權(quán)限才能訪問(wèn)某個(gè)方法,可以把需要指定的權(quán)限以數(shù)組的形式指定(注解上的數(shù)組屬性指定單個(gè)的時(shí)候可以不加大括號(hào),需要指定多個(gè)時(shí)就需要加大括號(hào))。比如下面這樣我們就指定了在訪問(wèn)/perm1AndPerm4時(shí)用戶必須同時(shí)擁有perm1和perm4這兩個(gè)權(quán)限。這時(shí)候就只有user2可以訪問(wèn),因?yàn)橹挥兴磐瑫r(shí)擁有perm1和perm4。

@RequestMapping("/perm1AndPerm4")
@RequiresPermissions({"perm1", "perm4"})
public Object perm1AndPerm4() {
 return "perm1AndPerm4";
}

當(dāng)同時(shí)指定了多個(gè)權(quán)限時(shí),默認(rèn)多個(gè)權(quán)限之間的關(guān)系是與的關(guān)系,即需要同時(shí)擁有指定的所有的權(quán)限。如果只需要擁有指定的多個(gè)權(quán)限中的一個(gè)就可以訪問(wèn),則我們可以通過(guò)logical=Logical.OR指定多個(gè)權(quán)限之間是或的關(guān)系。比如下面這樣我們就指定了在訪問(wèn)/perm1OrPerm4時(shí)只需要擁有perm1或perm4權(quán)限即可,這樣user1和user2都可以訪問(wèn)該方法。

@RequestMapping("/perm1OrPerm4")
@RequiresPermissions(value={"perm1", "perm4"}, logical=Logical.OR)
public Object perm1OrPerm4() {
 return "perm1OrPerm4";
}

@RequiresPermissions也可以標(biāo)注在Class上,表示在外部訪問(wèn)Class中的方法時(shí)都需要有對(duì)應(yīng)的權(quán)限。比如下面這樣我們?cè)贑lass級(jí)別指定了需要擁有權(quán)限perm2,而在index()方法上則沒(méi)有指定需要任何權(quán)限,但是我們?cè)谠L問(wèn)該方法時(shí)還是需要擁有Class級(jí)別指定的權(quán)限。此時(shí)將只有user1可以訪問(wèn)。

@RestController
@RequestMapping("/foo")
@RequiresPermissions("perm2")
public class FooController {
 @RequestMapping(method=RequestMethod.GET)
 public Object index() {
  Map<String, Object> map = new HashMap<>();
  map.put("abc", 123);
  return map;
 }
}

當(dāng)Class和方法級(jí)別都同時(shí)擁有@RequiresPermissions時(shí),方法級(jí)別的擁有更高的優(yōu)先級(jí),而且此時(shí)將只會(huì)校驗(yàn)方法級(jí)別要求的權(quán)限。如下我們?cè)贑lass級(jí)別指定了需要perm2權(quán)限,而在方法級(jí)別指定了需要perm3權(quán)限,那么在訪問(wèn)/foo時(shí)將只需要擁有perm3權(quán)限即可訪問(wèn)到index()方法。所以此時(shí)user1和user2都可以訪問(wèn)/foo。

@RestController
@RequestMapping("/foo")
@RequiresPermissions("perm2")
public class FooController {
 @RequestMapping(method=RequestMethod.GET)
 @RequiresPermissions("perm3")
 public Object index() {
  Map<String, Object> map = new HashMap<>();
  map.put("abc", 123);
  return map;
 }
}

但是如果此時(shí)我們?cè)贑lass上新增@RequiresRoles("role1")指定需要擁有角色role1,那么此時(shí)訪問(wèn)/foo時(shí)需要擁有Class上的role1和index()方法上@RequiresPermissions("perm3")指定的perm3權(quán)限。因?yàn)镽equiresRoles和RequiresPermissions屬于不同維度的權(quán)限定義,Shiro在校驗(yàn)的時(shí)候都將校驗(yàn)一遍,但是如果Class和方法上都擁有同類型的權(quán)限控制定義的注解時(shí),則只會(huì)以方法上的定義為準(zhǔn)。

@RestController
@RequestMapping("/foo")
@RequiresPermissions("perm2")
@RequiresRoles("role1")
public class FooController {
 @RequestMapping(method=RequestMethod.GET)
 @RequiresPermissions("perm3")
 public Object index() {
  Map<String, Object> map = new HashMap<>();
  map.put("abc", 123);
  return map;
 }
}

雖然示例中使用的只是RequiresPermissions,但是其它權(quán)限控制注解的用法也是類似的,其它注解的用法請(qǐng)感興趣的朋友自己實(shí)踐。

基于注解控制權(quán)限的原理

上面使用@RequiresPermissions我們指定的權(quán)限都是靜態(tài)的,寫本文的一個(gè)主要目的是介紹一種方法,通過(guò)擴(kuò)展實(shí)現(xiàn)來(lái)使指定的權(quán)限可以是動(dòng)態(tài)的。但是在擴(kuò)展前我們得知道它底層的工作方式,即實(shí)現(xiàn)原理,我們才能進(jìn)行擴(kuò)展。所以接下來(lái)我們先來(lái)看一下Shiro整合Spring后使用@RequiresPermissions的工作原理。在啟用對(duì)@RequiresPermissions的支持時(shí)我們定義了如下bean,這是一個(gè)Advisor,其繼承自StaticMethodMatcherPointcutAdvisor,它的方法匹配邏輯是只要Class或Method上擁有Shiro的幾個(gè)權(quán)限控制注解即可,而攔截以后的處理邏輯則是由相應(yīng)的Advice指定。

<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
 <property name="securityManager" ref="securityManager"/>
</bean>

以下是AuthorizationAttributeSourceAdvisor的源碼。我們可以看到在其構(gòu)造方法中通過(guò)setAdvice()指定了AopAllianceAnnotationsAuthorizingMethodInterceptor這個(gè)Advice實(shí)現(xiàn)類,這是基于MethodInterceptor的實(shí)現(xiàn)。

public class AuthorizationAttributeSourceAdvisor extends StaticMethodMatcherPointcutAdvisor {
 private static final Logger log = LoggerFactory.getLogger(AuthorizationAttributeSourceAdvisor.class);
 private static final Class<? extends Annotation>[] AUTHZ_ANNOTATION_CLASSES =
   new Class[] {
     RequiresPermissions.class, RequiresRoles.class,
     RequiresUser.class, RequiresGuest.class, RequiresAuthentication.class
   };
 protected SecurityManager securityManager = null;
 public AuthorizationAttributeSourceAdvisor() {
  setAdvice(new AopAllianceAnnotationsAuthorizingMethodInterceptor());
 }
 public SecurityManager getSecurityManager() {
  return securityManager;
 }
 public void setSecurityManager(org.apache.shiro.mgt.SecurityManager securityManager) {
  this.securityManager = securityManager;
 }
 public boolean matches(Method method, Class targetClass) {
  Method m = method;
  if ( isAuthzAnnotationPresent(m) ) {
   return true;
  }
  //The 'method' parameter could be from an interface that doesn't have the annotation.
  //Check to see if the implementation has it.
  if ( targetClass != null) {
   try {
    m = targetClass.getMethod(m.getName(), m.getParameterTypes());
    return isAuthzAnnotationPresent(m) || isAuthzAnnotationPresent(targetClass);
   } catch (NoSuchMethodException ignored) {
    //default return value is false. If we can't find the method, then obviously
    //there is no annotation, so just use the default return value.
   }
  }
  return false;
 }
 private boolean isAuthzAnnotationPresent(Class<?> targetClazz) {
  for( Class<? extends Annotation> annClass : AUTHZ_ANNOTATION_CLASSES ) {
   Annotation a = AnnotationUtils.findAnnotation(targetClazz, annClass);
   if ( a != null ) {
    return true;
   }
  }
  return false;
 }
 private boolean isAuthzAnnotationPresent(Method method) {
  for( Class<? extends Annotation> annClass : AUTHZ_ANNOTATION_CLASSES ) {
   Annotation a = AnnotationUtils.findAnnotation(method, annClass);
   if ( a != null ) {
    return true;
   }
  }
  return false;
 }
}

AopAllianceAnnotationsAuthorizingMethodInterceptor的源碼如下。其實(shí)現(xiàn)的MethodInterceptor接口的invoke方法又調(diào)用了父類的invoke方法。同時(shí)我們要看到在其構(gòu)造方法中創(chuàng)建了一些AuthorizingAnnotationMethodInterceptor實(shí)現(xiàn),這些實(shí)現(xiàn)才是實(shí)現(xiàn)權(quán)限控制的核心,待會(huì)我們會(huì)挑出PermissionAnnotationMethodInterceptor實(shí)現(xiàn)類來(lái)看其具體的實(shí)現(xiàn)邏輯。

public class AopAllianceAnnotationsAuthorizingMethodInterceptor
  extends AnnotationsAuthorizingMethodInterceptor implements MethodInterceptor {
 public AopAllianceAnnotationsAuthorizingMethodInterceptor() {
  List<AuthorizingAnnotationMethodInterceptor> interceptors =
    new ArrayList<AuthorizingAnnotationMethodInterceptor>(5);
  //use a Spring-specific Annotation resolver - Spring's AnnotationUtils is nicer than the
  //raw JDK resolution process.
  AnnotationResolver resolver = new SpringAnnotationResolver();
  //we can re-use the same resolver instance - it does not retain state:
  interceptors.add(new RoleAnnotationMethodInterceptor(resolver));
  interceptors.add(new PermissionAnnotationMethodInterceptor(resolver));
  interceptors.add(new AuthenticatedAnnotationMethodInterceptor(resolver));
  interceptors.add(new UserAnnotationMethodInterceptor(resolver));
  interceptors.add(new GuestAnnotationMethodInterceptor(resolver));
  setMethodInterceptors(interceptors);
 }
 protected org.apache.shiro.aop.MethodInvocation createMethodInvocation(Object implSpecificMethodInvocation) {
  final MethodInvocation mi = (MethodInvocation) implSpecificMethodInvocation;
  return new org.apache.shiro.aop.MethodInvocation() {
   public Method getMethod() {
    return mi.getMethod();
   }
   public Object[] getArguments() {
    return mi.getArguments();
   }
   public String toString() {
    return "Method invocation [" + mi.getMethod() + "]";
   }
   public Object proceed() throws Throwable {
    return mi.proceed();
   }
   public Object getThis() {
    return mi.getThis();
   }
  };
 }
 protected Object continueInvocation(Object aopAllianceMethodInvocation) throws Throwable {
  MethodInvocation mi = (MethodInvocation) aopAllianceMethodInvocation;
  return mi.proceed();
 }
 public Object invoke(MethodInvocation methodInvocation) throws Throwable {
  org.apache.shiro.aop.MethodInvocation mi = createMethodInvocation(methodInvocation);
  return super.invoke(mi);
 }
}

通過(guò)看父類的invoke方法實(shí)現(xiàn),最終我們會(huì)看到核心邏輯是調(diào)用assertAuthorized方法,而該方法的實(shí)現(xiàn)(源碼如下)又是依次判斷配置的AuthorizingAnnotationMethodInterceptor是否支持當(dāng)前方法進(jìn)行權(quán)限校驗(yàn)(通過(guò)判斷Class或Method上是否擁有其支持的注解),當(dāng)支持時(shí)則會(huì)調(diào)用其assertAuthorized方法進(jìn)行權(quán)限校驗(yàn),而AuthorizingAnnotationMethodInterceptor又會(huì)調(diào)用AuthorizingAnnotationHandler的assertAuthorized方法。

protected void assertAuthorized(MethodInvocation methodInvocation) throws AuthorizationException {
 //default implementation just ensures no deny votes are cast:
 Collection<AuthorizingAnnotationMethodInterceptor> aamis = getMethodInterceptors();
 if (aamis != null && !aamis.isEmpty()) {
  for (AuthorizingAnnotationMethodInterceptor aami : aamis) {
   if (aami.supports(methodInvocation)) {
    aami.assertAuthorized(methodInvocation);
   }
  }
 }
}

接下來(lái)我們?cè)倩剡^(guò)頭來(lái)看AopAllianceAnnotationsAuthorizingMethodInterceptor的定義的PermissionAnnotationMethodInterceptor,其源碼如下。結(jié)合AopAllianceAnnotationsAuthorizingMethodInterceptor的源碼和PermissionAnnotationMethodInterceptor的源碼,我們可以看到PermissionAnnotationMethodInterceptor中這時(shí)候指定了PermissionAnnotationHandler和SpringAnnotationResolver。PermissionAnnotationHandler是AuthorizingAnnotationHandler的一個(gè)子類。所以我們最終的權(quán)限控制由PermissionAnnotationHandler的assertAuthorized實(shí)現(xiàn)決定。

public class PermissionAnnotationMethodInterceptor extends AuthorizingAnnotationMethodInterceptor {
 public PermissionAnnotationMethodInterceptor() {
  super( new PermissionAnnotationHandler() );
 }
 public PermissionAnnotationMethodInterceptor(AnnotationResolver resolver) {
  super( new PermissionAnnotationHandler(), resolver);
 }
}

接下來(lái)我們來(lái)看PermissionAnnotationHandler的assertAuthorized方法實(shí)現(xiàn),其完整代碼如下。從實(shí)現(xiàn)上我們可以看到其會(huì)從Annotation中獲取配置的權(quán)限值,而這里的Annotation就是RequiresPermissions注解。而且在進(jìn)行權(quán)限校驗(yàn)時(shí)都是直接使用的我們定義注解時(shí)指定的文本值,待會(huì)我們進(jìn)行擴(kuò)展時(shí)就將從這里入手。

public class PermissionAnnotationHandler extends AuthorizingAnnotationHandler {
 public PermissionAnnotationHandler() {
  super(RequiresPermissions.class);
 }
 protected String[] getAnnotationValue(Annotation a) {
  RequiresPermissions rpAnnotation = (RequiresPermissions) a;
  return rpAnnotation.value();
 }
 public void assertAuthorized(Annotation a) throws AuthorizationException {
  if (!(a instanceof RequiresPermissions)) return;
  RequiresPermissions rpAnnotation = (RequiresPermissions) a;
  String[] perms = getAnnotationValue(a);
  Subject subject = getSubject();
  if (perms.length == 1) {
   subject.checkPermission(perms[0]);
   return;
  }
  if (Logical.AND.equals(rpAnnotation.logical())) {
   getSubject().checkPermissions(perms);
   return;
  }
  if (Logical.OR.equals(rpAnnotation.logical())) {
   // Avoid processing exceptions unnecessarily - "delay" throwing the exception by calling hasRole first
   boolean hasAtLeastOnePermission = false;
   for (String permission : perms) if (getSubject().isPermitted(permission)) hasAtLeastOnePermission = true;
   // Cause the exception if none of the role match, note that the exception message will be a bit misleading
   if (!hasAtLeastOnePermission) getSubject().checkPermission(perms[0]);
  }
 }
}

通過(guò)前面的介紹我們知道PermissionAnnotationHandler的assertAuthorized方法參數(shù)的Annotation是由AuthorizingAnnotationMethodInterceptor在調(diào)用AuthorizingAnnotationHandler的assertAuthorized方法時(shí)傳遞的。其源碼如下,從源碼中我們可以看到Annotation是通過(guò)getAnnotation方法獲得的。

public void assertAuthorized(MethodInvocation mi) throws AuthorizationException {
 try {
  ((AuthorizingAnnotationHandler)getHandler()).assertAuthorized(getAnnotation(mi));
 }
 catch(AuthorizationException ae) {
  if (ae.getCause() == null) ae.initCause(new AuthorizationException("Not authorized to invoke method: " + mi.getMethod()));
  throw ae;
 }   
}

沿著這個(gè)方向走下去,最終我們會(huì)找到SpringAnnotationResolver的getAnnotation方法實(shí)現(xiàn),其實(shí)現(xiàn)如下。從下面的代碼可以看到,其在尋找注解時(shí)是優(yōu)先尋找Method上的,如果在Method上沒(méi)有找到會(huì)從當(dāng)前方法調(diào)用的所屬Class上尋找對(duì)應(yīng)的注解。從這里也可以看到為什么我們之前在Class和Method上都定義了相同類型的權(quán)限控制注解時(shí)生效的是Method上的,而單獨(dú)存在的時(shí)候就是單獨(dú)定義的那個(gè)生效了。

public class SpringAnnotationResolver implements AnnotationResolver {
 public Annotation getAnnotation(MethodInvocation mi, Class<? extends Annotation> clazz) {
  Method m = mi.getMethod();
  Annotation a = AnnotationUtils.findAnnotation(m, clazz);
  if (a != null) return a;
  //The MethodInvocation's method object could be a method defined in an interface.
  //However, if the annotation existed in the interface's implementation (and not
  //the interface itself), it won't be on the above method object. Instead, we need to
  //acquire the method representation from the targetClass and check directly on the
  //implementation itself:
  Class<?> targetClass = mi.getThis().getClass();
  m = ClassUtils.getMostSpecificMethod(m, targetClass);
  a = AnnotationUtils.findAnnotation(m, clazz);
  if (a != null) return a;
  // See if the class has the same annotation
  return AnnotationUtils.findAnnotation(mi.getThis().getClass(), clazz);
 }
}

通過(guò)以上的源碼閱讀,相信讀者對(duì)于Shiro整合Spring后支持的權(quán)限控制注解的原理已經(jīng)有了比較深入的理解。上面貼出的源碼只是部分筆者認(rèn)為比較核心的,有想詳細(xì)了解完整內(nèi)容的請(qǐng)讀者自己沿著筆者提到的思路去閱讀完整代碼。
了解了這塊基于注解進(jìn)行權(quán)限控制的原理后,讀者朋友們也可以根據(jù)實(shí)際的業(yè)務(wù)需要進(jìn)行相應(yīng)的擴(kuò)展。

擴(kuò)展使用Spring EL表達(dá)式

假設(shè)現(xiàn)在內(nèi)部有下面這樣一個(gè)接口,其中有一個(gè)query方法,接收一個(gè)參數(shù)type。這里我們簡(jiǎn)化一點(diǎn),假設(shè)只要接收這么一個(gè)參數(shù),然后對(duì)應(yīng)不同的取值時(shí)將返回不同的結(jié)果。

public interface RealService {
 Object query(int type);
 
}

這個(gè)接口是對(duì)外開放的,通過(guò)對(duì)應(yīng)的URL可以請(qǐng)求到該方法,我們定義了對(duì)應(yīng)的Controller方法如下:

@RequestMapping("/service/{type}")
public Object query(@PathVariable("type") int type) {
 return this.realService.query(type);
}

上面的接口服務(wù)在進(jìn)行查詢的時(shí)候針對(duì)type是有權(quán)限的,不是每個(gè)用戶都可以使用每種type進(jìn)行查詢的,需要擁有對(duì)應(yīng)的權(quán)限才行。所以針對(duì)上面的處理器方法我們需要加上權(quán)限控制,而且在控制時(shí)需要的權(quán)限是隨著參數(shù)type動(dòng)態(tài)變的。假設(shè)關(guān)于type的每項(xiàng)權(quán)限的定義是query:type的形式,比如type=1時(shí)需要的權(quán)限是query:1,type=2時(shí)需要的權(quán)限是query:2。在沒(méi)有與Spring整合時(shí),我們會(huì)如下這樣做:

@RequestMapping("/service/{type}")
public Object query(@PathVariable("type") int type) {
 SecurityUtils.getSubject().checkPermission("query:" + type);
 return this.realService.query(type);
}

但是與Spring整合后,上面的做法耦合性強(qiáng),我們會(huì)更希望通過(guò)整合后的注解來(lái)進(jìn)行權(quán)限控制。對(duì)于上面的場(chǎng)景我們更希望通過(guò)@RequiresPermissions來(lái)指定需要的權(quán)限,但是@RequiresPermissions中定義的權(quán)限是靜態(tài)文本,固定的。它沒(méi)法滿足我們動(dòng)態(tài)的需求。這個(gè)時(shí)候可能你會(huì)想著我們可以把Controller處理方法拆分為多個(gè),單獨(dú)進(jìn)行權(quán)限控制。比如下面這樣:

@RequestMapping("/service/1")
@RequiresPermissions("query:1")
public Object service1() {
 return this.realService.query(1);
}
@RequiresPermissions("query:2")
@RequestMapping("/service/2")
public Object service2() {
 return this.realService.query(2);
}
//...
@RequestMapping("/service/200")
@RequiresPermissions("query:200")
public Object service200() {
 return this.realService.query(200);
}

這在type的取值范圍比較小的時(shí)候還可以,但是如果像上面這樣可能的取值有200種,把它們窮舉出來(lái)定義單獨(dú)的處理器方法并進(jìn)行權(quán)限控制就顯得有點(diǎn)麻煩了。另外就是如果將來(lái)type的取值有變動(dòng),我們還得添加新的處理器方法。所以最好的辦法是讓@RequiresPermissions支持動(dòng)態(tài)的權(quán)限定義,同時(shí)又可以維持靜態(tài)定義的支持。通過(guò)前面的分析我們知道,切入點(diǎn)是PermissionAnnotationHandler,而它里面是沒(méi)有提供對(duì)權(quán)限校驗(yàn)的擴(kuò)展的。我們?nèi)绻雽?duì)它擴(kuò)展簡(jiǎn)單的辦法就是把它整體的替換。但是我們需要?jiǎng)討B(tài)處理的權(quán)限是跟方法參數(shù)相關(guān)的,而PermissionAnnotationHandler中是取不到方法參數(shù)的,為此我們不能直接替換掉PermissionAnnotationHandler。PermissionAnnotationHandler是由PermissionAnnotationMethodInterceptor調(diào)用的,在其父類AuthorizingAnnotationMethodInterceptor的assertAuthorized方法中調(diào)用PermissionAnnotationHandler時(shí)是可以獲取到方法參數(shù)的。為此我們的擴(kuò)展點(diǎn)就選在PermissionAnnotationMethodInterceptor類上,我們也需要把它整體的替換。Spring的EL表達(dá)式可以支持解析方法參數(shù)值,這里我們選擇引入Spring的EL表達(dá)式,在@RequiresPermissions定義權(quán)限時(shí)可以使用Spring EL表達(dá)式引入方法參數(shù)。同時(shí)為了兼顧靜態(tài)的文本。這里引入Spring的EL表達(dá)式模板。關(guān)于Spring的EL表達(dá)式模板可以參考筆者的這篇博文。我們定義自己的PermissionAnnotationMethodInterceptor,把它繼承自PermissionAnnotationMethodInterceptor,重寫assertAuthoried方法,方法的實(shí)現(xiàn)邏輯參考PermissionAnnotationHandler中的邏輯,但是所使用的@RequiresPermissions中的權(quán)限定義,是我們使用Spring EL表達(dá)式基于當(dāng)前調(diào)用的方法作為EvaluationContext解析后的結(jié)果。以下是我們自己定義的PermissionAnnotationMethodInterceptor實(shí)現(xiàn)。

public class SelfPermissionAnnotationMethodInterceptor extends PermissionAnnotationMethodInterceptor {
 private final SpelExpressionParser parser = new SpelExpressionParser();
 private final ParameterNameDiscoverer paramNameDiscoverer = new DefaultParameterNameDiscoverer();
 private final TemplateParserContext templateParserContext = new TemplateParserContext();
 public SelfPermissionAnnotationMethodInterceptor(AnnotationResolver resolver) {
  super(resolver);
 }
 @Override
 public void assertAuthorized(MethodInvocation mi) throws AuthorizationException {
  Annotation annotation = super.getAnnotation(mi);
  RequiresPermissions permAnnotation = (RequiresPermissions) annotation;
  String[] perms = permAnnotation.value();
  EvaluationContext evaluationContext = new MethodBasedEvaluationContext(null, mi.getMethod(), mi.getArguments(), paramNameDiscoverer);
  for (int i=0; i<perms.length; i++) {
   Expression expression = this.parser.parseExpression(perms[i], templateParserContext);
   //使用Spring EL表達(dá)式解析后的權(quán)限定義替換原來(lái)的權(quán)限定義
   perms[i] = expression.getValue(evaluationContext, String.class);
  }
  Subject subject = getSubject();
  if (perms.length == 1) {
   subject.checkPermission(perms[0]);
   return;
  }
  if (Logical.AND.equals(permAnnotation.logical())) {
   getSubject().checkPermissions(perms);
   return;
  }
  if (Logical.OR.equals(permAnnotation.logical())) {
   // Avoid processing exceptions unnecessarily - "delay" throwing the exception by calling hasRole first
   boolean hasAtLeastOnePermission = false;
   for (String permission : perms) if (getSubject().isPermitted(permission)) hasAtLeastOnePermission = true;
   // Cause the exception if none of the role match, note that the exception message will be a bit misleading
   if (!hasAtLeastOnePermission) getSubject().checkPermission(perms[0]);
  }
 }
}

定義了自己的PermissionAnnotationMethodInterceptor后,我們需要替換原來(lái)的PermissionAnnotationMethodInterceptor為我們自己的PermissionAnnotationMethodInterceptor。根據(jù)前面介紹的Shiro整合Spring后使用@RequiresPermissions等注解的原理我們知道PermissionAnnotationMethodInterceptor是由AopAllianceAnnotationsAuthorizingMethodInterceptor指定的,而后者又是由AuthorizationAttributeSourceAdvisor指定的。為此我們需要在定義AuthorizationAttributeSourceAdvisor時(shí)通過(guò)顯示定義AopAllianceAnnotationsAuthorizingMethodInterceptor的方式顯示的定義其中的AuthorizingAnnotationMethodInterceptor,然后把自帶的PermissionAnnotationMethodInterceptor替換為我們自定義的SelfAuthorizingAnnotationMethodInterceptor。替換后的定義如下:

<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
 <property name="securityManager" ref="securityManager"/>
 <property name="advice">
  <bean class="org.apache.shiro.spring.security.interceptor.AopAllianceAnnotationsAuthorizingMethodInterceptor">
   <property name="methodInterceptors">
    <util:list>
     <bean class="org.apache.shiro.authz.aop.RoleAnnotationMethodInterceptor"
      c:resolver-ref="springAnnotationResolver"/>
     <!-- 使用自定義的PermissionAnnotationMethodInterceptor -->
     <bean class="com.elim.chat.shiro.SelfPermissionAnnotationMethodInterceptor"
      c:resolver-ref="springAnnotationResolver"/>
     <bean class="org.apache.shiro.authz.aop.AuthenticatedAnnotationMethodInterceptor"
      c:resolver-ref="springAnnotationResolver"/>
     <bean class="org.apache.shiro.authz.aop.UserAnnotationMethodInterceptor"
      c:resolver-ref="springAnnotationResolver"/>
     <bean class="org.apache.shiro.authz.aop.GuestAnnotationMethodInterceptor"
      c:resolver-ref="springAnnotationResolver"/>
    </util:list>
   </property>
  </bean>
 </property>
</bean>

<bean id="springAnnotationResolver" class="org.apache.shiro.spring.aop.SpringAnnotationResolver"/>

為了演示前面示例的動(dòng)態(tài)的權(quán)限,我們把角色與權(quán)限的關(guān)系調(diào)整如下,讓role1、role2和role3分別擁有query:1、query:2和query:3的權(quán)限。此時(shí)user1將擁有query:1和query:2的權(quán)限。

<bean id="realm" class="org.apache.shiro.realm.text.TextConfigurationRealm">
 <property name="userDefinitions">
  <value>
   user1=pass1,role1,role2
   user2=pass2,role2,role3
   admin=admin,admin
  </value>
 </property>
 <property name="roleDefinitions">
  <value>
   role1=perm1,perm2,query:1
   role2=perm1,perm3,query:2
   role3=perm3,perm4,query:3
  </value>
 </property>
</bean>

此時(shí)@RequiresPermissions中指定權(quán)限時(shí)就可以使用Spring EL表達(dá)式支持的語(yǔ)法了。因?yàn)槲覀冊(cè)诙xSelfPermissionAnnotationMethodInterceptor時(shí)已經(jīng)指定了應(yīng)用基于模板的表達(dá)式解析,此時(shí)權(quán)限中定義的文本都將作為文本解析,動(dòng)態(tài)的部分默認(rèn)需要使用#{前綴和}后綴包起來(lái)(這個(gè)前綴和后綴是可以指定的,但是默認(rèn)就好)。在動(dòng)態(tài)部分中可以使用#前綴引用變量,基于方法的表達(dá)式解析中可以使用參數(shù)名或p參數(shù)索引的形式引用方法參數(shù)。所以上面我們需要?jiǎng)討B(tài)的權(quán)限的query方法的@RequiresPermissions定義如下。

@RequestMapping("/service/{type}")
@RequiresPermissions("query:#{#type}")
public Object query(@PathVariable("type") int type) {
 return this.realService.query(type);
}

這樣user1在訪問(wèn)/service/1和/service/2是OK的,但是在訪問(wèn)/service/3和/service/300時(shí)會(huì)提示沒(méi)有權(quán)限,因?yàn)閡ser1沒(méi)有query:3和query:300的權(quán)限。

總結(jié)

以上所述是小編給大家介紹的Spring 整合Shiro 并擴(kuò)展使用EL表達(dá)式的實(shí)例詳解,希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!

相關(guān)文章

最新評(píng)論