Shiro权限注解原理
Posted fzsyw
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Shiro权限注解原理相关的知识,希望对你有一定的参考价值。
概述
前不久刚学会使用权限注解(),开始思索了一番。最开始猜测实现方式是注解@Aspect,具体实现方式类似如下所示(切面记录审计日志)。后来发现并非如此,所以特地分析一下源码。
@Component
@Aspect
public class AuditLogAspectConfig
@Pointcut("@annotation(com.ygsoft.ecp.mapp.basic.audit.annotation.AuditLog) || @annotation(com.ygsoft.ecp.mapp.basic.audit.annotation.AuditLogs)")
public void pointcut()
@After(value="pointcut()")
public void after(JoinPoint joinPoint)
//执行的逻辑
...
权限注解的源码分析
DefaultAdvisorAutoProxyCreator
这个类实现了BeanProcessor
接口,当ApplicationContext
读取所有的Bean配置信息后,这个类将扫描上下文,寻找所有的Advistor
(一个Advisor
是一个切入点和一个通知的组成),将这些Advisor
应用到所有符合切入点的Bean中。
@Configuration
public class ShiroAnnotationProcessorConfiguration extends AbstractShiroAnnotationProcessorConfiguration
@Bean
@DependsOn("lifecycleBeanPostProcessor")
protected DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator()
return super.defaultAdvisorAutoProxyCreator();
@Bean
protected AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager)
return super.authorizationAttributeSourceAdvisor(securityManager);
AuthorizationAttributeSourceAdvisor
继承了StaticMethodMatcherPointcutAdvisor
,如下代码所示,只匹配五个注解,也就是说只对这五个注解标注的类或者方法增强。StaticMethodMatcherPointcutAdvisor
是静态方法切点的抽象基类,默认情况下它匹配所有的类。StaticMethodMatcherPointcut
包括两个主要的子类分别是NameMatchMethodPointcut
和AbstractRegexpMethodPointcut
,前者提供简单字符串匹配方法前面,而后者使用正则表达式匹配方法前面。动态方法切点:DynamicMethodMatcerPointcut
是动态方法切点的抽象基类,默认情况下它匹配所有的类,而且也已经过时,建议使用DefaultPointcutAdvisor
和DynamicMethodMatcherPointcut
动态方法代替。另外还需关注构造器中的传入的AopAllianceAnnotationsAuthorizingMethodInterceptor
。
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;
if ( targetClass != null)
try
m = targetClass.getMethod(m.getName(), m.getParameterTypes());
if ( isAuthzAnnotationPresent(m) )
return true;
catch (NoSuchMethodException ignored)
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
在初始化时,interceptors
添加了5个方法拦截器(都继承自AuthorizingAnnotationMethodInterceptor
),这5个拦截器分别对5种权限验证的方法进行拦截,执行invoke方法。
public class AopAllianceAnnotationsAuthorizingMethodInterceptor
extends AnnotationsAuthorizingMethodInterceptor implements MethodInterceptor
public AopAllianceAnnotationsAuthorizingMethodInterceptor()
List<AuthorizingAnnotationMethodInterceptor> interceptors =
new ArrayList<AuthorizingAnnotationMethodInterceptor>(5);
AnnotationResolver resolver = new SpringAnnotationResolver();
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);
public Object invoke(MethodInvocation methodInvocation) throws Throwable
org.apache.shiro.aop.MethodInvocation mi = createMethodInvocation(methodInvocation);
return super.invoke(mi);
...
AopAllianceAnnotationsAuthorizingMethodInterceptor
的invoke方法,又会调用超类AuthorizingMethodInterceptor
的invoke方法,在该方法中先执行assertAuthorized方法,进行权限校验,校验不通过,抛出AuthorizationException
异常,中断方法;校验通过,则执行methodInvocation.proceed()
,该方法也就是被拦截并且需要权限校验的方法。
public abstract class AuthorizingMethodInterceptor extends MethodInterceptorSupport
public Object invoke(MethodInvocation methodInvocation) throws Throwable
assertAuthorized(methodInvocation);
return methodInvocation.proceed();
protected abstract void assertAuthorized(MethodInvocation methodInvocation) throws AuthorizationException;
assertAuthorized方法最终执行的还是AuthorizingAnnotationMethodInterceptor.assertAuthorized
,而AuthorizingAnnotationMethodInterceptor
有5中的具体的实现类(RoleAnnotationMethodInterceptor
, PermissionAnnotationMethodInterceptor
, AuthenticatedAnnotationMethodInterceptor
, UserAnnotationMethodInterceptor
, GuestAnnotationMethodInterceptor
)。
public abstract class AnnotationsAuthorizingMethodInterceptor extends AuthorizingMethodInterceptor
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);
...
AuthorizingAnnotationMethodInterceptor
的assertAuthorized,首先从子类获取AuthorizingAnnotationHandler
,再调用该实现类的assertAuthorized
方法。
public abstract class AuthorizingAnnotationMethodInterceptor extends AnnotationMethodInterceptor
public AuthorizingAnnotationMethodInterceptor( AuthorizingAnnotationHandler handler )
super(handler);
public AuthorizingAnnotationMethodInterceptor( AuthorizingAnnotationHandler handler,
AnnotationResolver resolver)
super(handler, resolver);
public Object invoke(MethodInvocation methodInvocation) throws Throwable
assertAuthorized(methodInvocation);
return methodInvocation.proceed();
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;
现在分析其中一种实现类PermissionAnnotationMethodInterceptor
,也是用的最多的,但是这个类的实际代码很少,很明显上述分析的getHandler在PermissionAnnotationMethodInterceptor
中返回值为PermissionAnnotationHandler
。
public class PermissionAnnotationMethodInterceptor extends AuthorizingAnnotationMethodInterceptor
public PermissionAnnotationMethodInterceptor()
super( new PermissionAnnotationHandler() );
public PermissionAnnotationMethodInterceptor(AnnotationResolver resolver)
super( new PermissionAnnotationHandler(), resolver);
在PermissionAnnotationHandler
类中,终于发现实际的检验逻辑,还是调用的Subject.checkPermission()
进行校验。
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()))
boolean hasAtLeastOnePermission = false;
for (String permission : perms) if (getSubject().isPermitted(permission)) hasAtLeastOnePermission = true;
if (!hasAtLeastOnePermission) getSubject().checkPermission(perms[0]);
实现类似编程式AOP
定义一个注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Log
String value() default "";
继承StaticMethodMatcherPointcutAdvisor
类,并实现相关的方法。
@SuppressWarnings("serial")
@Component
public class HelloAdvisor extends StaticMethodMatcherPointcutAdvisor
public HelloAdvisor()
setAdvice(new LogMethodInterceptor());
public boolean matches(Method method, Class targetClass)
Method m = method;
if ( isAuthzAnnotationPresent(m) )
return true;
if ( targetClass != null)
try
m = targetClass.getMethod(m.getName(), m.getParameterTypes());
return isAuthzAnnotationPresent(m);
catch (NoSuchMethodException ignored)
return false;
private boolean isAuthzAnnotationPresent(Method method)
Annotation a = AnnotationUtils.findAnnotation(method, Log.class);
return a!= null;
实现MethodInterceptor
接口,定义切面处理的逻辑
public class LogMethodInterceptor implements MethodInterceptor
public Object invoke(MethodInvocation invocation) throws Throwable
Log log = invocation.getMethod().getAnnotation(Log.class);
System.out.println("log: "+log.value());
return invocation.proceed();
定义一个测试类,并添加Log注解
@Component
public class TestHello
@Log("test log")
public String say()
return "ss";
编写启动类,并且配置DefaultAdvisorAutoProxyCreator
@Configuration
public class TestBoot
public static void main(String[] args)
ApplicationContext ctx = new AnnotationConfigApplicationContext("com.fzsyw.test");
TestHello th = ctx.getBean(TestHello.class);
System.out.println(th.say());
@Bean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator()
DefaultAdvisorAutoProxyCreator da = new DefaultAdvisorAutoProxyCreator();
da.setProxyTargetClass(true);
return da;
最终打印的结果如下,证明编程式的AOP生效。
log: test log
ss
总结与思考
Shiro的注解式权限,使用确实方便,通过源码也分析了它的实现原理,比较核心的是配置DefaultAdvisorAutoProxyCreator
和继承StaticMethodMatcherPointcutAdvisor
。其中的5中权限注解,使用了统一一套代码架构,用到了的模板模式,方便扩展。最后自己也简单做了一个小例子,加深对编程式AOP的理解。
以上是关于Shiro权限注解原理的主要内容,如果未能解决你的问题,请参考以下文章
shiro框架 RequiresPermissions注解怎么动态配置
shiro框架 RequiresPermissions注解怎么动态配置