基于策略和资源的授权机制
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.Entities;
|
||||||
using HelloShop.IdentityService.EntityFrameworks;
|
using HelloShop.IdentityService.EntityFrameworks;
|
||||||
|
using HelloShop.ServiceDefaults.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
// For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
|
// 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
|
public class UsersController(IdentityServiceDbContext dbContext) : ControllerBase
|
||||||
{
|
{
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
public IEnumerable<User> Get()
|
[Authorize(IdentityPermissions.Users.Default)]
|
||||||
|
public IEnumerable<User> GetUsers()
|
||||||
{
|
{
|
||||||
return dbContext.Set<User>();
|
return dbContext.Set<User>();
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET api/<UsersController>/5
|
|
||||||
[HttpGet("{id}")]
|
[HttpGet("{id}")]
|
||||||
public User? Get(int id)
|
[Authorize(IdentityPermissions.Users.Default)]
|
||||||
|
public User? GetUser(int id)
|
||||||
{
|
{
|
||||||
return dbContext.Set<User>().Find(id);
|
return dbContext.Set<User>().Find(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
// POST api/<UsersController>
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
public void Post([FromBody] User value)
|
[Authorize(IdentityPermissions.Users.Create)]
|
||||||
|
public void PostUser([FromBody] User value)
|
||||||
{
|
{
|
||||||
dbContext.Add(value);
|
dbContext.Add(value);
|
||||||
dbContext.SaveChanges();
|
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.
|
// Copyright (c) HelloShop Corporation. All rights reserved.
|
||||||
// See the license file in the project root for more information.
|
// See the license file in the project root for more information.
|
||||||
|
|
||||||
|
using HelloShop.ServiceDefaults.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
|
||||||
namespace HelloShop.IdentityService.Entities
|
namespace HelloShop.IdentityService.Entities
|
||||||
{
|
{
|
||||||
public class User: IdentityUser<int>
|
public class User : IdentityUser<int>, IAuthorizationResource
|
||||||
{
|
{
|
||||||
public DateTimeOffset CreationTime { get; init; } = TimeProvider.System.GetUtcNow();
|
public DateTimeOffset CreationTime { get; init; } = TimeProvider.System.GetUtcNow();
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
|||||||
namespace HelloShop.IdentityService.EntityFrameworks.Migrations
|
namespace HelloShop.IdentityService.EntityFrameworks.Migrations
|
||||||
{
|
{
|
||||||
[DbContext(typeof(IdentityServiceDbContext))]
|
[DbContext(typeof(IdentityServiceDbContext))]
|
||||||
[Migration("20240327110650_InitialCreate")]
|
[Migration("20240330112954_InitialCreate")]
|
||||||
partial class InitialCreate
|
partial class InitialCreate
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
@ -56,10 +56,7 @@ builder.Services.AddAuthentication(options =>
|
|||||||
builder.Services.AddDataSeedingProviders();
|
builder.Services.AddDataSeedingProviders();
|
||||||
builder.Services.AddOpenApi();
|
builder.Services.AddOpenApi();
|
||||||
builder.Services.AddPermissionDefinitions();
|
builder.Services.AddPermissionDefinitions();
|
||||||
builder.Services.AddHttpClient().AddHttpContextAccessor().AddRemotePermissionChecker(options =>
|
builder.Services.AddAuthorization().AddDistributedMemoryCache().AddHttpClient().AddHttpContextAccessor().AddTransient<IPermissionChecker, LocalPermissionChecker>().AddCustomAuthorization();
|
||||||
{
|
|
||||||
options.ApiEndpoint = "https://localhost:5001/api/Permissions/PermissionList";
|
|
||||||
});
|
|
||||||
|
|
||||||
var app = builder.Build();
|
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
|
await distributedCache.SetObjectAsync(cacheKey, new PermissionGrantCacheItem(isGranted), new DistributedCacheEntryOptions
|
||||||
{
|
{
|
||||||
SlidingExpiration = TimeSpan.FromMinutes(10)
|
AbsoluteExpiration = DateTimeOffset.Now.AddSeconds(1)
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isGranted)
|
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.Authorization;
|
||||||
using HelloShop.ServiceDefaults.Permissions;
|
using HelloShop.ServiceDefaults.Permissions;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Routing;
|
using Microsoft.AspNetCore.Routing;
|
||||||
@ -72,4 +73,13 @@ public static class PermissionExtensions
|
|||||||
|
|
||||||
return services;
|
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