基于策略和资源的授权机制
This commit is contained in:
parent
2d0e504108
commit
65925ce6ef
@ -1,39 +0,0 @@
|
||||
// Copyright (c) HelloShop Corporation. All rights reserved.
|
||||
// See the license file in the project root for more information.
|
||||
|
||||
using HelloShop.ServiceDefaults.Authorization;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace HelloShop.IdentityService.Controllers
|
||||
{
|
||||
[Route("api/[controller]")]
|
||||
[ApiController]
|
||||
public class TestsController(IPermissionChecker permissionChecker, IAuthorizationService authorizationService) : ControllerBase
|
||||
{
|
||||
|
||||
[Authorize(IdentityPermissions.Users.Update)]
|
||||
public async Task<IActionResult> Foo()
|
||||
{
|
||||
var result = await permissionChecker.IsGrantedAsync(IdentityPermissions.Users.Update, "Order", "1");
|
||||
|
||||
var result2 = await authorizationService.AuthorizeAsync(User, IdentityPermissions.Users.Update);
|
||||
|
||||
return Ok("Hello, World!");
|
||||
}
|
||||
|
||||
[HttpGet(nameof(Bar))]
|
||||
[Authorize(IdentityPermissions.Users.Create)]
|
||||
public IActionResult Bar()
|
||||
{
|
||||
return Ok("Hello, World!");
|
||||
}
|
||||
|
||||
[Authorize(IdentityPermissions.Users.Delete)]
|
||||
[HttpGet(nameof(Baz))]
|
||||
public IActionResult Baz()
|
||||
{
|
||||
return Ok("Hello, World!");
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,7 @@
|
||||
using HelloShop.IdentityService.Entities;
|
||||
using HelloShop.IdentityService.EntityFrameworks;
|
||||
using HelloShop.ServiceDefaults.Authorization;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
// For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
|
||||
@ -11,24 +13,66 @@ namespace HelloShop.IdentityService.Controllers
|
||||
public class UsersController(IdentityServiceDbContext dbContext) : ControllerBase
|
||||
{
|
||||
[HttpGet]
|
||||
public IEnumerable<User> Get()
|
||||
[Authorize(IdentityPermissions.Users.Default)]
|
||||
public IEnumerable<User> GetUsers()
|
||||
{
|
||||
return dbContext.Set<User>();
|
||||
}
|
||||
|
||||
// GET api/<UsersController>/5
|
||||
[HttpGet("{id}")]
|
||||
public User? Get(int id)
|
||||
[Authorize(IdentityPermissions.Users.Default)]
|
||||
public User? GetUser(int id)
|
||||
{
|
||||
return dbContext.Set<User>().Find(id);
|
||||
}
|
||||
|
||||
// POST api/<UsersController>
|
||||
[HttpPost]
|
||||
public void Post([FromBody] User value)
|
||||
[Authorize(IdentityPermissions.Users.Create)]
|
||||
public void PostUser([FromBody] User value)
|
||||
{
|
||||
dbContext.Add(value);
|
||||
dbContext.SaveChanges();
|
||||
}
|
||||
|
||||
[HttpPut("{id}")]
|
||||
[Authorize(IdentityPermissions.Users.Update)]
|
||||
public void PutUser(int id, [FromBody] User value)
|
||||
{
|
||||
var user = dbContext.Set<User>().Find(id);
|
||||
if (user != null)
|
||||
{
|
||||
dbContext.SaveChanges();
|
||||
}
|
||||
}
|
||||
|
||||
[HttpDelete("{id}")]
|
||||
[Authorize(IdentityPermissions.Users.Delete)]
|
||||
public async Task<IActionResult> DeleteUser(int id, [FromServices] IAuthorizationService authorizationService)
|
||||
{
|
||||
var user = dbContext.Set<User>().Find(id);
|
||||
|
||||
if (user != null)
|
||||
{
|
||||
var result = await authorizationService.AuthorizeAsync(User, user, IdentityPermissions.Users.Delete);
|
||||
|
||||
if (result.Succeeded)
|
||||
{
|
||||
dbContext.Remove(user);
|
||||
|
||||
dbContext.SaveChanges();
|
||||
|
||||
return Ok();
|
||||
}
|
||||
}
|
||||
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
[HttpGet(nameof(Bar))]
|
||||
[Authorize(IdentityPermissions.Users.Create)]
|
||||
public IActionResult Bar()
|
||||
{
|
||||
return Ok("Hello, World!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,13 @@
|
||||
// Copyright (c) HelloShop Corporation. All rights reserved.
|
||||
// See the license file in the project root for more information.
|
||||
|
||||
using HelloShop.ServiceDefaults.Authorization;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
|
||||
namespace HelloShop.IdentityService.Entities
|
||||
{
|
||||
public class User: IdentityUser<int>
|
||||
public class User : IdentityUser<int>, IAuthorizationResource
|
||||
{
|
||||
public DateTimeOffset CreationTime { get; init; } = TimeProvider.System.GetUtcNow();
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
namespace HelloShop.IdentityService.EntityFrameworks.Migrations
|
||||
{
|
||||
[DbContext(typeof(IdentityServiceDbContext))]
|
||||
[Migration("20240327110650_InitialCreate")]
|
||||
[Migration("20240330112954_InitialCreate")]
|
||||
partial class InitialCreate
|
||||
{
|
||||
/// <inheritdoc />
|
@ -56,10 +56,7 @@ builder.Services.AddAuthentication(options =>
|
||||
builder.Services.AddDataSeedingProviders();
|
||||
builder.Services.AddOpenApi();
|
||||
builder.Services.AddPermissionDefinitions();
|
||||
builder.Services.AddHttpClient().AddHttpContextAccessor().AddRemotePermissionChecker(options =>
|
||||
{
|
||||
options.ApiEndpoint = "https://localhost:5001/api/Permissions/PermissionList";
|
||||
});
|
||||
builder.Services.AddAuthorization().AddDistributedMemoryCache().AddHttpClient().AddHttpContextAccessor().AddTransient<IPermissionChecker, LocalPermissionChecker>().AddCustomAuthorization();
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
|
@ -0,0 +1,32 @@
|
||||
using HelloShop.ServiceDefaults.Permissions;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Authorization.Infrastructure;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace HelloShop.ServiceDefaults.Authorization;
|
||||
|
||||
public class CustomAuthorizationPolicyProvider(IOptions<AuthorizationOptions> options, IPermissionDefinitionManager permissionDefinitionManager) : DefaultAuthorizationPolicyProvider(options)
|
||||
{
|
||||
public override async Task<AuthorizationPolicy?> GetPolicyAsync(string policyName)
|
||||
{
|
||||
AuthorizationPolicy? policy = await base.GetPolicyAsync(policyName);
|
||||
|
||||
if (policy != null)
|
||||
{
|
||||
return policy;
|
||||
}
|
||||
|
||||
var permissionDefinition = permissionDefinitionManager.GetOrNullAsync(policyName);
|
||||
|
||||
if (permissionDefinition != null)
|
||||
{
|
||||
var policyBuilder = new AuthorizationPolicyBuilder();
|
||||
|
||||
policyBuilder.Requirements.Add(new OperationAuthorizationRequirement { Name = policyName });
|
||||
|
||||
return policyBuilder.Build();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
using HelloShop.ServiceDefaults.Constants;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Reflection.Metadata.Ecma335;
|
||||
|
||||
namespace HelloShop.ServiceDefaults.Authorization;
|
||||
|
||||
public interface IAuthorizationResource
|
||||
{
|
||||
string ResourceType => GetType().Name;
|
||||
|
||||
string ResourceId => GetType().GetProperty(EntityConnstants.DefaultKey)?.GetValue(this)?.ToString() ?? throw new NotImplementedException();
|
||||
}
|
@ -33,7 +33,7 @@ public abstract class PermissionChecker(IHttpContextAccessor httpContextAccessor
|
||||
|
||||
await distributedCache.SetObjectAsync(cacheKey, new PermissionGrantCacheItem(isGranted), new DistributedCacheEntryOptions
|
||||
{
|
||||
SlidingExpiration = TimeSpan.FromMinutes(10)
|
||||
AbsoluteExpiration = DateTimeOffset.Now.AddSeconds(1)
|
||||
});
|
||||
|
||||
if (isGranted)
|
||||
|
@ -0,0 +1,29 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Authorization.Infrastructure;
|
||||
|
||||
namespace HelloShop.ServiceDefaults.Authorization;
|
||||
|
||||
public class PermissionRequirementHandler(IPermissionChecker permissionChecker) : AuthorizationHandler<OperationAuthorizationRequirement>
|
||||
{
|
||||
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, OperationAuthorizationRequirement requirement)
|
||||
{
|
||||
if (await permissionChecker.IsGrantedAsync(context.User, requirement.Name))
|
||||
{
|
||||
context.Succeed(requirement);
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
27
src/HelloShop.ServiceDefaults/Authorization/ResourceInfo.cs
Normal file
27
src/HelloShop.ServiceDefaults/Authorization/ResourceInfo.cs
Normal file
@ -0,0 +1,27 @@
|
||||
namespace HelloShop.ServiceDefaults.Authorization;
|
||||
|
||||
public class ResourceInfo : 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}";
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
namespace HelloShop.ServiceDefaults.Constants;
|
||||
|
||||
public static class EntityConnstants
|
||||
{
|
||||
public const string DefaultKey = "Id";
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
using HelloShop.ServiceDefaults.Authorization;
|
||||
using HelloShop.ServiceDefaults.Permissions;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
@ -72,4 +73,13 @@ public static class PermissionExtensions
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
public static IServiceCollection AddCustomAuthorization(this IServiceCollection services)
|
||||
{
|
||||
services.AddSingleton<IAuthorizationPolicyProvider, CustomAuthorizationPolicyProvider>();
|
||||
services.AddTransient<IAuthorizationHandler, PermissionRequirementHandler>();
|
||||
services.AddTransient<IAuthorizationHandler, ResourcePermissionRequirementHandler>();
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user