概述

前不久刚学会使用权限注解(),开始思索了一番。最开始猜测实现方式是注解@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包括两个主要的子类分别是NameMatchMethodPointcutAbstractRegexpMethodPointcut,前者提供简单字符串匹配方法前面,而后者使用正则表达式匹配方法前面。动态方法切点:DynamicMethodMatcerPointcut是动态方法切点的抽象基类,默认情况下它匹配所有的类,而且也已经过时,建议使用DefaultPointcutAdvisorDynamicMethodMatcherPointcut动态方法代替。另外还需关注构造器中的传入的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的理解。

最新文章

  1. 【BZOJ】2286: [Sdoi2011消耗战
  2. children和childNodes的区别
  3. uboot 链接地址与运行地址的区别
  4. 两个小的java程序,用于练习java基本语法
  5. android去掉标题栏
  6. python 继承和多态
  7. ios 相册相关
  8. JS实例(一)
  9. heapq
  10. 自己定义View之绘制圆环
  11. redis加入windows服务自启动
  12. 分享一本书&lt;&lt;谁都不敢欺负你&gt;&gt;
  13. 【css笔记(2)】如何给元素应用规则?
  14. 内核知识第八讲,PDE,PTE,页目录表,页表的内存管理
  15. 微信小程序scroll-view(或者其他view) 计算高度 px转rpx有关
  16. hdu 2159 FATE (二维完全背包)
  17. lua 语法的使用总结
  18. C++ static 静态变量&amp;静态成员函数
  19. php 公历阴历互相转换
  20. CTF-逆向工程实验吧Just Click

热门文章

  1. 完全平方数(C语言实现)
  2. dbo是默认用户也是架构
  3. asp core 配置用户密码验证
  4. 洛谷 P3367 并查集 【模板题】
  5. 【Android UI】侧滑栏的使用(HorizontalScrollView控件的使用)
  6. C语言入门9-1-分类函数
  7. C#中的Contains与IndexOf的区别
  8. Linux更换默认Security源
  9. ThreadPoolExecutor带来的性能问题
  10. flex布局知识总结