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

时间:2018-10-12 07:17:11

标签: 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; }
}

我有几种情况:

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

  2. 每个人都可以看到彼此的职务,但只有HR可以编辑。而且该员工的老板可以编辑,而员工本人则不能。

用例:

  • 我是RoleID 4的管理员。我想看到我的Salary中名为Employee 5的约翰·史密斯(John Smith)中的EmployeeID

  • 我是RoleID 4的经理。我想查看'Employee Salary EmployeeID`的named Mark Twain with。8. Mark不是我的直接下属。他来自不同的部门。我不能那样做。

  • 我是EmployeeID 5的员工,我想看看我的Salary。允许。

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

  • 我来自HR。我可以查看和编辑公司中所有员工的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; }
}

然后检查(通过Authorize属性),以了解特定角色(老板,HR或其他人员)是否可以访问特定字段(例如Salary)的特定数据(例如Employee) ID 22)。顺便说一下,这是很多“特定的”。

我应该怎么做?我的想法好吗?

2 个答案:

答案 0 :(得分:5)

如果逻辑不太复杂或更通用,可以设置custom output formatter来防止某些字段写入Respose。

该方法还有下一个问题:

  1. 不应处理复杂的逻辑。由于它导致业务逻辑传播到多个地方
  2. 替换默认序列化。因此,如果在mvn dependency:tree中设置了特定的序列化设置,则应该进行传输

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

Startup

然后输出格式化程序可能像:

public class AuthorizePropertyAttribute : Attribute
{
    public AuthorizePropertyAttribute(string role) => Role = role;
    public string Role { get; set; }
}

那需要

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));
    }
}    

注册:

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;
    }
}

在这种情况下,对简单用户的响应将缺少Salary字段services.AddMvc(options => { options.OutputFormatters.Insert(0, new AuthFormatter()); }); ,同时经理将看到完整的响应 {"Id":1,"Name":"John"},当然,“工资”属性应设置属性

{"Id":1,"Name":"John","Salary":100000}

答案 1 :(得分:1)

您应该实现2种不同的方法。一个用于请求数据时的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; }
}

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

Mapper.Map<DTOxxxx>(yourObject);

您还可以使用[Authorize]属性来检查用户是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);
    }
}

我敢肯定有很多更好的解决方案,但是对我来说,这非常简单,在其他应用程序中难以实现,并且不会出现明显的性能损失