Shiro之@RequiresPermissions注解原理详解
Posted 敲代码的小小酥
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Shiro之@RequiresPermissions注解原理详解相关的知识,希望对你有一定的参考价值。
前言
shiro为我们提供了几个权限注解,如下图:
这几个注解原理都类似,这里我们讲解@RequiresPermissions的原理。
铺垫
第一
首先要清楚@RequiresPermissions的基本用法。就是在Controller的方法里,加上@RequiresPermissions注解,并写上权限标识。那么,有该权限标识的用户,才能访问到请求。如下图:
第二
先剧透一下,@RequiresPermissions注解的原理是使用了Spring的AOP进行了增强。来判断当前用户是否有该权限标识。我们复习一下Spring的AOP原理。AOP的核心流程是ReflectiveMethodInvocation类中的proceed方法。该方法会遍历加强集合,执行每一个加强的方法。不熟悉的可以查看这篇博客:《Spring之Joinpoint类详解》
执行流程分析
有了上面的铺垫,我们开始从源码分析其执行流程。
首先,断点打到ReflectiveMethodInvocation类的proceed方法这里,因为要对代理对象进行增强,必定要走这里。然后发送请求,进入断点,我们先看加强链上有哪些元素:
第一个Expose开头的对象,是Spring自己生成的元素,我们无需关注。第二个元素,就是加强@RequiresPermissions而生成的元素。看该类的源码:
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.shiro.spring.security.interceptor;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.shiro.aop.AnnotationResolver;
import org.apache.shiro.authz.aop.*;
import org.apache.shiro.spring.aop.SpringAnnotationResolver;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
/**
* Allows Shiro Annotations to work in any <a href="http://aopalliance.sourceforge.net/">AOP Alliance</a>
* specific implementation environment (for example, Spring).
*
* @since 0.2
*/
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);
/**
* Creates a @link MethodInvocation MethodInvocation that wraps an
* @link org.aopalliance.intercept.MethodInvocation org.aopalliance.intercept.MethodInvocation instance,
* enabling Shiro Annotations in <a href="http://aopalliance.sourceforge.net/">AOP Alliance</a> environments
* (Spring, etc).
*
* @param implSpecificMethodInvocation AOP Alliance @link org.aopalliance.intercept.MethodInvocation MethodInvocation
* @return a Shiro @link MethodInvocation MethodInvocation instance that wraps the AOP Alliance instance.
*/
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();
;
/**
* Simply casts the method argument to an
* @link org.aopalliance.intercept.MethodInvocation org.aopalliance.intercept.MethodInvocation and then
* calls <code>methodInvocation.@link org.aopalliance.intercept.MethodInvocation#proceed proceed()</code>
*
* @param aopAllianceMethodInvocation the @link org.aopalliance.intercept.MethodInvocation org.aopalliance.intercept.MethodInvocation
* @return the @link org.aopalliance.intercept.MethodInvocation#proceed() org.aopalliance.intercept.MethodInvocation.proceed() method call result.
* @throws Throwable if the underlying AOP Alliance <code>proceed()</code> call throws a <code>Throwable</code>.
*/
protected Object continueInvocation(Object aopAllianceMethodInvocation) throws Throwable
MethodInvocation mi = (MethodInvocation) aopAllianceMethodInvocation;
return mi.proceed();
/**
* Creates a Shiro @link MethodInvocation MethodInvocation instance and then immediately calls
* @link org.apache.shiro.authz.aop.AuthorizingMethodInterceptor#invoke super.invoke.
*
* @param methodInvocation the AOP Alliance-specific <code>methodInvocation</code> instance.
* @return the return value from invoking the method invocation.
* @throws Throwable if the underlying AOP Alliance method invocation throws a <code>Throwable</code>.
*/
public Object invoke(MethodInvocation methodInvocation) throws Throwable
org.apache.shiro.aop.MethodInvocation mi = createMethodInvocation(methodInvocation);
return super.invoke(mi);
可以看出,这个类实现了MethodInterceptor接口。那么,其invoke方法,就是对原对象方法的增强。点进invoke方法,最后调到了AnnotationsAuthorizingMethodInterceptor类的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);
在这里,会遍历是否有前言中截图的那几个注解,如果有的话,则调用相应的注解Handler去处理相应的逻辑。@RequiresPermissions的Handler的处理逻辑就是调用Realm中定义的权限获取方法,判断是否有注解中的标识,具体的调用Realm流程不再解析,前面博客已经解析过。
这样,就对@RequiresPermissions注解的方法进行了增强。
@RequiresPermissions切面
使用AOP,一定要有切面,这样Spring在实例化bean过程中,才会生成代理对象和加强链。Shiro的切面是AuthorizationAttributeSourceAdvisor类,需要加入Spring容器中管理,配置如下:
/**
* 开启Shiro注解通知器
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(
@Qualifier("securityManager") SecurityManager securityManager)
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
下面分析这个类源码。前面博客说过,Advisor切面的两大核心就是切点pointcut和增强advice。我们看AuthorizationAttributeSourceAdvisor 的这两个核心在哪儿:
/**
* Create a new AuthorizationAttributeSourceAdvisor.
*/
public AuthorizationAttributeSourceAdvisor()
setAdvice(new AopAllianceAnnotationsAuthorizingMethodInterceptor());
这里设置了增强advice。正是我们上面分析的AopAllianceAnnotationsAuthorizingMethodInterceptor类。
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;
matches方法定义了切点。就是在对@RequiresPermissions等注解收集,然后判断是不是切点。这样,切面就分析完了。
总结
shiro定义了AuthorizationAttributeSourceAdvisor 切面,在切面里,定义了AopAllianceAnnotationsAuthorizingMethodInterceptor增强,来查询Realm中用户的权限等信息,判断是否和参数中的数据匹配。定义了matches方法,来收集@RequiresPermissions等注解。
在Spring实例化Bean时,执行BeanPostProcessor的postProcessAfterInitialization方法生成代理对象时(循环依赖的情况除外),就会找到shiro定义的切面,生成相应的代理对象,当执行方法时,就会走增强的方法,进行方法增强。
所以对于shiro框架的使用者而言,只需将AuthorizationAttributeSourceAdvisor 切面加入到Spring容器,然后在Realm中定义用户的权限查询方法。然后在需要的方法上加@RequiresPermissions注解,就可以进行权限的控制了。
以上是关于Shiro之@RequiresPermissions注解原理详解的主要内容,如果未能解决你的问题,请参考以下文章
Shiro系列之Shiro+Spring MVC整合(Integration)
Shiro系列之Shiro+Spring MVC整合(Integration)
Shiro系列之Shiro+Spring MVC整合(Integration)
Shiro系列之Shiro+Mysql实现用户授权(Authorization)