如何为异常需求自定义ASP.NET Web API AuthorizeAttribute

Kev*_*ans 35 asp.net security asp.net-mvc asp.net-web-api

我继承自System.Web.Http.AuthorizeAttribute以创建自定义授权/身份验证例程,以满足使用ASP.NET MVC 4开发的Web应用程序的一些不寻常的要求.这增加了用于来自Web的Ajax调用的Web API的安全性客户.要求是:

  1. 用户必须在每次执行事务时登录,以验证其他人在登录并离开后没有走到工作站.
  2. 在程序时无法将角色分配给Web服务方法.必须在运行时分配它们,以便管理员可以对其进行配置.此信息存储在系统数据库中.

Web客户端是单页面应用程序(SPA),因此典型的表单身份验证不能很好地工作,但我尝试尽可能多地重用ASP.NET安全框架以满足要求.定制的AuthorizeAttribute非常适合于确定与Web服务方法关联的角色的要求2.我接受三个参数,应用程序名称,资源名称和操作来确定哪些角色与方法相关联.

public class DoThisController : ApiController
{
    [Authorize(Application = "MyApp", Resource = "DoThis", Operation = "read")]
    public string GetData()
    {
        return "We did this.";
    }
}
Run Code Online (Sandbox Code Playgroud)

我重写OnAuthorization方法以获取角色并验证用户身份.由于必须针对每个事务对用户进行认证,因此通过在同一步骤中执行认证和授权来减少来回聊天.我通过使用基本身份验证从Web客户端获取用户凭据,该身份验证传递HTTP标头中的加密凭据.所以我的OnAuthorization方法如下所示:

public override void OnAuthorization(HttpActionContext actionContext)
{

     string username;
     string password;
     if (GetUserNameAndPassword(actionContext, out username, out password))
     {
         if (Membership.ValidateUser(username, password))
         {
             FormsAuthentication.SetAuthCookie(username, false);
             base.Roles = GetResourceOperationRoles();
         }
         else
         {
             FormsAuthentication.SignOut();
             base.Roles = "";
         }
     }
     else
     {
         FormsAuthentication.SignOut();
         base.Roles = "";
     }
     base.OnAuthorization(actionContext);
 }
Run Code Online (Sandbox Code Playgroud)

GetUserNameAndPassword从HTTP标头中检索凭据.然后,我使用Membership.ValidateUser验证凭据.我有一个自定义成员资格提供程序和角色提供程序插入到自定义数据库.如果用户已通过身份验证,则我将检索资源和操作的角色.从那里我使用基于OnAuthorization的基础来完成授权过程.这是它崩溃的地方.

如果用户已通过身份验证,我使用标准表单身份验证方法将用户登录到(FormsAuthentication.SetAuthCookie),如果他们失败,我将其注销(FormsAuthentication.SignOut).但问题似乎是基于OnAuthorization类无法访问更新的Principal,以便将IsAuthenticated设置为正确的值.它总是落后一步.我的猜测是,它使用的是一些缓存值,直到Web客户端往返才会更新.

因此,所有这些导致了我的具体问题,即是否有另一种方法可以在不使用cookie的情况下将IsAuthenticated设置为当前Principal的正确值?在我看来,cookie并不真正适用于我必须每次都进行身份验证的特定场景.我知道IsAuthenticated没有设置为正确值的原因是我也覆盖了HandleUnauthorizedRequest方法:

 protected override void HandleUnauthorizedRequest(HttpActionContext filterContext)
 {
     if (((System.Web.HttpContext.Current.User).Identity).IsAuthenticated)
     {
         filterContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.Forbidden);
     }
     else
     {
         base.HandleUnauthorizedRequest(filterContext);
     }
 }
Run Code Online (Sandbox Code Playgroud)

如果失败是由于授权而不是身份验证,它允许我将状态代码Forbidden返回给Web客户端,并且它可以相应地做出响应.

那么在这种情况下为当前原理设置IsAuthenticated的正确方法是什么?

Kev*_*ans 38

我的场景的最佳解决方案似乎完全绕过了OnAuthorization.因为我每次都必须验证cookie和缓存原理并没有多大用处.所以这是我提出的解决方案:

public override void OnAuthorization(HttpActionContext actionContext)
{
    string username;
    string password;

    if (GetUserNameAndPassword(actionContext, out username, out password))
    {
        if (Membership.ValidateUser(username, password))
        {
            if (!isUserAuthorized(username))
                actionContext.Response = 
                    new HttpResponseMessage(System.Net.HttpStatusCode.Forbidden);
        }
        else
        {
            actionContext.Response = 
                new HttpResponseMessage(System.Net.HttpStatusCode.Unauthorized);
        }
    }
    else
    {
        actionContext.Response = 
            new HttpResponseMessage(System.Net.HttpStatusCode.BadRequest);
    }
}
Run Code Online (Sandbox Code Playgroud)

我开发了自己的方法来验证名为isUserAuthorized的角色,我不再使用基础OnAuthorization,因为它检查当前的Principle以查看它是否已经过身份验证. IsAuthenticated只允许获取,所以我不确定如何设置它,我似乎不需要当前的原则.测试了这一点,它工作正常.

如果有人有更好的解决方案或可以看到任何问题,仍然感兴趣.

  • @VijayBalkawade - 此代码现在是SimpleSecurity开源项目的一部分.您可以在此处获取GetUserNameAndPassword的完整源代码.https://simplesecurity.codeplex.com/SourceControl/latest#SimpleSecurity.Filters/BasicAuthorizeAttribute.cs (3认同)

bob*_*yer 25

要添加到已经接受的答案:检查当前的源代码(aspnetwebstack.codeplex.com)System.Web.Http.AuthorizeAttribute,看起来文档已经过时了.Base OnAuthorization()只调用/检查私有静态SkipAuthorization()(它只检查是否AllowAnonymousAttribute在上下文中使用以绕过其余的身份验证检查).然后,如果没有跳过,则OnAuthorization()调用public IsAuthorized(),如果该调用失败,则调用protected virtual HandleUnauthorizedRequest().这就是它所做的一切......

public override void OnAuthorization(HttpActionContext actionContext)
{
    if (actionContext == null)
    {
        throw Error.ArgumentNull("actionContext");
    }

    if (SkipAuthorization(actionContext))
    {
        return;
    }

    if (!IsAuthorized(actionContext))
    {
        HandleUnauthorizedRequest(actionContext);
    }
}
Run Code Online (Sandbox Code Playgroud)

从内部看IsAuthorized(),这就是针对角色和用户检查原则的地方.所以,IsAuthorized()用你上面所拥有的东西代替,而不是OnAuthorization()将要走的路.然后,你仍然可能必须覆盖任何一个OnAuthorization()或者HandleUnauthorizedRequest()无论如何决定何时返回401和403的响应.


Mik*_*oud 5

为了补充凯文绝对正确的答案,我想说我可能会稍微修改它以利用响应对象的现有 .NET 框架路径,以确保框架(或其他使用者)中的下游代码不会受到不利影响通过一些无法预测的奇怪特质。

具体来说,这意味着使用以下代码:

actionContext.Response = actionContext.ControllerContext.Request.CreateErrorResponse(HttpStatusCode.Unauthorized, REQUEST_NOT_AUTHORIZED);
Run Code Online (Sandbox Code Playgroud)

而不是:

actionContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.Unauthorized);
Run Code Online (Sandbox Code Playgroud)

在哪里REQUEST_NOT_AUTHORIZED

private const string REQUEST_NOT_AUTHORIZED = "Authorization has been denied for this request.";
Run Code Online (Sandbox Code Playgroud)

stringSRResources.RequestNotAuthorized.NET 框架中的定义中提取了它。

很好的答案凯文!我以同样的方式实现了我的,因为OnAuthorization在基类中执行没有意义,因为我正在验证一个为我们的应用程序定制的 HTTP 标头,实际上根本不想检查 Principal,因为没有一个。