前言 shiro为我们提供了几个权限注解,如下图:
文章图片
这几个注解原理都类似,这里我们讲解@RequiresPermissions的原理。
铺垫 第一
首先要清楚@RequiresPermissions的基本用法。就是在Controller的方法里,加上@RequiresPermissions注解,并写上权限标识。那么,有该权限标识的用户,才能访问到请求。如下图:
文章图片
第二
先剧透一下,@RequiresPermissions注解的原理是使用了Spring的AOP进行了增强。来判断当前用户是否有该权限标识。我们复习一下Spring的AOP原理。AOP的核心流程是ReflectiveMethodInvocation类中的proceed方法。该方法会遍历加强集合,执行每一个加强的方法。不熟悉的可以查看这篇博客:《Spring之Joinpoint类详解》
执行流程分析 有了上面的铺垫,我们开始从源码分析其执行流程。
首先,断点打到ReflectiveMethodInvocation类的proceed方法这里,因为要对代理对象进行增强,必定要走这里。然后发送请求,进入断点,我们先看加强链上有哪些元素:
文章图片
第一个Expose开头的对象,是Spring自己生成的元素,我们无需关注。第二个元素,就是加强@RequiresPermissions而生成的元素。看该类的源码:
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements.See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership.The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License");
you may not use this file except in compliance
* with the License.You may obtain a copy of the License at
*
*http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied.See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.shiro.spring.security.interceptor;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.shiro.aop.AnnotationResolver;
import org.apache.shiro.authz.aop.*;
import org.apache.shiro.spring.aop.SpringAnnotationResolver;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
/**
* Allows Shiro Annotations to work in any AOP Alliance
* specific implementation environment (for example, Spring).
*
* @since 0.2
*/
public class AopAllianceAnnotationsAuthorizingMethodInterceptor
extends AnnotationsAuthorizingMethodInterceptor implements MethodInterceptor {public AopAllianceAnnotationsAuthorizingMethodInterceptor() {
List interceptors =
new ArrayList(5);
//use a Spring-specific Annotation resolver - Spring's AnnotationUtils is nicer than the
//raw JDK resolution process.
AnnotationResolver resolver = new SpringAnnotationResolver();
//we can re-use the same resolver instance - it does not retain state:
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);
}
/**
* Creates a {@link MethodInvocation MethodInvocation} that wraps an
* {@link org.aopalliance.intercept.MethodInvocation org.aopalliance.intercept.MethodInvocation} instance,
* enabling Shiro Annotations in AOP Alliance environments
* (Spring, etc).
*
* @param implSpecificMethodInvocation AOP Alliance {@link org.aopalliance.intercept.MethodInvocation MethodInvocation}
* @return a Shiro {@link MethodInvocation MethodInvocation} instance that wraps the AOP Alliance instance.
*/
protected org.apache.shiro.aop.MethodInvocation createMethodInvocation(Object implSpecificMethodInvocation) {
final MethodInvocation mi = (MethodInvocation) implSpecificMethodInvocation;
return new org.apache.shiro.aop.MethodInvocation() {
public Method getMethod() {
return mi.getMethod();
}public Object[] getArguments() {
return mi.getArguments();
}public String toString() {
return "Method invocation [" + mi.getMethod() + "]";
}public Object proceed() throws Throwable {
return mi.proceed();
}public Object getThis() {
return mi.getThis();
}
};
}/**
* Simply casts the method argument to an
* {@link org.aopalliance.intercept.MethodInvocation org.aopalliance.intercept.MethodInvocation} and then
* calls methodInvocation.{@link org.aopalliance.intercept.MethodInvocation#proceed proceed}()
*
* @param aopAllianceMethodInvocation the {@link org.aopalliance.intercept.MethodInvocation org.aopalliance.intercept.MethodInvocation}
* @return the {@link org.aopalliance.intercept.MethodInvocation#proceed() org.aopalliance.intercept.MethodInvocation.proceed()} method call result.
* @throws Throwable if the underlying AOP Alliance proceed()
call throws a Throwable
.
*/
protected Object continueInvocation(Object aopAllianceMethodInvocation) throws Throwable {
MethodInvocation mi = (MethodInvocation) aopAllianceMethodInvocation;
return mi.proceed();
}/**
* Creates a Shiro {@link MethodInvocation MethodInvocation} instance and then immediately calls
* {@link org.apache.shiro.authz.aop.AuthorizingMethodInterceptor#invoke super.invoke}.
*
* @param methodInvocation the AOP Alliance-specific methodInvocation
instance.
* @return the return value from invoking the method invocation.
* @throws Throwable if the underlying AOP Alliance method invocation throws a Throwable
.
*/
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
org.apache.shiro.aop.MethodInvocation mi = createMethodInvocation(methodInvocation);
return super.invoke(mi);
}
}
可以看出,这个类实现了MethodInterceptor接口。那么,其invoke方法,就是对原对象方法的增强。点进invoke方法,最后调到了AnnotationsAuthorizingMethodInterceptor类的assertAuthorized方法,如下:
protected void assertAuthorized(MethodInvocation methodInvocation) throws AuthorizationException {
//default implementation just ensures no deny votes are cast:
Collection aamis = getMethodInterceptors();
if (aamis != null && !aamis.isEmpty()) {
for (AuthorizingAnnotationMethodInterceptor aami : aamis) {
if (aami.supports(methodInvocation)) {
aami.assertAuthorized(methodInvocation);
}
}
}
}
在这里,会遍历是否有前言中截图的那几个注解,如果有的话,则调用相应的注解Handler去处理相应的逻辑。@RequiresPermissions的Handler的处理逻辑就是调用Realm中定义的权限获取方法,判断是否有注解中的标识,具体的调用Realm流程不再解析,前面博客已经解析过。
这样,就对@RequiresPermissions注解的方法进行了增强。
@RequiresPermissions切面 使用AOP,一定要有切面,这样Spring在实例化bean过程中,才会生成代理对象和加强链。Shiro的切面是AuthorizationAttributeSourceAdvisor类,需要加入Spring容器中管理,配置如下:
/**
* 开启Shiro注解通知器
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(
@Qualifier("securityManager") SecurityManager securityManager)
{
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
下面分析这个类源码。前面博客说过,Advisor切面的两大核心就是切点pointcut和增强advice。我们看AuthorizationAttributeSourceAdvisor 的这两个核心在哪儿:
/**
* Create a new AuthorizationAttributeSourceAdvisor.
*/
public AuthorizationAttributeSourceAdvisor() {
setAdvice(new AopAllianceAnnotationsAuthorizingMethodInterceptor());
}
这里设置了增强advice。正是我们上面分析的AopAllianceAnnotationsAuthorizingMethodInterceptor类。
public boolean matches(Method method, Class targetClass) {
Method m = method;
if ( isAuthzAnnotationPresent(m) ) {
return true;
}//The 'method' parameter could be from an interface that doesn't have the annotation.
//Check to see if the implementation has it.
if ( targetClass != null) {
try {
m = targetClass.getMethod(m.getName(), m.getParameterTypes());
return isAuthzAnnotationPresent(m) || isAuthzAnnotationPresent(targetClass);
} catch (NoSuchMethodException ignored) {
//default return value is false.If we can't find the method, then obviously
//there is no annotation, so just use the default return value.
}
}return false;
}
matches方法定义了切点。就是在对@RequiresPermissions等注解收集,然后判断是不是切点。这样,切面就分析完了。
总结 【shiro|Shiro之@RequiresPermissions注解原理详解】shiro定义了AuthorizationAttributeSourceAdvisor 切面,在切面里,定义了AopAllianceAnnotationsAuthorizingMethodInterceptor增强,来查询Realm中用户的权限等信息,判断是否和参数中的数据匹配。定义了matches方法,来收集@RequiresPermissions等注解。
在Spring实例化Bean时,执行BeanPostProcessor的postProcessAfterInitialization方法生成代理对象时(循环依赖的情况除外),就会找到shiro定义的切面,生成相应的代理对象,当执行方法时,就会走增强的方法,进行方法增强。
所以对于shiro框架的使用者而言,只需将AuthorizationAttributeSourceAdvisor 切面加入到Spring容器,然后在Realm中定义用户的权限查询方法。然后在需要的方法上加@RequiresPermissions注解,就可以进行权限的控制了。
推荐阅读
- Shiro之保存Session到数据库中-yellowcong
- √|shiro教程(session管理)
- Shiro入门-session管理
- servlet总结|设置session失效时间(不使用框架)----使用shiro设置session失效时间(使用shiro框架)
- shiro|shiro中session实现的简单分析
- Shiro 设置session超时时间
- springboot+shiro入门学习(一)
- Shiro|shiro中的session 获取过期时间/设置过期时间
- Shiro 之Subject、SecurityManager、Realm源码分析