基于客户端角色的身份验证

时间:2018-06-06 16:09:18

标签: c# asp.net-web-api oauth roles

目前,我正在使用OAuth和WebApi使用基于角色的身份验证对我的应用程序中的用户进行身份验证。我这样设置了这个:

public override async Task GrantResourceOwnerCredentials (OAuthGrantResourceOwnerCredentialsContext context)
{
    var user = await AuthRepository.FindUser(context.UserName, context.Password);

    if (user === null)
    {
        context.SetError("invalid_grant", "The username or password is incorrect");
        return;
    }

    var id = new ClaimsIdentity(context.Options.AuthenticationType);
    id.AddClaim(New Claim(ClaimTypes.Name, context.UserName));

    foreach (UserRole userRole in user.UserRoles)
    {
        id.AddClaim(new Claim(ClaimTypes.Role, userRole.Role.Name));
    }

    context.Validated(id);
}

使用<Authorize>标记保护我的API路线。

然而,我遇到了一个问题,我的用户可以为不同的客户端担任不同的角色。例如:

用户A可以与多个客户端关联:客户端A和客户端B.
当从任一客户端访问信息时,用户A可以具有不同的“角色”。因此,用户A可以是客户A的管理员和客户B的基本用户。

这意味着,以下示例:

[Authorize(Roles = "Admin")]
[Route("api/clients/{clientId}/billingInformation")]
public IHttpActionResult GetBillingInformation(int clientId) 
{
    ...
}

用户A可以访问客户端A的帐单信息,但不能访问客户端B.

显然,我现在拥有的这种身份验证不起作用。设置客户端特定的基于角色的身份验证的最佳方法是什么?我可以简单地改变我现在拥有的东西,还是我必须完全以不同的方式设置它?

4 个答案:

答案 0 :(得分:1)

您可以删除authorize标记并在函数内部执行角色验证。

Lambda解决方案:

是否存在基于CustomerID和UserID添加的角色?
如果是这样,您可以执行类似下面示例的操作,根据您拥有的值获取客户,然后返回响应。

string userID = RequestContext.Principal.Identity.GetUserId();
var customer = Customer.WHERE(x => x.UserID == userID && x.clientId == clientId && x.Roles == '1')

您能否向我们提供有关您用于存储客户与用户之间的连接/角色的更多信息。

修改

以下是有关如何使用ActionFilterAttribute的示例。它从请求中获取CustomerId,然后从请求中获取标识的UserId。因此,您可以使用[UserAuthorizeAttribute]

替换[Authorize]
    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)]
    public class UserAuthorizeAttribute : System.Web.Http.Filters.ActionFilterAttribute
    {
        public override void OnActionExecuting(HttpActionContext actionContext)
        {
            try
            {
                var authHeader = actionContext.Request.Headers.GetValues("Authorization").First();
                if (string.IsNullOrEmpty(authHeader))
                {
                    actionContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.BadRequest)
                    {
                        Content = new StringContent("Missing Authorization-Token")
                    };
                    return;
                }

                ClaimsPrincipal claimPrincipal = actionContext.Request.GetRequestContext().Principal as ClaimsPrincipal;
                if (!IsAuthoticationvalid(claimPrincipal))
                {
                    actionContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.BadRequest)
                    {
                        Content = new StringContent("Invalid Authorization-Token")
                    };
                    return;
                }

                if (!IsUserValid(claimPrincipal))
                {
                    actionContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.BadRequest)
                    {
                        Content = new StringContent("Invalid User name or Password")
                    };
                    return;
                }

                //Finally role has perpession to access the particular function
                if (!IsAuthorizationValid(actionContext, claimPrincipal))
                {
                    actionContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.BadRequest)
                    {
                        Content = new StringContent("Permission Denied")
                    };
                    return;
                }

            }
            catch (Exception ex)
            {
                actionContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.BadRequest)
                {
                    Content = new StringContent("Missing Authorization-Token")
                };
                return;
            }

            try
            {
                //AuthorizedUserRepository.GetUsers().First(x => x.Name == RSAClass.Decrypt(token));
                base.OnActionExecuting(actionContext);
            }
            catch (Exception)
            {
                actionContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.Forbidden)
                {
                    Content = new StringContent("Unauthorized User")
                };
                return;
            }
        }

        private bool IsAuthoticationvalid(ClaimsPrincipal claimPrincipal)
        {
            if (claimPrincipal.Identity.AuthenticationType.ToLower() == "bearer"
                && claimPrincipal.Identity.IsAuthenticated)
            {
                return true;
            }
            return false;
        }

        private bool IsUserValid(ClaimsPrincipal claimPrincipal)
        {
            string userID = claimPrincipal.Identity.GetUserId();
            var securityStamp = claimPrincipal.Claims.Where(c => c.Type.Equals("AspNet.Identity.SecurityStamp", StringComparison.OrdinalIgnoreCase)).Single().Value;

            var user = _context.AspNetUsers.Where(x => x.userID.Equals(userID, StringComparison.OrdinalIgnoreCase)
                && x.SecurityStamp.Equals(securityStamp, StringComparison.OrdinalIgnoreCase));
            if (user != null)
            {
                return true;
            }
            return false;
        }

        private bool IsAuthorizationValid(HttpActionContext actionContext, ClaimsPrincipal claimPrincipal)
        {
            string userId = claimPrincipal.Identity.GetUserId();
            string customerId = (string)actionContext.ActionArguments["CustomerId"];
            return AllowedToView(userId, customerId);
        }

        private bool AllowedToView(string userId, string customerId)
        {
            var customer = _context.WHERE(x => x.UserId == userId && x.CustomerId == customerId && x.RoleId == '1')
            return false;
        }
    }

答案 1 :(得分:1)

我个人认为你需要完全不使用[Authorize]属性。很明显,您的授权要求比该方法更复杂,并且开箱即用#34;是为了。

此外,关于我认为身份验证和授权可以互换使用的问题。我们在这里处理的是授权。

由于您使用的是基于身份和声明的授权。我会看着这样做&#34;在飞行中&#34;可以这么说。除了声明之外,您还可以使用动态策略生成以及使用IAuthorizationRequirement实例的基于服务的授权来构建复杂的规则和要求。

深入研究这一问题是一个很大的话题,但有一些非常好的资源可供使用。最初的方法(我自己使用过)由Dom和Brock以IdentityServer成名进行了详细描述。

他们去年在国家数据中心对此进行了全面的视频演示,您可以观看here

基于此视频中讨论的概念,Jerrie Pelser在博客中写了一篇关于密切实施的文章,您可以阅读here

一般组成部分是:

[授权]属性将由策略生成器替换,例如:

public class AuthorizationPolicyProvider : DefaultAuthorizationPolicyProvider
{
    private readonly IConfiguration _configuration;

    public AuthorizationPolicyProvider(IOptions<AuthorizationOptions> options, IConfiguration configuration) : base(options)
    {
        _configuration = configuration;
    }

    public override async Task<AuthorizationPolicy> GetPolicyAsync(string policyName)
    {
        // Check static policies first
        var policy = await base.GetPolicyAsync(policyName);

        if (policy == null)
        {
            policy = new AuthorizationPolicyBuilder()
                .AddRequirements(new HasScopeRequirement(policyName, $"https://{_configuration["Auth0:Domain"]}/"))
                .Build();
        }

        return policy;
    }
}

然后你会创建IAuthorizationRequirement所需的任何实例来确保用户被正确授权,例如:

public class HasScopeRequirement : IAuthorizationRequirement
{
    public string Issuer { get; }
    public string Scope { get; }

    public HasScopeRequirement(string scope, string issuer)
    {
        Scope = scope ?? throw new ArgumentNullException(nameof(scope));
        Issuer = issuer ?? throw new ArgumentNullException(nameof(issuer));
    }
}
然后,Dom和Brock还详细介绍了一个客户端实现,它将所有这些结合在一起,可能看起来像这样:

  public class AuthorisationProviderClient : IAuthorisationProviderClient
  {
    private readonly UserManager<ApplicationUser> userManager;
    private readonly RoleManager<IdentityRole> roleManager;

    public AuthorisationProviderClient(
      UserManager<ApplicationUser> userManager, 
      RoleManager<IdentityRole> roleManager)
    {
      this.userManager = userManager;
      this.roleManager = roleManager;
    }

    public async Task<bool> IsInRole(ClaimsPrincipal user, string role)
    {
      var appUser = await GetApplicationUser(user);
      return await userManager.IsInRoleAsync(appUser, role);
    }

    public async Task<List<Claim>> GetAuthorisationsForUser(ClaimsPrincipal user)
    {
      List<Claim> claims = new List<Claim>();
      var appUser = await GetApplicationUser(user);

      var roles = await userManager.GetRolesAsync(appUser);

      foreach (var role in roles)
      {
        var idrole = await roleManager.FindByNameAsync(role);

        var roleClaims = await roleManager.GetClaimsAsync(idrole);

        claims.AddRange(roleClaims);
      }

      return claims;
    }

    public async Task<bool> HasClaim(ClaimsPrincipal user, string claimValue)
    {
      Claim required = null;
      var appUser = await GetApplicationUser(user);

      var userRoles = await userManager.GetRolesAsync(appUser);

      foreach (var userRole in userRoles)
      {
        var identityRole = await roleManager.FindByNameAsync(userRole);

        // this only checks the AspNetRoleClaims table
        var roleClaims = await roleManager.GetClaimsAsync(identityRole);
        required = roleClaims.FirstOrDefault(x => x.Value == claimValue);

        if (required != null)
        {
          break;
        }
      }

      if (required == null)
      {
        // this only checks the AspNetUserClaims table
        var userClaims = await userManager.GetClaimsAsync(appUser);
        required = userClaims.FirstOrDefault(x => x.Value == claimValue);
      }

      return required != null;
    }

    private async Task<ApplicationUser> GetApplicationUser(ClaimsPrincipal user)
    {
      return await userManager.GetUserAsync(user);
    }
  }

虽然这个实现并没有解决你的确切要求(无论如何都很难做到),但这几乎可以肯定是我采用的方法,考虑到你在问题中说明的情景。

答案 2 :(得分:1)

一种解决方案是将客户端/用户关系添加为ClaimsIdentity的一部分,然后使用派生的AuthorizeAttribute进行检查。

您可以使用包含其所有角色的字典以及在该角色中授权的客户端扩展User对象 - 可能包含在您的数据库中:

public Dictionary<string, List<int>> ClientRoles { get; set; }

GrantResourceOwnerCredentials方法中,您可以将这些作为单个声明添加,并将客户ID作为值:

foreach (var userClientRole in user.ClientRoles)
{
    oAuthIdentity.AddClaim(new Claim(userClientRole.Key,
        string.Join("|", userClientRole.Value)));
}

然后创建一个自定义属性来处理读取声明值。这里稍微棘手的部分是获得clientId值。您已经给出了一个示例,它在路径中,但在您的应用程序中可能不一致。您可以考虑在标头中显式传递它,或者在任何所需的情况下派生任何URL / Route解析函数。

public class AuthorizeForCustomer : System.Web.Http.AuthorizeAttribute
{
    protected override bool IsAuthorized(HttpActionContext actionContext)
    {
        var isAuthorized = base.IsAuthorized(actionContext);

        string clientId = ""; //Get client ID from actionContext.Request;

        var user = actionContext.ControllerContext.RequestContext.Principal as ClaimsPrincipal;
        var claim = user.FindFirst(this.Roles);
        var clientIds = claim.Value.Split('|');

        return isAuthorized && clientIds.Contains(clientId);
    }
}

你只需要交换

[Authorize(Roles = "Admin")]

[AuthorizeForCustomer(Roles = "Admin")]

请注意,这个简单的示例仅适用于单个角色,但您可以理解。

答案 3 :(得分:0)

要求是让用户拥有不同的授权。不要强迫用户权限/授权与其角色严格匹配。角色是用户身份的一部分,不应该依赖于客户端。
我建议分解要求:

注意:声明应仅包含用户身份数据(姓名,电子邮件,角色等)。添加授权,令牌中的访问权限声明在我的选择中不是一个好的选择:

  • 令牌大小可能会急剧增加

  • 用户可能对域有不同的授权 上下文或微服务

以下一些有用的链接:

https://docs.microsoft.com/en-us/dotnet/framework/security/claims-based-identity-model
https://leastprivilege.com/2016/12/16/identity-vs-permissions/
https://leastprivilege.com/2014/06/24/resourceaction-based-authorization-for-owin-and-mvc-and-web-api/