基于资源授权的最佳实践
This commit is contained in:
1 parent
65925ce6ef
commit
9fdac9de5f
11 files changed
+72
-110
No files matched your search
@@ -4,7 +4,7 @@ namespace HelloShop.ServiceDefaults.Authorization;
|
||||
|
||||
public interface IPermissionChecker
|
||||
{
|
||||
Task<bool> IsGrantedAsync(string name, string? resourceType = null, string? resourceId = null);
|
||||
Task<bool> IsGrantedAsync(string permissionName, string? resourceType = null, string? resourceId = null);
|
||||
|
||||
Task<bool> IsGrantedAsync(ClaimsPrincipal claimsPrincipal, string name, string? resourceType = null, string? resourceId = null);
|
||||
Task<bool> IsGrantedAsync(ClaimsPrincipal claimsPrincipal, string permissionName, string? resourceType = null, string? resourceId = null);
|
||||
}
|
||||
@@ -9,15 +9,15 @@ public abstract class PermissionChecker(IHttpContextAccessor httpContextAccessor
|
||||
{
|
||||
protected HttpContext HttpContext { get; init; } = httpContextAccessor.HttpContext ?? throw new InvalidOperationException();
|
||||
|
||||
public async Task<bool> IsGrantedAsync(string name, string? resourceType = null, string? resourceId = null) => await IsGrantedAsync(HttpContext.User, name, resourceType, resourceId);
|
||||
public async Task<bool> IsGrantedAsync(string permissionName, string? resourceType = null, string? resourceId = null) => await IsGrantedAsync(HttpContext.User, permissionName, resourceType, resourceId);
|
||||
|
||||
public async Task<bool> IsGrantedAsync(ClaimsPrincipal claimsPrincipal, string name, string? resourceType = null, string? resourceId = null)
|
||||
public async Task<bool> IsGrantedAsync(ClaimsPrincipal claimsPrincipal, string permissionName, string? resourceType = null, string? resourceId = null)
|
||||
{
|
||||
var roleIds = claimsPrincipal.FindAll(CustomClaimTypes.RoleIdentifier).Select(c => Convert.ToInt32(c.Value)).ToArray();
|
||||
|
||||
foreach (var roleId in roleIds)
|
||||
{
|
||||
var cacheKey = PermissionGrantCacheItem.CreateCacheKey(roleId, name, resourceType, resourceId);
|
||||
var cacheKey = PermissionGrantCacheItem.CreateCacheKey(roleId, permissionName, resourceType, resourceId);
|
||||
|
||||
if (distributedCache.TryGetValue(cacheKey, out PermissionGrantCacheItem? cacheItem) && cacheItem != null)
|
||||
{
|
||||
@@ -29,11 +29,11 @@ public abstract class PermissionChecker(IHttpContextAccessor httpContextAccessor
|
||||
continue;
|
||||
}
|
||||
|
||||
bool isGranted = await IsGrantedAsync(roleId, name, resourceType, resourceId);
|
||||
bool isGranted = await IsGrantedAsync(roleId, permissionName, resourceType, resourceId);
|
||||
|
||||
await distributedCache.SetObjectAsync(cacheKey, new PermissionGrantCacheItem(isGranted), new DistributedCacheEntryOptions
|
||||
{
|
||||
AbsoluteExpiration = DateTimeOffset.Now.AddSeconds(1)
|
||||
AbsoluteExpiration = DateTimeOffset.Now
|
||||
});
|
||||
|
||||
if (isGranted)
|
||||
@@ -45,5 +45,5 @@ public abstract class PermissionChecker(IHttpContextAccessor httpContextAccessor
|
||||
return false;
|
||||
}
|
||||
|
||||
public abstract Task<bool> IsGrantedAsync(int roleId, string name, string? resourceType = null, string? resourceId = null);
|
||||
public abstract Task<bool> IsGrantedAsync(int roleId, string permissionName, string? resourceType = null, string? resourceId = null);
|
||||
}
|
||||
@@ -7,7 +7,21 @@ public class PermissionRequirementHandler(IPermissionChecker permissionChecker)
|
||||
{
|
||||
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, OperationAuthorizationRequirement requirement)
|
||||
{
|
||||
if (await permissionChecker.IsGrantedAsync(context.User, requirement.Name))
|
||||
if (context.Resource is IAuthorizationResource resource)
|
||||
{
|
||||
if (await permissionChecker.IsGrantedAsync(context.User,requirement.Name, resource.ResourceType, resource.ResourceId))
|
||||
{
|
||||
context.Succeed(requirement);
|
||||
}
|
||||
else
|
||||
{
|
||||
context.Fail();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if(await permissionChecker.IsGrantedAsync(context.User,requirement.Name))
|
||||
{
|
||||
context.Succeed(requirement);
|
||||
return;
|
||||
@@ -16,14 +30,3 @@ public class PermissionRequirementHandler(IPermissionChecker permissionChecker)
|
||||
context.Fail();
|
||||
}
|
||||
}
|
||||
|
||||
public class ResourcePermissionRequirementHandler(IPermissionChecker permissionChecker) : AuthorizationHandler<OperationAuthorizationRequirement, IAuthorizationResource>
|
||||
{
|
||||
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, OperationAuthorizationRequirement requirement, IAuthorizationResource resource)
|
||||
{
|
||||
if (await permissionChecker.IsGrantedAsync(context.User, requirement.Name, resource.ResourceType, resource.ResourceId))
|
||||
{
|
||||
context.Succeed(requirement);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ public class RemotePermissionChecker(IHttpContextAccessor httpContextAccessor, I
|
||||
{
|
||||
private readonly RemotePermissionCheckerOptions _remotePermissionCheckerOptions = options.Value;
|
||||
|
||||
public override async Task<bool> IsGrantedAsync(int roleId, string name, string? resourceType = null, string? resourceId = null)
|
||||
public override async Task<bool> IsGrantedAsync(int roleId, string permissionName, string? resourceType = null, string? resourceId = null)
|
||||
{
|
||||
string? accessToken = await HttpContext.GetTokenAsync("access_token");
|
||||
|
||||
@@ -25,24 +25,17 @@ public class RemotePermissionChecker(IHttpContextAccessor httpContextAccessor, I
|
||||
|
||||
Dictionary<string, string?> parameters = new()
|
||||
{
|
||||
{ nameof(roleId), roleId.ToString() },
|
||||
{ nameof(name), name },
|
||||
{ nameof(permissionName), permissionName },
|
||||
{ nameof(resourceType) , resourceType },
|
||||
{ nameof(resourceId), resourceId }
|
||||
};
|
||||
|
||||
string queryString = QueryHelpers.AddQueryString(string.Empty, parameters);
|
||||
|
||||
var permissionGrants = httpClient.GetFromJsonAsAsyncEnumerable<PermissionGrantedResponse>(queryString);
|
||||
HttpRequestMessage request = new(HttpMethod.Head, queryString);
|
||||
|
||||
await foreach (var permissionGrant in permissionGrants)
|
||||
{
|
||||
if (permissionGrant != null && permissionGrant.IsGranted)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
HttpResponseMessage response = await httpClient.SendAsync(request);
|
||||
|
||||
return false;
|
||||
return response.IsSuccessStatusCode;
|
||||
}
|
||||
}
|
||||
@@ -1,27 +1,7 @@
|
||||
namespace HelloShop.ServiceDefaults.Authorization;
|
||||
|
||||
public class ResourceInfo : IAuthorizationResource
|
||||
public record struct ResourceInfo(string ResourceType, string ResourceId) : IAuthorizationResource
|
||||
{
|
||||
public required string ResourceType { get; set; }
|
||||
|
||||
public required string ResourceId { get; set; }
|
||||
|
||||
public static implicit operator string(ResourceInfo resource) => resource.ToString();
|
||||
|
||||
public static explicit operator ResourceInfo(string resourcePath)
|
||||
{
|
||||
string[] separators = resourcePath.Split(":");
|
||||
|
||||
if (separators == null || separators.Length != 2)
|
||||
{
|
||||
throw new ArgumentException("Resource path must be in the format 'type:id'", nameof(resourcePath));
|
||||
}
|
||||
|
||||
ResourceInfo resourceInfo = new() { ResourceType = separators.First(), ResourceId = separators.Last() };
|
||||
|
||||
return resourceInfo;
|
||||
}
|
||||
|
||||
|
||||
public override string ToString() => $"{ResourceType}:{ResourceId}";
|
||||
public override readonly string ToString() => $"{ResourceType}:{ResourceId}";
|
||||
}
|
||||
|
||||
@@ -78,7 +78,6 @@ public static class PermissionExtensions
|
||||
{
|
||||
services.AddSingleton<IAuthorizationPolicyProvider, CustomAuthorizationPolicyProvider>();
|
||||
services.AddTransient<IAuthorizationHandler, PermissionRequirementHandler>();
|
||||
services.AddTransient<IAuthorizationHandler, ResourcePermissionRequirementHandler>();
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user