首页 文章

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

提问于
浏览
32

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

  • 用户必须在每次执行交易时登录,以验证其他人在登录并离开后没有走到工作站 .

  • 无法在程序时将角色分配给Web服务方法 . 必须在运行时分配它们,以便管理员可以对其进行配置 . 此信息存储在系统数据库中 .

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客户端往返才会更新 .

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

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

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

3 回答

  • 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 ,因为它检查当前的 Principle 以查看它是否 isAuthenticated . IsAuthenticated 只允许获取,所以我不知道如何设置它,我似乎不需要当前的 Principle . 测试了这一点,它工作正常 .

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

  • 2

    要添加到已接受的答案:检查 System.Web.Http.AuthorizeAttribute 的当前源代码(aspnetwebstack.codeplex.com),看起来文档已过期 . 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);
        }
    }
    

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

  • 24

    为了增加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框架中的 SRResources.RequestNotAuthorized 定义中删除了 string .

    凯文很棒!我以同样的方式实现了我,因为在基类中执行 OnAuthorization 是没有意义的,因为我正在验证我们的应用程序自定义的HTTP标头并且没有't actually want to check the Principal at all because there wasn't .

相关问题