如何对 ASP.Net Core 中特定对象中的特定字段进行授权?

Mor*_*siu 4 c# asp.net-core

我需要检查数据库中特定对象中特定字段的权限。

让我们举个例子。我有模特叫Employee

public class Employee {

    [Key]
    public int EmployeeID { get; set; }

    public string JobTitle { get; set; }

    public string Description { get; set; }

    public int Salary { get; set; } // <---- Restricted

    public int BossID { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

我有几个案例:

  1. 我需要限制对特定领域的访问,Salary因为我不希望任何人看到彼此的薪水。但是 HR 可以看到任何人Salary并对其进行编辑。如果我是这个员工,我可以看到我自己的Salary,但不能编辑它。

  2. 每个人都可以看到彼此的职位名称,但只有 HR 可以编辑它。还有那个员工的老板,可以编辑,员工自己不能。

用例:

  • 我是 RoleID 为 4 的经理。我想看看Salary我的Employee名字为 John Smith 的角色 ID为EmployeeID5。我可以做到。

  • 我是 RoleID 4 的经理。我想看看Salary“Employee named Mark Twain withEmployeeID ” 8。Mark不是我的直接下属。他来自不同的分支。我不能这样做。

  • 我是EmployeeID5 的员工,我想看看我的Salary. 这是允许的。

  • 我是EmployeeID5名员工,我想编辑自己的Salary. 这是禁止的。我收到 HTTP 错误 401。

  • 我是人力资源的。我可以查看和编辑Salary公司中的所有员工。

我想到了这样的事情:

public class Access {
  [Required]
  public int RoleID { get; set; }

  [Required]
  public string TableName { get; set; }

  [Required]
  public string ColumnName { get; set; }

  [Required]
  public int RowID { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

然后(按Authorize属性)检查特定角色(老板、HR 或其他角色)是否可以访问特定字段(例如Salary)以获取特定数据(例如Employeeid 22)。顺便说一下,这是很多“具体”。

我该怎么做?我的想法“OK”吗?

ASp*_*rin 6

如果逻辑不太复杂或更通用,则可以设置自定义输出格式化程序以防止将某些字段写入响应。

该方法有以下问题:

  1. 不应该处理复杂的逻辑。由于它导致业务逻辑传播到多个地方
  2. 替换默认序列化。所以如果里面设置了特定的序列化设置Startup,那么就应该调用

让我们看一个例子。可能有一个自定义属性,如

public class AuthorizePropertyAttribute : Attribute
{
    public AuthorizePropertyAttribute(string role) => Role = role;
    public string Role { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

然后输出格式化程序可能是这样的:

public class AuthFormatter : TextOutputFormatter
{
    public AuthFormatter()
    {
        SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/json"));
        SupportedEncodings.Add(Encoding.UTF8);
    }

    public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context, 
        Encoding selectedEncoding)
    {
        var settings = new JsonSerializerSettings 
        {
            ContractResolver = new AuthorizedPropertyContractResolver(context.HttpContext.User)
        };
        await context.HttpContext.Response.WriteAsync(
            JsonConvert.SerializeObject(context.Object, settings));
    }
}    
Run Code Online (Sandbox Code Playgroud)

那将需要

public class AuthorizedPropertyContractResolver : DefaultContractResolver
{
    public AuthorizedPropertyContractResolver(ClaimsPrincipal user)
    {
        User = user;
    }

    public ClaimsPrincipal User { get; }

    protected override JsonProperty CreateProperty(MemberInfo member, 
        MemberSerialization memberSerialization)
    {
        var result = base.CreateProperty(member, memberSerialization);
        result.ShouldSerialize = e =>
        {
            var role = member.GetCustomAttribute<AuthorizePropertyAttribute>()?.Role;
            return string.IsNullOrWhiteSpace(role) ? true : User.IsInRole(role);
        };
        return result;
    }
}
Run Code Online (Sandbox Code Playgroud)

登记:

services.AddMvc(options =>
{
    options.OutputFormatters.Insert(0, new AuthFormatter());
});
Run Code Online (Sandbox Code Playgroud)

在这种情况下,简单用户的 Response 将缺少 Salary 字段{"Id":1,"Name":"John"},同时经理将看到完整的响应 {"Id":1,"Name":"John","Salary":100000},当然属性“Salary”应该设置属性

[AuthorizeProperty("Boss")]
public double Salary { get; set; }
Run Code Online (Sandbox Code Playgroud)


Dan*_*tin 4

您应该实施两种不同的方法。一种是供HR索取数据时使用,另一种是供简单用户使用。那么您永远不应该返回整个对象 (json),而是创建一些保存所需数据的 DTO(数据传输对象)。那么让我们举个例子:

public class DTOGetEmployeeByEmployee {

    public int EmployeeID { get; set; }

    public string JobTitle { get; set; }

    public string Description { get; set; }

    public int BossID { get; set; }
}

public class DTOGetEmployeeByHR {

    public int EmployeeID { get; set; }

    public string JobTitle { get; set; }

    public string Description { get; set; }

    public int Salary { get; set; }

    public int BossID { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

一旦用户请求该员工,就从数据库中获取它,然后将其转换为所需的 DTO。到目前为止我看到的最好的方法是使用 AutoMapper 来做到这一点:

Mapper.Map<DTOxxxx>(yourObject);
Run Code Online (Sandbox Code Playgroud)

您还可以使用[授权]属性来检查用户是HR还是员工。我结合 JWT-Token 多次执行此操作。

public class EmployeeController
{
    [Authorize("HR")]
    [HttpGet, Route("GetForHR")]
    public IActionResult Get(int employeeID)
    {
        // Note: this is just a sample out of my head, so there will be adjustments needed in order to run that

        // Check if the HR is allowed to access the Employees data

        // Get the Employee by its ID
        var emp = ...;

        // Convert it to the DTO
        var dto = Mapper.Map<DTOGetEmployee>(emp);

        // return the dto
        return Ok(dto);
    }
}
Run Code Online (Sandbox Code Playgroud)

我打赌有很多更好的解决方案,但对我来说,这非常简单,很容易在其他应用程序中重新实现,并且没有明显的性能损失