Shiro权限注解原理
2019-08-20 17:55
459 查看
概述
前不久刚学会使用权限注解(),开始思索了一番。最开始猜测实现方式是注解@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的理解。
相关文章推荐
- SpringMVC结合Shiro注解实现权限控制原理分析
- aop:config在shiro权限注解中发挥的作用
- spring shiro权限注解方式验证;
- Shiro Springmvc 权限注解不生效
- Shiro入门之二 --------基于注解方式的权限控制与Ehcache缓存
- shiro与springMVC整合时有关权限注解失效原因
- shiro原理的分析,系统权限管理以及 运行流程分析
- shiro注解权限控制-5个权限注解
- 【Shiro权限管理】17.Shiro权限注解
- 2017.2.13 开涛shiro教程-第十二章-与Spring集成(二)shiro权限注解
- spring shiro权限注解方式验证;
- spring对shiro注解支持的原理
- shiro注解权限控制-5个权限注解
- Shiro权限注解
- Spring Boot + Spring Cloud 实现权限管理系统 后端篇(二十四):权限控制(Shiro 注解)
- ssh项目整合shiro时,在struts2的action中使用shiro的注解进行权限控制时,NoSuchMethodException:com.sun.proxy.$Proxy26
- shiro-权限注解
- shiro注解更改角色权限认证方式,和和或
- shiro注解权限控制-5个权限注解
- springboot shiro注解无法使用及权限异常500跳转到401