概述

前不久刚学会使用权限注解(),开始思索了一番。最开始猜测实现方式是注解@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的理解。

Shiro权限注解原理的更多相关文章

  1. Shiro 权限注解

      Shiro 权限注解:   Shiro 提供了相应的注解用于权限控制,如果使用这些注解就需要使用AOP 的功能来进行 判断,如Spring AOP:Shiro 提供了Spring AOP 集成用于 ...

  2. 2017.2.13 开涛shiro教程-第十二章-与Spring集成(二)shiro权限注解

    原博客地址:http://jinnianshilongnian.iteye.com/blog/2018398 根据下载的pdf学习. 第十二章-与Spring集成(二)shiro权限注解 shiro注 ...

  3. SpringBoot Shiro 权限注解不起作用

    最近在学习springboot结合shiro做权限管理时碰到一个问题. 问题如下: 我在userRealm中的doGetAuthorizationInfo方法中给用户添加了权限,然后在Controll ...

  4. Shiro 权限校验不通过时,区分GET和POST请求正确响应对应的方式

    引入:https://blog.csdn.net/catoop/article/details/69210140 本文基于Shiro权限注解方式来控制Controller方法是否能够访问. 例如使用到 ...

  5. Shiro权限管理框架(三):Shiro中权限过滤器的初始化流程和实现原理

    本篇是Shiro系列第三篇,Shiro中的过滤器初始化流程和实现原理.Shiro基于URL的权限控制是通过Filter实现的,本篇从我们注入的ShiroFilterFactoryBean开始入手,翻看 ...

  6. SpringMVC整合Shiro权限框架

    尊重原创:http://blog.csdn.net/donggua3694857/article/details/52157313 最近在学习Shiro,首先非常感谢开涛大神的<跟我学Shiro ...

  7. 类Shiro权限校验框架的设计和实现(2)--对复杂权限表达式的支持

    前言: 我看了下shiro好像默认不支持复杂表达式的权限校验, 它需要开发者自己去做些功能扩展的工作. 针对这个问题, 同时也会为了弥补上一篇文章提到的支持复杂表示需求, 特地尝试写一下解决方法. 本 ...

  8. 4.SSM配置shiro权限管理

    作者QQ:1095737364    QQ群:123300273     欢迎加入! 1.搭建SSM项目: http://www.cnblogs.com/yysbolg/p/6909021.html ...

  9. Shiro框架 (原理分析与简单实现)

    Shiro框架(原理分析与简单实现) 有兴趣的同学也可以阅读我之前分享的:Java权限管理(授权与认证)CRM权限管理   (PS : 这篇博客里面的实现方式没有使用框架,完全是手写的授权与认证,可以 ...

随机推荐

  1. linux 不重启识别新添加的硬盘

    1.fdisk -l 看有没有新的磁盘 oebiotech@hadoop08:/media/nbc9$ sudo fdisk -l |grep sdl 2.查看主机总线 oebiotech@hadoo ...

  2. HBaseCon Asia2019 会议总结

    一.首先会议流程. 1. The current status of HBase 2.The advantage and technology trend of HBase on the cloud ...

  3. kafka学习(二)-------- 什么是Kafka

    通过Kafka的快速入门 https://www.cnblogs.com/tree1123/p/11150927.html 能了解到Kafka的基本部署,使用,但他和其他的消息中间件有什么不同呢? K ...

  4. JavaScript知识点---->运算规则与运算(逻辑、位)

    *在js中不同类型之间的运算,所得到结果的类型也会有所变化: string + number = string string + boolean = string string + undefiend ...

  5. Android/AndroidStudio/idea使用教程之git使用(详细)(码云)

    已经安装好了AndroidStudio,安装教程 本教程是作者自己摸索出来的,有不足之处还请大家海涵.多多拍砖,互相学习. 第一步:下载git,安装git客户端 直接百度git,下载git ​ 安装g ...

  6. 集群之间配置 SSH无密码登录

    集群之间配置 SSH无密码登录 配置 ssh (1)基本语法 ssh 另一台电脑的 ip 地址 (2)ssh 连接时出现 Host key verification failed 的解决方法 # ss ...

  7. 【css系列】六种实现元素水平居中方法

    一.前言 居中效果在CSS中很是普通的效果,平时大家所看到的居中效果主要分为三大类:水平居中.垂直居中和水平垂直居中.而其中水平居中相对于后两者来说要简单得多.使用了css3的flexbox的属性轻松 ...

  8. 2017day2

    系统模块: # Author: sonny# -*- coding:utf-8 -*-import sys; #print(sys.path);print(sys.argv);print(sys.ar ...

  9. Vue的基本使用(三)

    1.过滤器 1.局部过滤器,在当前组件内部使用过滤器,给某些数据添油加醋. //声明 filters:{ "过滤器的名字":function(val,a,b){ //a就是alex ...

  10. vue教程(一)-html使用vue

    前后端分离.微服务框架是当下比较流行的词汇,而vue就是前端框架的佼佼者.下面重点介绍一下vue的用法: vue起步:1.引包    2.启动new Vue({el:目的地,template:模板内容 ...