37

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

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

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

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

我重写OnAuthorization方法以获取角色并对用户进行身份验证。由于每个事务都必须对用户进行身份验证,因此我通过在同一步骤中执行身份验证和授权来减少来回的喋喋不休。我通过使用在 HTTP 标头中传递加密凭据的基本身份验证从 Web 客户端获取用户凭据。所以我的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 客户端进行往返时才会更新。

所以所有这些都引出了我的具体问题,即是否有另一种方法可以在不使用 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);
     }
 }

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

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

4

3 回答 3

40

我的方案的最佳解决方案似乎是完全绕过基本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,因为它检查当前的Principle以查看它是否isAuthenticatedIsAuthenticated只允许获取,所以我不确定如何设置它,而且我似乎不需要当前的Principle。对此进行了测试,它工作正常。

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

于 2012-09-28T20:18:16.010 回答
29

要添加到已经接受的答案:检查当前源代码 (aspnetwebstack.codeplex.com) System.Web.Http.AuthorizeAttribute,看起来文档已过时。BaseOnAuthorization()仅调用/检查私有静态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);
    }
}

往里看IsAuthorized(),这就是针对角色和用户检查 Principle 的地方。IsAuthorized()所以,用你上面的东西而不是覆盖OnAuthorization()将是要走的路。话又说回来,您仍然可能需要覆盖其中一个OnAuthorization()HandleUnauthorizedRequest()无论如何来决定何时返回 401 与 403 响应。

于 2012-11-17T19:59:12.197 回答
5

为了补充 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.";

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

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

于 2017-06-02T10:52:29.600 回答