在整體介紹這個部分之前,如果對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系列的主目錄。
