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

时间:2012-09-27 20:28:44

标签: asp.net asp.net-mvc security asp.net-web-api

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

  1. 用户每次执行事务时都必须登录才能进行验证 有人没有其他人没有走到工作站 登录并离开。
  2. 无法在程序时将角色分配给Web服务方法。 必须在运行时分配它们,以便管理员可以 配置这个。此信息存储在系统数据库中。
  3. Web客户端是single page application (SPA),因此典型的表单身份验证不能很好地工作,但我正在尝试尽可能多地重用ASP.NET安全框架以满足要求。自定义 AuthorizeAttribute 对于确定与Web服务方法关联的角色的要求2非常有效。我接受三个参数,应用程序名称,资源名称和操作来确定哪些角色与方法相关联。

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

    我覆盖 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);
     }
    

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

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

    因此,所有这些导致了我的具体问题,即是否有另一种方法可以将 IsAuthenticated 设置为当前 Principal 的正确值而不使用Cookie?在我看来,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);
         }
     }
    

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

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

3 个答案:

答案 0 :(得分:37)

我的方案的最佳解决方案似乎完全绕过了基础 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);
    }
}

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

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

答案 1 :(得分:25)

要添加已经接受的答案:检查System.Web.Http.AuthorizeAttribute的当前源代码(aspnetwebstack.codeplex.com),看起来文档已过期。 Base OnAuthorization()只调用/检查私有静态SkipAuthorization()(它只检查上下文中是否使用AllowAnonymousAttribute来绕过其余的身份验证检查)。然后,如果没有跳过,OnAuthorization()会调用公共IsAuthorized(),如果该调用失败,则会调用受保护的虚拟HandleUnauthorizedRequest()。这就是它的全部......

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

    if (SkipAuthorization(actionContext))
    {
        return;
    }

    if (!IsAuthorized(actionContext))
    {
        HandleUnauthorizedRequest(actionContext);
    }
}

查看IsAuthorized()内部,这是针对角色和用户检查原则的位置。因此,使用上面的内容而不是IsAuthorized()覆盖OnAuthorization()将是最佳选择。然后,您仍然可能必须覆盖OnAuthorization()HandleUnauthorizedRequest(),以决定何时返回401与403响应。

答案 2 :(得分:4)

为了给Kevin添加绝对正确的答案,我想说我可能稍微修改它以利用响应对象的现有.NET框架路径来确保框架(或其他消费者)中的下游代码是没有受到一些无法预测的奇怪特质的不利影响。

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

actionContext.Response = actionContext.ControllerContext.Request.CreateErrorResponse(HttpStatusCode.Unauthorized, REQUEST_NOT_AUTHORIZED);

而不是:

actionContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.Unauthorized);

REQUEST_NOT_AUTHORIZED的位置:

private const string REQUEST_NOT_AUTHORIZED = "Authorization has been denied for this request.";

我从.NET框架中的string定义中提取了SRResources.RequestNotAuthorized

很棒的回答凯文!我以同样的方式实现了我,因为在基类中执行OnAuthorization没有任何意义,因为我正在验证我们的应用程序自定义的HTTP头,并且实际上并不想检查Principal,因为它没有'一个。