概述
前不久刚学会使用权限注解 (), 开始思索了一番. 最开始猜测实现方式是注解 @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 的理解.
来源: https://www.cnblogs.com/fzsyw/p/11384551.html