亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 開發 > Java > 正文

Spring 整合Shiro 并擴展使用EL表達式的實例詳解

2024-07-14 08:40:13
字體:
來源:轉載
供稿:網友

Shiro是一個輕量級的權限控制框架,應用非常廣泛。本文的重點是介紹Spring整合Shiro,并通過擴展使用Spring的EL表達式,使@RequiresRoles等支持動態的參數。對Shiro的介紹則不在本文的討論范圍之內,讀者如果有對shiro不是很了解的,可以通過其官方網站了解相應的信息。infoq上也有一篇文章對shiro介紹比較全面的,也是官方推薦的,其地址是https://www.infoq.com/articles/apache-shiro。

Shiro整合Spring

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

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

接下來需要在你的web.xml中定義一個shiroFilter,應用它來攔截所有的需要權限控制的請求,通常是配置為/*。另外該Filter需要加入最前面,以確保請求進來后最先通過shiro的權限控制。這里的Filter對應的class配置的是DelegatingFilterProxy,這是Spring提供的一個Filter的代理,可以使用Spring bean容器中的一個bean來作為當前的Filter實例,對應的bean就會取filter-name對應的那個bean。所以下面的配置會到bean容器中尋找一個名為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>

獨立使用Shiro時通常會定義一個org.apache.shiro.web.servlet.ShiroFilter來做類似的事。

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

<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   # 其它地址都要求用戶已經登錄了   /** = 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"/>
<!-- 簡單起見,這里就使用基于文本的Realm實現 --><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,則可以通過ShiroFilterFactoryBean的filters指定自定義的Filter及其別名映射關系。比如下面這樣我們新增了一個別名為logger的Filter,并在filterChainDefinitions中指定了/**需要應用別名為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   # 其它地址都要求用戶已經登錄了   /** = authc,logger  </value> </property></bean>

其實我們需要應用的Filter別名定義也可以不直接通過ShiroFilterFactoryBean的setFilters()來指定,而是直接在對應的bean容器中定義對應的Filter對應的bean。因為默認情況下,ShiroFilterFactoryBean會把bean容器中的所有的Filter類型的bean以其id為別名注冊到filters中。所以上面的定義等價于下面這樣。

<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   # 其它地址都要求用戶已經登錄了   /** = authc,logger  </value> </property></bean><bean id="logger" class="com.elim.chat.shiro.filter.LoggerFilter"/>

經過以上幾步,Shiro和Spring的整合就完成了,這個時候我們請求工程的任意路徑都會要求我們登錄,且會自動跳轉到loginUrl指定的路徑讓我們輸入用戶名/密碼登錄。這個時候我們應該提供一個表單,通過username獲得用戶名,通過password獲得密碼,然后提交登錄請求的時候請求需要提交到loginUrl指定的地址,但是請求方式需要變為POST。登錄時使用的用戶名/密碼是我們在TextConfigurationRealm中定義的用戶名/密碼,基于我們上面的配置則可以使用user1/pass1、admin/admin等。登錄成功后就會跳轉到successUrl參數指定的地址了。如果我們是使用user1/pass1登錄的,則我們還可以試著訪問一下/admin/index,這個時候會因為權限不足跳轉到unauthorized.jsp。

啟用基于注解的支持

基本的整合需要我們把URL需要應用的權限控制都定義在ShiroFilterFactoryBean的filterChainDefinitions中。這有時候會沒那么靈活。Shiro為我們提供了整合Spring后可以使用的注解,它允許我們在需要進行權限控制的Class或Method上加上對應的注解以定義訪問Class或Method需要的權限,如果是定義中Class上的,則表示調用該Class中所有的方法都需要對應的權限(注意需要是外部調用,這是動態代理的局限)。要使用這些注解我們需要在Spring的bean容器中添加下面兩個bean定義,這樣才能在運行時根據注解定義來判斷用戶是否擁有對應的權限。這是通過Spring的AOP機制來實現的,關于Spring Aop如果有不是特別了解的,可以參考筆者寫在iteye的《Spring Aop介紹專欄》。下面的兩個bean定義,AuthorizationAttributeSourceAdvisor是定義了一個Advisor,其會基于Shiro提供的注解配置的方法進行攔截,校驗權限。DefaultAdvisorAutoProxyCreator則是提供了為標注有Shiro提供的權限控制注解的Class創建代理對象,并在攔截到目標方法調用時應用AuthorizationAttributeSourceAdvisor的功能。當攔截到了用戶的一個請求,而該用戶沒有對應方法或類上標注的權限時,將拋出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容器中已經定義了<aop:config/>或<aop:aspectj-autoproxy/>,則可以不再定義DefaultAdvisorAutoProxyCreator。因為前面兩種情況都會自動添加與DefaultAdvisorAutoProxyCreator類似的bean。關于DefaultAdvisorAutoProxyCreator的更多介紹也可以參考筆者的Spring Aop自動創建代理對象的原理這篇博客。

Shiro提供的權限控制注解如下:

RequiresAuthentication:需要用戶在當前會話中是被認證過的,即需要通過用戶名/密碼登錄過,不包括RememberMe自動登錄。

RequiresUser:需要用戶是被認證過的,可以是在本次會話中通過用戶名/密碼登錄認證,也可以是通過RememberMe自動登錄。

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

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

<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可以添加在方法上,用來指定調用該方法時需要擁有的權限。下面的代碼我們就指定了在訪問/perm1時必須擁有perm1這個權限。這個時候user1和user2都能訪問。

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

如果需要指定必須同時擁有多個權限才能訪問某個方法,可以把需要指定的權限以數組的形式指定(注解上的數組屬性指定單個的時候可以不加大括號,需要指定多個時就需要加大括號)。比如下面這樣我們就指定了在訪問/perm1AndPerm4時用戶必須同時擁有perm1和perm4這兩個權限。這時候就只有user2可以訪問,因為只有它才同時擁有perm1和perm4。

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

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

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

@RequiresPermissions也可以標注在Class上,表示在外部訪問Class中的方法時都需要有對應的權限。比如下面這樣我們在Class級別指定了需要擁有權限perm2,而在index()方法上則沒有指定需要任何權限,但是我們在訪問該方法時還是需要擁有Class級別指定的權限。此時將只有user1可以訪問。

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

當Class和方法級別都同時擁有@RequiresPermissions時,方法級別的擁有更高的優先級,而且此時將只會校驗方法級別要求的權限。如下我們在Class級別指定了需要perm2權限,而在方法級別指定了需要perm3權限,那么在訪問/foo時將只需要擁有perm3權限即可訪問到index()方法。所以此時user1和user2都可以訪問/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; }}

但是如果此時我們在Class上新增@RequiresRoles("role1")指定需要擁有角色role1,那么此時訪問/foo時需要擁有Class上的role1和index()方法上@RequiresPermissions("perm3")指定的perm3權限。因為RequiresRoles和RequiresPermissions屬于不同維度的權限定義,Shiro在校驗的時候都將校驗一遍,但是如果Class和方法上都擁有同類型的權限控制定義的注解時,則只會以方法上的定義為準。

@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,但是其它權限控制注解的用法也是類似的,其它注解的用法請感興趣的朋友自己實踐。

基于注解控制權限的原理

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

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

以下是AuthorizationAttributeSourceAdvisor的源碼。我們可以看到在其構造方法中通過setAdvice()指定了AopAllianceAnnotationsAuthorizingMethodInterceptor這個Advice實現類,這是基于MethodInterceptor的實現。

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的源碼如下。其實現的MethodInterceptor接口的invoke方法又調用了父類的invoke方法。同時我們要看到在其構造方法中創建了一些AuthorizingAnnotationMethodInterceptor實現,這些實現才是實現權限控制的核心,待會我們會挑出PermissionAnnotationMethodInterceptor實現類來看其具體的實現邏輯。

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

通過看父類的invoke方法實現,最終我們會看到核心邏輯是調用assertAuthorized方法,而該方法的實現(源碼如下)又是依次判斷配置的AuthorizingAnnotationMethodInterceptor是否支持當前方法進行權限校驗(通過判斷Class或Method上是否擁有其支持的注解),當支持時則會調用其assertAuthorized方法進行權限校驗,而AuthorizingAnnotationMethodInterceptor又會調用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);   }  } }}

接下來我們再回過頭來看AopAllianceAnnotationsAuthorizingMethodInterceptor的定義的PermissionAnnotationMethodInterceptor,其源碼如下。結合AopAllianceAnnotationsAuthorizingMethodInterceptor的源碼和PermissionAnnotationMethodInterceptor的源碼,我們可以看到PermissionAnnotationMethodInterceptor中這時候指定了PermissionAnnotationHandler和SpringAnnotationResolver。PermissionAnnotationHandler是AuthorizingAnnotationHandler的一個子類。所以我們最終的權限控制由PermissionAnnotationHandler的assertAuthorized實現決定。

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

接下來我們來看PermissionAnnotationHandler的assertAuthorized方法實現,其完整代碼如下。從實現上我們可以看到其會從Annotation中獲取配置的權限值,而這里的Annotation就是RequiresPermissions注解。而且在進行權限校驗時都是直接使用的我們定義注解時指定的文本值,待會我們進行擴展時就將從這里入手。

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

通過前面的介紹我們知道PermissionAnnotationHandler的assertAuthorized方法參數的Annotation是由AuthorizingAnnotationMethodInterceptor在調用AuthorizingAnnotationHandler的assertAuthorized方法時傳遞的。其源碼如下,從源碼中我們可以看到Annotation是通過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; }   }

沿著這個方向走下去,最終我們會找到SpringAnnotationResolver的getAnnotation方法實現,其實現如下。從下面的代碼可以看到,其在尋找注解時是優先尋找Method上的,如果在Method上沒有找到會從當前方法調用的所屬Class上尋找對應的注解。從這里也可以看到為什么我們之前在Class和Method上都定義了相同類型的權限控制注解時生效的是Method上的,而單獨存在的時候就是單獨定義的那個生效了。

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

通過以上的源碼閱讀,相信讀者對于Shiro整合Spring后支持的權限控制注解的原理已經有了比較深入的理解。上面貼出的源碼只是部分筆者認為比較核心的,有想詳細了解完整內容的請讀者自己沿著筆者提到的思路去閱讀完整代碼。
了解了這塊基于注解進行權限控制的原理后,讀者朋友們也可以根據實際的業務需要進行相應的擴展。

擴展使用Spring EL表達式

假設現在內部有下面這樣一個接口,其中有一個query方法,接收一個參數type。這里我們簡化一點,假設只要接收這么一個參數,然后對應不同的取值時將返回不同的結果。

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

這個接口是對外開放的,通過對應的URL可以請求到該方法,我們定義了對應的Controller方法如下:

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

上面的接口服務在進行查詢的時候針對type是有權限的,不是每個用戶都可以使用每種type進行查詢的,需要擁有對應的權限才行。所以針對上面的處理器方法我們需要加上權限控制,而且在控制時需要的權限是隨著參數type動態變的。假設關于type的每項權限的定義是query:type的形式,比如type=1時需要的權限是query:1,type=2時需要的權限是query:2。在沒有與Spring整合時,我們會如下這樣做:

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

但是與Spring整合后,上面的做法耦合性強,我們會更希望通過整合后的注解來進行權限控制。對于上面的場景我們更希望通過@RequiresPermissions來指定需要的權限,但是@RequiresPermissions中定義的權限是靜態文本,固定的。它沒法滿足我們動態的需求。這個時候可能你會想著我們可以把Controller處理方法拆分為多個,單獨進行權限控制。比如下面這樣:

@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的取值范圍比較小的時候還可以,但是如果像上面這樣可能的取值有200種,把它們窮舉出來定義單獨的處理器方法并進行權限控制就顯得有點麻煩了。另外就是如果將來type的取值有變動,我們還得添加新的處理器方法。所以最好的辦法是讓@RequiresPermissions支持動態的權限定義,同時又可以維持靜態定義的支持。通過前面的分析我們知道,切入點是PermissionAnnotationHandler,而它里面是沒有提供對權限校驗的擴展的。我們如果想對它擴展簡單的辦法就是把它整體的替換。但是我們需要動態處理的權限是跟方法參數相關的,而PermissionAnnotationHandler中是取不到方法參數的,為此我們不能直接替換掉PermissionAnnotationHandler。PermissionAnnotationHandler是由PermissionAnnotationMethodInterceptor調用的,在其父類AuthorizingAnnotationMethodInterceptor的assertAuthorized方法中調用PermissionAnnotationHandler時是可以獲取到方法參數的。為此我們的擴展點就選在PermissionAnnotationMethodInterceptor類上,我們也需要把它整體的替換。Spring的EL表達式可以支持解析方法參數值,這里我們選擇引入Spring的EL表達式,在@RequiresPermissions定義權限時可以使用Spring EL表達式引入方法參數。同時為了兼顧靜態的文本。這里引入Spring的EL表達式模板。關于Spring的EL表達式模板可以參考筆者的這篇博文。我們定義自己的PermissionAnnotationMethodInterceptor,把它繼承自PermissionAnnotationMethodInterceptor,重寫assertAuthoried方法,方法的實現邏輯參考PermissionAnnotationHandler中的邏輯,但是所使用的@RequiresPermissions中的權限定義,是我們使用Spring EL表達式基于當前調用的方法作為EvaluationContext解析后的結果。以下是我們自己定義的PermissionAnnotationMethodInterceptor實現。

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表達式解析后的權限定義替換原來的權限定義   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后,我們需要替換原來的PermissionAnnotationMethodInterceptor為我們自己的PermissionAnnotationMethodInterceptor。根據前面介紹的Shiro整合Spring后使用@RequiresPermissions等注解的原理我們知道PermissionAnnotationMethodInterceptor是由AopAllianceAnnotationsAuthorizingMethodInterceptor指定的,而后者又是由AuthorizationAttributeSourceAdvisor指定的。為此我們需要在定義AuthorizationAttributeSourceAdvisor時通過顯示定義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"/>

為了演示前面示例的動態的權限,我們把角色與權限的關系調整如下,讓role1、role2和role3分別擁有query:1、query:2和query:3的權限。此時user1將擁有query:1和query:2的權限。

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

此時@RequiresPermissions中指定權限時就可以使用Spring EL表達式支持的語法了。因為我們在定義SelfPermissionAnnotationMethodInterceptor時已經指定了應用基于模板的表達式解析,此時權限中定義的文本都將作為文本解析,動態的部分默認需要使用#{前綴和}后綴包起來(這個前綴和后綴是可以指定的,但是默認就好)。在動態部分中可以使用#前綴引用變量,基于方法的表達式解析中可以使用參數名或p參數索引的形式引用方法參數。所以上面我們需要動態的權限的query方法的@RequiresPermissions定義如下。

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

這樣user1在訪問/service/1和/service/2是OK的,但是在訪問/service/3和/service/300時會提示沒有權限,因為user1沒有query:3和query:300的權限。

總結

以上所述是小編給大家介紹的Spring 整合Shiro 并擴展使用EL表達式的實例詳解,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對VeVb武林網網站的支持!


注:相關教程知識閱讀請移步到JAVA教程頻道。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
91中文字幕一区| 日本亚洲欧洲色α| 精品亚洲va在线va天堂资源站| 日韩电影中文字幕| 91高清视频免费| 欧美性xxxxxxxxx| 亚洲国产毛片完整版| 最近2019免费中文字幕视频三| 国产精品精品国产| 久久久久久久久久久久久久久久久久av| 91久久精品日日躁夜夜躁国产| 日韩专区在线播放| 久久久久久久久久国产| 91九色国产社区在线观看| 91精品国产免费久久久久久| 国产精品一区久久久| 国产一区二区三区丝袜| 色综合久久88| 中文字幕欧美国内| 亚洲精品视频中文字幕| 国精产品一区一区三区有限在线| 亚洲精品国产拍免费91在线| 国产成人精品一区二区| 日韩精品久久久久久久玫瑰园| 色妞欧美日韩在线| 欧美激情手机在线视频| 日韩av男人的天堂| 国产精品激情av电影在线观看| 亚洲电影免费观看高清| 国产精品美女网站| 亚洲精品一二区| 国产精品热视频| 成人网在线观看| 亚洲变态欧美另类捆绑| 久久人人爽人人爽爽久久| 日韩中文字在线| 日韩在线一区二区三区免费视频| 欧美视频在线免费| 国产日韩av在线播放| 丝袜亚洲另类欧美重口| 国产精品旅馆在线| 26uuu另类亚洲欧美日本老年| 欧美精品在线看| 亚洲伊人久久大香线蕉av| 啊v视频在线一区二区三区| 欧美另类极品videosbestfree| 庆余年2免费日韩剧观看大牛| 欧美国产日韩一区| 久久久最新网址| 91丨九色丨国产在线| 亚洲精品720p| 性视频1819p久久| 91人人爽人人爽人人精88v| 亚洲国产成人精品电影| 亚洲第一网中文字幕| 91在线观看免费| 亚洲国产精品电影在线观看| 国产视频久久久久久久| 亚洲精品国精品久久99热一| 国产97人人超碰caoprom| 日韩免费黄色av| 日日狠狠久久偷偷四色综合免费| 欧美成人网在线| 日韩国产精品一区| 国产精品成熟老女人| 26uuu国产精品视频| 欧美激情精品久久久久| 欧美日韩国产va另类| 欧洲日本亚洲国产区| 亚洲欧美日韩国产中文专区| 97热精品视频官网| 97香蕉超级碰碰久久免费软件| 2019最新中文字幕| 欧美在线国产精品| 日韩在线观看成人| 亚洲欧美国内爽妇网| 欧美片一区二区三区| 久久精品国产99国产精品澳门| 日韩免费精品视频| 欧美激情xxxxx| 亚洲国产高清自拍| 国产日韩av在线| 成人在线视频网| 久久天堂av综合合色| 中文字幕亚洲综合久久| 亚洲一区二区在线| 国产精品一区二区性色av| 久久夜色精品亚洲噜噜国产mv| 日韩中文娱乐网| 国产一区二区三区欧美| 日本精品久久久| 国产日韩欧美成人| 亚洲free嫩bbb| 欧美激情一区二区三区高清视频| www.欧美三级电影.com| www.久久色.com| 国产视频综合在线| 国产精品久久久久久久久免费| 欧美黄色成人网| 亚洲最大在线视频| 成人免费网站在线看| 久久色免费在线视频| 日本a级片电影一区二区| 国产精品大陆在线观看| 国产精品入口免费视频一| 在线a欧美视频| 亚洲电影免费观看| 国产精品视频地址| 国产综合香蕉五月婷在线| 中文字幕视频在线免费欧美日韩综合在线看| 亚洲精品美女网站| 日韩电影大全免费观看2023年上| 97久久伊人激情网| 欧美孕妇与黑人孕交| 国产一区私人高清影院| 国产精品偷伦免费视频观看的| 日本精品一区二区三区在线| 亚洲成**性毛茸茸| 日韩在线高清视频| 色播久久人人爽人人爽人人片视av| 夜夜躁日日躁狠狠久久88av| 日韩av中文字幕在线| 日本三级韩国三级久久| 中文字幕欧美日韩精品| 国产91在线播放九色快色| 国产精品jizz在线观看麻豆| 中日韩美女免费视频网址在线观看| 91在线观看欧美日韩| 国产精品久久久久免费a∨| 欧美中文字幕在线视频| 日本一区二区在线免费播放| 欧美亚洲伦理www| 亚洲精品一二区| 91欧美激情另类亚洲| 欧美精品在线视频观看| 欧美成人精品三级在线观看| 久久av在线看| 亚洲国产欧美久久| 亚洲精品av在线播放| 国产福利成人在线| 国产精品美女在线观看| 国产精品福利无圣光在线一区| 91久久久久久国产精品| 另类少妇人与禽zozz0性伦| 国产欧美亚洲精品| 久久久噜噜噜久久| 欧美久久精品一级黑人c片| 久久精品国产成人| 国产精品久久久久久久天堂| 插插插亚洲综合网| 精品国产91久久久久久| 国产精品免费看久久久香蕉| 国产一区二区在线免费视频| 欧美亚洲视频一区二区| 久久在线观看视频| 精品国产欧美成人夜夜嗨| 亚洲视屏在线播放| 日韩在线观看成人| 国产女人18毛片水18精品| 国产精品无av码在线观看| 亚洲男人天堂久| 欧美洲成人男女午夜视频| 欧美国产精品人人做人人爱| 日韩av在线电影网|