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 权限注解
Shiro 权限注解: Shiro 提供了相应的注解用于权限控制,如果使用这些注解就需要使用AOP 的功能来进行 判断,如Spring AOP:Shiro 提供了Spring AOP 集成用于 ...
- 2017.2.13 开涛shiro教程-第十二章-与Spring集成(二)shiro权限注解
原博客地址:http://jinnianshilongnian.iteye.com/blog/2018398 根据下载的pdf学习. 第十二章-与Spring集成(二)shiro权限注解 shiro注 ...
- SpringBoot Shiro 权限注解不起作用
最近在学习springboot结合shiro做权限管理时碰到一个问题. 问题如下: 我在userRealm中的doGetAuthorizationInfo方法中给用户添加了权限,然后在Controll ...
- Shiro 权限校验不通过时,区分GET和POST请求正确响应对应的方式
引入:https://blog.csdn.net/catoop/article/details/69210140 本文基于Shiro权限注解方式来控制Controller方法是否能够访问. 例如使用到 ...
- Shiro权限管理框架(三):Shiro中权限过滤器的初始化流程和实现原理
本篇是Shiro系列第三篇,Shiro中的过滤器初始化流程和实现原理.Shiro基于URL的权限控制是通过Filter实现的,本篇从我们注入的ShiroFilterFactoryBean开始入手,翻看 ...
- SpringMVC整合Shiro权限框架
尊重原创:http://blog.csdn.net/donggua3694857/article/details/52157313 最近在学习Shiro,首先非常感谢开涛大神的<跟我学Shiro ...
- 类Shiro权限校验框架的设计和实现(2)--对复杂权限表达式的支持
前言: 我看了下shiro好像默认不支持复杂表达式的权限校验, 它需要开发者自己去做些功能扩展的工作. 针对这个问题, 同时也会为了弥补上一篇文章提到的支持复杂表示需求, 特地尝试写一下解决方法. 本 ...
- 4.SSM配置shiro权限管理
作者QQ:1095737364 QQ群:123300273 欢迎加入! 1.搭建SSM项目: http://www.cnblogs.com/yysbolg/p/6909021.html ...
- Shiro框架 (原理分析与简单实现)
Shiro框架(原理分析与简单实现) 有兴趣的同学也可以阅读我之前分享的:Java权限管理(授权与认证)CRM权限管理 (PS : 这篇博客里面的实现方式没有使用框架,完全是手写的授权与认证,可以 ...
随机推荐
- 20190101.DDD笔记
建立领域模型步骤 根据提供的信息完善主要业务场景和业务流程: 根据业务流程识别领域事件并按照时序排列: 针对领域事件进行命令识别: 针对领域事件和命令进行聚合和子域的初步识别: 在识别的subdoma ...
- Excel催化剂开源第45波-按原图大小导出图片
从Excel中导出图片,是一个很常规的需求,也有一些久旧不衰的界面操作法小技巧从OpenXml文件中批量导出,在VBA开发中,也会使用Chart对象的背景图的技巧来导出.总体来说,和真正想要的效果还是 ...
- [leetcode] 19. Remove Nth Node From End of List (Medium)
原题链接 删除单向链表的倒数第n个结点. 思路: 用两个索引一前一后,同时遍历,当后一个索引值为null时,此时前一个索引表示的节点即为要删除的节点. Runtime: 13 ms, faster t ...
- 基于ng-zorro的ASP.NET ZERO前端实现之代码生成器
上一篇介绍了集成ng-zorro的过程,本篇我们来看下如何用abp官方的生成器来生成前端代码. Abp官方提供了一个强大的代码生成器 ASP.NET Zero Power Tools,它的Visual ...
- 前端响应式のmedia文件分离
响应式cssのmedia文件分离 media简介 1.媒体查询,添加自CSS3 2.一个媒体查询由一个可选的媒体类型和零个或多个使用媒体功能的限制了样式表范围的表达式组成,允许内容的呈现针对一个特定范 ...
- Flutter学习笔记(11)--文本组件、图标及按钮组件
如需转载,请注明出处:Flutter学习笔记(10)--容器组件.图片组件 文本组件 文本组件(text)负责显示文本和定义显示样式,下表为text常见属性 Text组件属性及描述 属性名 类型 默认 ...
- python List交集、并集、差集
工作中遇到了求两个集合的差集,但是集合集合中包含字典,所以使用difference方法会报错,看了一些别人的博客,整理了一下. 1. 获取两个list 的交集print list(set(a).int ...
- android 界面提示框架WisdomProgressHUD,为金典而生
一:简述 今天给android开发者们,推荐一个金典的界面提示框架WisdomProgressHUD,使用简洁方便. WisdomProgressHUD 是一个半透明的 HUD 指示器. Wisdom ...
- 基于SpringBoot从零构建博客网站 - 新增创建、修改、删除专栏功能
守望博客是支持创建专栏的功能,即可以将一系列相关的文章归档到专栏中,方便用户管理和查阅文章.这里主要讲解专栏的创建.修改和删除功能,至于专栏还涉及其它的功能,例如关注专栏等后续会穿插着介绍. 1.创建 ...
- 解决axios接收二进制流文件乱码问题
1. 须将axios 配置中的responseType设置为'arraybuffer',这样就不会让表格出现乱码现象: 2. 如果要动态设置文件名则需要让后台将名字设置到响应头中,否则将是一个乱码的文 ...