目录
2.2.1 增强实现类AopAllianceAnnotationsAuthorizingMethodInterceptor
Shiro作为一款比较流行的登录认证、访问控制安全框架,被广泛应用在程序员社区;Shiro登录验证、访问控制、Session管理等流程内部都是委托给SecurityManager安全管理器来完成的,SecurityManager安全管理器前面文章已经进行了详细解析,详见:Shiro框架:Shiro SecurityManager安全管理器解析;在此基础上,前面文章已对Shiro用户登录认证流程(详见:Shiro框架:Shiro登录认证流程源码解析)进行了源码跟踪,Shiro框架:Shiro用户访问控制鉴权流程-内置过滤器方式源码解析?也对用户访问控制鉴权流程-内置过滤器方式进行了详细解析,本篇文章继续对用户访问控制鉴权流程-Aop注解方式进行源码解析,了解不同的使用方式以便更好的应用到实际项目中;
想要深入了解Shiro框架整体原理,可移步:
Shiro的权限校验逻辑是嵌入到Spring Aop的增强逻辑中进行执行的,下面先看一下Shiro与Spring Aop是如何结合的,也即对Shiro校验逻辑在Spring Aop的嵌入点进行分析;
Shiro的校验逻辑应用到了Aop框架的抽象类StaticMethodMatcherPointcutAdvisor,该类的继承层次结构如下:
可以看出,其实现了切点Pointcut和切面Advisor 2个顶层接口,并通过实现方法匹配接口MethodMatcher定位切点,进而执行增强逻辑;
在Shiro框架中,实现StaticMethodMatcherPointcutAdvisor的具体子类为AopAllianceAnnotationsAuthorizingMethodInterceptor,其同时实现了Shiro框架的切点和增强逻辑,下面进行具体分析;
对Spring Aop的实现逻辑感兴趣,可以参考之前的文章:
Shiro框架的Aop切面逻辑主要包含了2部分:
切点(Pointcut):Shiro如何通过自定义注解方式捕捉切点
增强逻辑(Advice):Shiro如何通过增强逻辑执行内部校验过程
Shiro的切点和增强逻辑定义在AuthorizationAttributeSourceAdvisor切面类中的,其类图如下:
可以看出,其继承了StaticMethodMatcherPointcutAdvisor类,并植入了切点和增强逻辑,下面分别进行解析;
AuthorizationAttributeSourceAdvisor类中,是通过实现接口MethodMatcher的方法matches引入切点的,具体实现如下:
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;
}
private boolean isAuthzAnnotationPresent(Class<?> targetClazz) {
for( Class<? extends Annotation> annClass : AUTHZ_ANNOTATION_CLASSES ) {
Annotation a = AnnotationUtils.findAnnotation(targetClazz, annClass);
if ( a != null ) {
return true;
}
}
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;
}
如上,通过判断Class级别和Method级别是否包含注解AUTHZ_ANNOTATION_CLASSES,来判断是否命中切点;
这里AUTHZ_ANNOTATION_CLASSES的定义如下:
private static final Class<? extends Annotation>[] AUTHZ_ANNOTATION_CLASSES =
new Class[] {
RequiresPermissions.class, RequiresRoles.class,
RequiresUser.class, RequiresGuest.class, RequiresAuthentication.class
};
这里的注解都是Shiro框架内置的自定义注解,比如RequiresPermissions注解用于用户权限校验,RequiresRoles注解用于用户角色校验,RequiresAuthentication注解校验用户登录状态;
因此Shiro通过自定义注解的方式实现了Aop切点;
Shiro的增强逻辑同样在类AuthorizationAttributeSourceAdvisor中,在构造方法中通过setAdvice指定了增强实现类,如下:
/**
* Create a new AuthorizationAttributeSourceAdvisor.
*/
public AuthorizationAttributeSourceAdvisor() {
setAdvice(new AopAllianceAnnotationsAuthorizingMethodInterceptor());
}
在类AopAllianceAnnotationsAuthorizingMethodInterceptor中,实现了是如何进行增强逻辑处理的,下面进行具体分析;
AopAllianceAnnotationsAuthorizingMethodInterceptor类图如下,
左侧类层次结构是Shiro内部的实现,用于实现Shiro的增强逻辑
右侧类层次结构是Spring Aop中实现增强的切入点,这里实现了MethodInterceptor方法拦截器接口,其内部通过引入的invoke方式执行增强逻辑:
public interface MethodInterceptor extends Interceptor {
/**
* Implement this method to perform extra treatments before and
* after the invocation. Polite implementations would certainly
* like to invoke {@link Joinpoint#proceed()}.
* @param invocation the method invocation joinpoint
* @return the result of the call to {@link Joinpoint#proceed()};
* might be intercepted by the interceptor
* @throws Throwable if the interceptors or the target object
* throws an exception
*/
Object invoke(MethodInvocation invocation) throws Throwable;
}
下面主要分析下AopAllianceAnnotationsAuthorizingMethodInterceptor是如何实现invoke方法的;
增强方法的实现如下:
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
org.apache.shiro.aop.MethodInvocation mi = createMethodInvocation(methodInvocation);
return super.invoke(mi);
}
这里创建了Shiro内部自定义的MethodInvocation,然后调用父类invoke方法,createMethodInvocation实现如下:
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();
}
};
}
父类AuthorizingMethodInterceptor的invoke方法实现如下:
public abstract class AuthorizingMethodInterceptor extends MethodInterceptorSupport {
/**
* Invokes the specified method (<code>methodInvocation.{@link org.apache.shiro.aop.MethodInvocation#proceed proceed}()</code>
* if authorization is allowed by first
* calling {@link #assertAuthorized(org.apache.shiro.aop.MethodInvocation) assertAuthorized}.
*/
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
assertAuthorized(methodInvocation);
return methodInvocation.proceed();
}
/**
* Asserts that the specified MethodInvocation is allowed to continue by performing any necessary authorization
* (access control) checks first.
* @param methodInvocation the <code>MethodInvocation</code> to invoke.
* @throws AuthorizationException if the <code>methodInvocation</code> should not be allowed to continue/execute.
*/
protected abstract void assertAuthorized(MethodInvocation methodInvocation) throws AuthorizationException;
}
可以看到,invoke方法内,首先调用assertAuthorized方法执行增强逻辑(Shiro框架中即为自定义校验逻辑),校验通过,再继续执行目标方法,下面重点分析下assertAuthorized的具体实现逻辑;
assertAuthorized方法的实现是在类AnnotationsAuthorizingMethodInterceptor中,如下:
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);
}
}
}
}
如上,这里主要包含了3个部分:
- 首先获取Shiro自定义的方法拦截器
- 然后通过supports方法过滤方法拦截器
- 最后调用方法拦截器的assertAuthorized方法实现拦截
下面进行具体分析:
Shiro自定义方法拦截器保存在了成员变量methodInterceptors中,其具体赋值是在AopAllianceAnnotationsAuthorizingMethodInterceptor构造函数中,如下:
public AopAllianceAnnotationsAuthorizingMethodInterceptor() {
List<AuthorizingAnnotationMethodInterceptor> interceptors =
new ArrayList<AuthorizingAnnotationMethodInterceptor>(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);
}
下面首先对Shiro自定义方法拦截器进行分析;
如上,实例化了多个Shiro自定义方法拦截器,整体类图如下:
可以看出这里的PermissionAnnotationMethodInterceptor方法拦截器等也是实现了Shiro?MethodInterceptor接口,并且通过组合的方式注入到了前面的增强类AopAllianceAnnotationsAuthorizingMethodInterceptor中;
在上面Shiro多个自定义方法拦截器创建时,这里以PermissionAnnotationMethodInterceptor为例分析,其创建过程如下:
public PermissionAnnotationMethodInterceptor(AnnotationResolver resolver) {
super( new PermissionAnnotationHandler(), resolver);
}
这里绑定了注解处理器PermissionAnnotationHandler,注解处理器的整体类图图示如下:
?在PermissionAnnotationHandler内部通过构造函数绑定了注解@RequiresPermissions,如下:
public class PermissionAnnotationHandler extends AuthorizingAnnotationHandler {
/**
* Default no-argument constructor that ensures this handler looks for
* {@link org.apache.shiro.authz.annotation.RequiresPermissions RequiresPermissions} annotations.
*/
public PermissionAnnotationHandler() {
super(RequiresPermissions.class);
}
//……
}
另外,我们可以看到实例化时注入了注解解析器,这里实际为SpringAnnotationResolver,主要完成对指定注解的解析工作,实现如下:
public class SpringAnnotationResolver implements AnnotationResolver {
public Annotation getAnnotation(MethodInvocation mi, Class<? extends Annotation> clazz) {
Method m = mi.getMethod();
Annotation a = AnnotationUtils.findAnnotation(m, clazz);
if (a != null) return a;
//The MethodInvocation's method object could be a method defined in an interface.
//However, if the annotation existed in the interface's implementation (and not
//the interface itself), it won't be on the above method object. Instead, we need to
//acquire the method representation from the targetClass and check directly on the
//implementation itself:
Class<?> targetClass = mi.getThis().getClass();
m = ClassUtils.getMostSpecificMethod(m, targetClass);
a = AnnotationUtils.findAnnotation(m, clazz);
if (a != null) return a;
// See if the class has the same annotation
return AnnotationUtils.findAnnotation(mi.getThis().getClass(), clazz);
}
}
归纳总结,在PermissionAnnotationMethodInterceptor创建过程中,注入了PermissionAnnotationHandler和SpringAnnotationResolver:
- PermissionAnnotationHandler注解处理器:绑定了注解@RequiresPermissions
- SpringAnnotationResolver注解解析器:用于判断目标方法是否存在指定注解
supports方法的实现是在父类AnnotationMethodInterceptor中,如下:
public boolean supports(MethodInvocation mi) {
return getAnnotation(mi) != null;
}
protected Annotation getAnnotation(MethodInvocation mi) {
return getResolver().getAnnotation(mi, getHandler().getAnnotationClass());
}
这里通过getHandler().getAnnotationClass()方法获取绑定的注解,同样以PermissionAnnotationMethodInterceptor为例,getHandler()即为PermissionAnnotationHandler,getAnnotationClass()即为@RequiresPermissions;
这里getResolver()即为前面创建的SpringAnnotationResolver,通过getAnnotation方法获取目标方法上的@RequiresPermissions注解,并判断该注解是否存在,存在则匹配成功,然后继续调用方法拦截器进行执行;
通过调用assertAuthorized方法执行拦截逻辑,以PermissionAnnotationMethodInterceptor为例,其实现是在父类AuthorizingAnnotationMethodInterceptor中实现的,如下:
public void assertAuthorized(MethodInvocation mi) throws AuthorizationException {
try {
((AuthorizingAnnotationHandler)getHandler()).assertAuthorized(getAnnotation(mi));
}
catch(AuthorizationException ae) {
// Annotation handler doesn't know why it was called, so add the information here if possible.
// Don't wrap the exception here since we don't want to mask the specific exception, such as
// UnauthenticatedException etc.
if (ae.getCause() == null) ae.initCause(new AuthorizationException("Not authorized to invoke method: " + mi.getMethod()));
throw ae;
}
}
这里又委托给注解处理器,即PermissionAnnotationHandler,调用其方法assertAuthorized执行校验逻辑,参数为匹配到的目标方法上的注解@RequiresPermissions,具体实现如下:
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())) {
// Avoid processing exceptions unnecessarily - "delay" throwing the exception by calling hasRole first
boolean hasAtLeastOnePermission = false;
for (String permission : perms) if (getSubject().isPermitted(permission)) hasAtLeastOnePermission = true;
// Cause the exception if none of the role match, note that the exception message will be a bit misleading
if (!hasAtLeastOnePermission) getSubject().checkPermission(perms[0]);
}
}
这里获取到了注解上配置的访问目标方法所需要的权限,也获取了当前登录用户Subject,然后调用其checkPermission方法执行权限校验,实现如下:
public void checkPermission(String permission) throws AuthorizationException {
assertAuthzCheckPossible();
securityManager.checkPermission(getPrincipals(), permission);
}
可以看到,这里也是委托给了安全管理器执行权限校验,并获取了当前登录用户的账户名Principals,进一步跟踪如下:
public void checkPermission(PrincipalCollection principals, String permission) throws AuthorizationException {
this.authorizer.checkPermission(principals, permission);
}
这里继续委托给权限校验器authorizer执行权限校验,这里具体类为ModularRealmAuthorizer,继续跟踪如下:
/**
* If !{@link #isPermitted(org.apache.shiro.subject.PrincipalCollection, Permission) isPermitted(permission)}, throws
* an <code>UnauthorizedException</code> otherwise returns quietly.
*/
public void checkPermission(PrincipalCollection principals, Permission permission) throws AuthorizationException {
assertRealmsConfigured();
if (!isPermitted(principals, permission)) {
throw new UnauthorizedException("Subject does not have permission [" + permission + "]");
}
}
如上会校验权限组件Realm已配置,并委托给isPermitted方法校验当前用户是否具备指定权限,没有配置指定权限时,会抛出权限校验异常UnauthorizedException;
后续isPermitted方法的处理流程可以参见的Shiro框架:Shiro用户访问控制鉴权流程-内置过滤器方式源码解析?的章节“3.2.1?isPermitted权限校验”。
至此,Shiro用户访问控制鉴权流程-Aop注解方式源码解析完成,Over~~