ABP拦截器之AuthorizationInterceptor
在整体介绍这个部分之前,如果对ABP中的权限控制还没有一个很明确的认知,请先阅读这篇文章,然后在读下面的内容。
AuthorizationInterceptor看这个名字我们就知道这个拦截器拦截用户一些常规验证操作的,包括用户的登陆信息以及一些Features和Permissions的操作,那么这个过程到底是怎样的呢?这个还是和之前以前,首先在ABP的AbpBootstrapper的AddInterceptorRegistrars方法中添加拦截器的注册类。
private void AddInterceptorRegistrars() { ValidationInterceptorRegistrar.Initialize(IocManager); AuditingInterceptorRegistrar.Initialize(IocManager); EntityHistoryInterceptorRegistrar.Initialize(IocManager); UnitOfWorkRegistrar.Initialize(IocManager); AuthorizationInterceptorRegistrar.Initialize(IocManager); }
然后跟着代码,我们来一步步看看AuthorizationInterceptorRegistrar这个拦截器注册类中到底做了些什么?
/// <summary> /// This class is used to register interceptors on the Application Layer. /// </summary> internal static class AuthorizationInterceptorRegistrar { public static void Initialize(IIocManager iocManager) { iocManager.IocContainer.Kernel.ComponentRegistered += Kernel_ComponentRegistered; } private static void Kernel_ComponentRegistered(string key, IHandler handler) { if (ShouldIntercept(handler.ComponentModel.Implementation)) { handler.ComponentModel.Interceptors.Add(new InterceptorReference(typeof(AuthorizationInterceptor))); } } private static bool ShouldIntercept(Type type) { if (SelfOrMethodsDefinesAttribute<AbpAuthorizeAttribute>(type)) { return true; } if (SelfOrMethodsDefinesAttribute<RequiresFeatureAttribute>(type)) { return true; } return false; } private static bool SelfOrMethodsDefinesAttribute<TAttr>(Type type) { if (type.GetTypeInfo().IsDefined(typeof(TAttr), true)) { return true; } return type .GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) .Any(m => m.IsDefined(typeof(TAttr), true)); } }
这个类的作用顾名思义:在应用层注册拦截器。这个类的Initialize方法中还是订阅整个ABP框架中唯一的依赖注入容器IocContainer的ComponentRegistered这个方法,在这个方法的订阅函数中还是用来限定到你哪些类型可以使用当前的AuthorizationInterceptor,通过查看ShouldIntercept方法,我们发现如果当前类型或者当前类型所属的任何一个方法定义了自定义属性AbpAuthorizeAttribute或者是RequiresFeatureAttribute的时候那么就能够使用AuthorizationInterceptor这个拦截器,知道了使用这个拦截器,那么这个拦截器到底能够做些什么呢?
我们首先来看一看这个AuthorizationInterceptor这个方法到底做了些什么?
public class AuthorizationInterceptor : IInterceptor { private readonly IAuthorizationHelper _authorizationHelper; public AuthorizationInterceptor(IAuthorizationHelper authorizationHelper) { _authorizationHelper = authorizationHelper; } public void Intercept(IInvocation invocation) { _authorizationHelper.Authorize(invocation.MethodInvocationTarget, invocation.TargetType); invocation.Proceed(); } }
在这个类中,通过构造函数来注入了IAuthorizationHelper 这个接口,然后再Intercept方法中调用这个接口的Authorize方法,我们知道在我们执行客户端请求的方法之前依赖注入容器会拦截当前的方法,并执行Intercept这个方法,在执行当前方法之前强制插入一些操作,比如现在的这个_authorizationHelper.Authorize这个方法,在该方法执行之后再执行客户端请求的方法,这个是AOP思想的具体体现。
我们现在来看IAuthorizationHelper 接口的具体实现类中Authorize方法到底做了些什么?
public class AuthorizationHelper : IAuthorizationHelper, ITransientDependency { public IAbpSession AbpSession { get; set; } public IPermissionChecker PermissionChecker { get; set; } public IFeatureChecker FeatureChecker { get; set; } public ILocalizationManager LocalizationManager { get; set; } private readonly IFeatureChecker _featureChecker; private readonly IAuthorizationConfiguration _authConfiguration; public AuthorizationHelper(IFeatureChecker featureChecker, IAuthorizationConfiguration authConfiguration) { _featureChecker = featureChecker; _authConfiguration = authConfiguration; AbpSession = NullAbpSession.Instance; PermissionChecker = NullPermissionChecker.Instance; LocalizationManager = NullLocalizationManager.Instance; } public virtual async Task AuthorizeAsync(IEnumerable<IAbpAuthorizeAttribute> authorizeAttributes) { if (!_authConfiguration.IsEnabled) { return; } if (!AbpSession.UserId.HasValue) { throw new AbpAuthorizationException( LocalizationManager.GetString(AbpConsts.LocalizationSourceName, "CurrentUserDidNotLoginToTheApplication") ); } foreach (var authorizeAttribute in authorizeAttributes) { await PermissionChecker.AuthorizeAsync(authorizeAttribute.RequireAllPermissions, authorizeAttribute.Permissions); } } public virtual async Task AuthorizeAsync(MethodInfo methodInfo, Type type) { await CheckFeatures(methodInfo, type); await CheckPermissions(methodInfo, type); } protected virtual async Task CheckFeatures(MethodInfo methodInfo, Type type) { var featureAttributes = ReflectionHelper.GetAttributesOfMemberAndType<RequiresFeatureAttribute>(methodInfo, type); if (featureAttributes.Count <= 0) { return; } foreach (var featureAttribute in featureAttributes) { await _featureChecker.CheckEnabledAsync(featureAttribute.RequiresAll, featureAttribute.Features); } } protected virtual async Task CheckPermissions(MethodInfo methodInfo, Type type) { if (!_authConfiguration.IsEnabled) { return; } if (AllowAnonymous(methodInfo, type)) { return; } var authorizeAttributes = ReflectionHelper .GetAttributesOfMemberAndType(methodInfo, type) .OfType<IAbpAuthorizeAttribute>() .ToArray(); if (!authorizeAttributes.Any()) { return; } await AuthorizeAsync(authorizeAttributes); } private static bool AllowAnonymous(MemberInfo memberInfo, Type type) { return ReflectionHelper .GetAttributesOfMemberAndType(memberInfo, type) .OfType<IAbpAllowAnonymousAttribute>() .Any(); } }
在这个类中,首先调用的就是下面的方法
public virtual async Task AuthorizeAsync(MethodInfo methodInfo, Type type) { await CheckFeatures(methodInfo, type); await CheckPermissions(methodInfo, type); }
那么我们来首先看看CheckFeatures这个方法到底做了些什么?
protected virtual async Task CheckFeatures(MethodInfo methodInfo, Type type) { var featureAttributes = ReflectionHelper.GetAttributesOfMemberAndType<RequiresFeatureAttribute>(methodInfo, type); if (featureAttributes.Count <= 0) { return; } foreach (var featureAttribute in featureAttributes) { await _featureChecker.CheckEnabledAsync(featureAttribute.RequiresAll, featureAttribute.Features); } }
在这个方法内部首先通过反射来获取当前方法是否定义过RequiresFeatureAttribute这个自定义的属性,如果定义过那么获取这些自定义的RequireFeature,如果没有定义过那么就直接返回,定义过就通过一个循环来遍历所有的RequiresFeature,然后调用CheckEnableAsync这个方法,我们一步步来看这个方法内部到底做了些什么?
/// <summary> /// Checks if one or all of the given features are enabled. Throws <see cref="AbpAuthorizationException"/> if not. /// </summary> /// <param name="featureChecker"><see cref="IFeatureChecker"/> instance</param> /// <param name="requiresAll">True, to require that all the given features are enabled. False, to require one or more.</param> /// <param name="featureNames">Names of the features</param> public static async Task CheckEnabledAsync(this IFeatureChecker featureChecker, bool requiresAll, params string[] featureNames) { if (featureNames.IsNullOrEmpty()) { return; } if (requiresAll) { foreach (var featureName in featureNames) { if (!(await featureChecker.IsEnabledAsync(featureName))) { throw new AbpAuthorizationException( "Required features are not enabled. All of these features must be enabled: " + string.Join(", ", featureNames) ); } } } else { foreach (var featureName in featureNames) { if (await featureChecker.IsEnabledAsync(featureName)) { return; } } throw new AbpAuthorizationException( "Required features are not enabled. At least one of these features must be enabled: " + string.Join(", ", featureNames) ); } }
这个方法是定义在FeatureCheckerExtensions这个扩展类中的,按照该方法的注释,这个方法主要用来检查当前Feature是否是enabled,在这个方法的内部,如果当前的RequireFeature的RequiresAll属性为true那么就会检查当前RequireFeature的Features的每一个值是否都是true,如果有一个不为true,那么就会提示:Required features are not enabled. All of these features must be enabled:的错误提示信息。如果当前的RequireFeature的RequiresAll属性为false,那么就就会检查当前RequireFeature的Features的任何一个值是否为true,只要有一个满足条件就返回,如果所有的Features对应的值都为false,那么就会抛出异常信息:Required features are not enabled. At least one of these features must be enabled:这个就是CheckFeatures的全部过程,通过这个过程我们知道主要是检查当前定义了RequireFeature的自定义属性的方法是否满足其内部的Features值是否为true的过程。
在说完CheckFeatures之后我们再来看看CheckPermissions方法,看看这个方法的内部到底做了些什么?
protected virtual async Task CheckPermissions(MethodInfo methodInfo, Type type) { if (!_authConfiguration.IsEnabled) { return; } if (AllowAnonymous(methodInfo, type)) { return; } var authorizeAttributes = ReflectionHelper .GetAttributesOfMemberAndType(methodInfo, type) .OfType<IAbpAuthorizeAttribute>() .ToArray(); if (!authorizeAttributes.Any()) { return; } await AuthorizeAsync(authorizeAttributes); }
在这个方法内部,首先看AuthorizationConfiguration是否已经配置为false,如果定义为false,那么后面的过程就不会再继续,通常这个AuthorizationConfiguration内部的IsEnable的配置通常都在当前的Module的PreInitialize方法中可以进行相关配置,如果当前属性为true那么,就接下来验证后面的过程,首先通过调用AllowAnonymous(methodInfo, type)方法来验证当前方法是否允许匿名进行登录,具体的原理是,验证当前方法是否有继承自IAbpAllowAnonymousAttribute这个接口的自定义属性(CustomAttribute),如果继承自定义过这个接口那么就跳过当前检验过程,最后再检验当前的方法所属的类型是否定义过继承自IAbpAuthorizeAttribute接口的自定义属性,如果没有这些自定义属性,那么也会跳过CheckPermissions方法,如果都不满足上述条件,最后就会去校验当前方法继承自IAbpAuthorizeAttribute接口的自定义属性,具体校验的过程在子方法AuthorizeAsync(authorizeAttributes)中进行,我们来看看这个子方法到底做了些什么?
public virtual async Task AuthorizeAsync(IEnumerable<IAbpAuthorizeAttribute> authorizeAttributes) { if (!_authConfiguration.IsEnabled) { return; } if (!AbpSession.UserId.HasValue) { throw new AbpAuthorizationException( LocalizationManager.GetString(AbpConsts.LocalizationSourceName, "CurrentUserDidNotLoginToTheApplication") ); } foreach (var authorizeAttribute in authorizeAttributes) { await PermissionChecker.AuthorizeAsync(authorizeAttribute.RequireAllPermissions, authorizeAttribute.Permissions); } }
在这个方法的内部,首先也是判断AuthorizationConfiguration是否已经配置为false,如果配置为false,那么就跳过下面的过程,接下来会检验AbpSession.UserId.HasValue是否有值,这个AbpSession.UserId就是当前登录用户的Id,如果当前UserId为Null,那么当客户端请求这个方法的时候,服务端直接抛出异常,并提示:CurrentUserDidNotLoginToTheApplication,如果当前用户已经登录过,最后再验证当前方法的每一个继承自IAbpAuthorizeAttribute接口的自定义属性,具体的验证过程是在PermissionChecker中定义的,我们到PermissionChecker中看看这个过程到底是怎么样的?
/// <summary> /// Authorizes current user for given permission or permissions, /// throws <see cref="AbpAuthorizationException"/> if not authorized. /// </summary> /// <param name="permissionChecker">Permission checker</param> /// <param name="requireAll"> /// If this is set to true, all of the <see cref="permissionNames"/> must be granted. /// If it's false, at least one of the <see cref="permissionNames"/> must be granted. /// </param> /// <param name="permissionNames">Name of the permissions to authorize</param> /// <exception cref="AbpAuthorizationException">Throws authorization exception if</exception> public static async Task AuthorizeAsync(this IPermissionChecker permissionChecker, bool requireAll, params string[] permissionNames) { if (await IsGrantedAsync(permissionChecker, requireAll, permissionNames)) { return; } var localizedPermissionNames = LocalizePermissionNames(permissionChecker, permissionNames); if (requireAll) { throw new AbpAuthorizationException( string.Format( L( permissionChecker, "AllOfThesePermissionsMustBeGranted", "Required permissions are not granted. All of these permissions must be granted: {0}" ), string.Join(", ", localizedPermissionNames) ) ); } else { throw new AbpAuthorizationException( string.Format( L( permissionChecker, "AtLeastOneOfThesePermissionsMustBeGranted", "Required permissions are not granted. At least one of these permissions must be granted: {0}" ), string.Join(", ", localizedPermissionNames) ) ); } }
我们在解释这个方法之前首先来看看这个方法的注释:Authorizes current user for given permission or permissions,按照解释,这个方法是验证当前登录用户User是否有特定的Permissions,这个方法也是传递两个参数,一个是 requireAll 另外一个是 string[] permissionNames,首先如果定义的requireAll 为true,那么就必须保证当前当前用户必须授予了所有的permissionNames中定义的内容,如果为false,那么只需要满足其中定义的任何一个Permission即可,如果不满足这些定义的Permission,那么就直接抛出异常,那么当前方法就会由于当前登录用户没有被授予相应的Permission而不能执行当前方法,这在一定的程度上保证了同一个方法不同的用户登录时会拥有不同的结果,当然这个很多人可能对之前提到的ABP中的Feature和Permission还不太了解,那么在接下来我会对这些内容来进行单独的介绍,欢迎关注后续文章。
这篇文章就介绍到这里,点击这里返回整个ABP系列的主目录。