如何自定义ASP.NET Web API属性以满足异常需求

如何自定义ASP.NET Web API属性以满足异常需求,asp.net,asp.net-mvc,security,asp.net-web-api,Asp.net,Asp.net Mvc,Security,Asp.net Web Api,我继承了System.Web.Http.AuthorizeAttribute来创建自定义授权/身份验证例程,以满足使用ASP.NET MVC 4开发的Web应用程序的一些特殊要求。这为用于来自Web客户端的Ajax调用的Web API增加了安全性。这些要求是: 用户必须在每次执行事务时登录以进行验证 在有人走到工作站后,其他人没有走到工作站 登录并离开 无法在程序时将角色分配给web服务方法。 必须在运行时分配它们,以便管理员可以 配置这个。此信息存储在系统数据库中 web客户端是一个典型的应用

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

  • 用户必须在每次执行事务时登录以进行验证 在有人走到工作站后,其他人没有走到工作站 登录并离开
  • 无法在程序时将角色分配给web服务方法。 必须在运行时分配它们,以便管理员可以 配置这个。此信息存储在系统数据库中
  • web客户端是一个典型的应用程序,因此典型的表单身份验证无法很好地工作,但我正在尝试尽可能多地重用ASP.NET安全框架,以满足要求。定制的authorized属性对于确定与web服务方法关联的角色的需求2非常有用。我接受三个参数:应用程序名、资源名和操作,以确定与方法关联的角色

    public class DoThisController : ApiController
    {
        [Authorize(Application = "MyApp", Resource = "DoThis", Operation = "read")]
        public string GetData()
        {
            return "We did this.";
        }
    }
    
    我重写了OnAuthorization方法以获取角色并对用户进行身份验证。由于每个事务都必须对用户进行身份验证,因此我通过在同一步骤中执行身份验证和授权来减少来回的喋喋不休。我使用基本身份验证从web客户端获取用户凭据,该身份验证在HTTP头中传递加密的凭据。因此,我的授权方法如下所示:

    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);
     }
    
    GetUserName和Password从HTTP头检索凭据。然后我使用Membership.ValidateUser验证凭据。我有一个自定义的成员资格提供程序和角色提供程序插入到一个自定义数据库。如果用户经过身份验证,那么我将检索资源和操作的角色。在此基础上,我使用基本的授权来完成授权过程。这就是它的故障所在

    如果用户经过身份验证,我将使用标准表单身份验证方法登录用户(FormsAuthentication.SetAuthCookie),如果用户身份验证失败,我将其注销(FormsAuthentication.SignOut)。但问题似乎是baseOnAuthorization类无权访问已更新的主体,以便将isauthorizated设置为正确的值。它总是落后一步。我的猜测是,它正在使用一些缓存的值,这些值在往返到web客户端之前不会得到更新

    因此,所有这些导致了我的具体问题,即,是否有其他方法可以在不使用cookies的情况下将IsAuthenticated设置为当前主体的正确值?在我看来,cookies并不真正适用于我每次都必须进行身份验证的特定场景。我知道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客户端返回禁止状态代码,并且它可以相应地响应


    因此,在这种情况下,为当前的原则设置已验证的正确方法是什么?

    对于我的情况,最好的解决方案似乎是完全绕过基本的验证。因为每次我都要验证cookies和缓存,所以这个原则没有多大用处。这就是我提出的解决方案:

    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,因为它会检查当前的原则,查看是否已验证已验证只允许GET,因此我不确定如何设置它,而且我似乎不需要当前的原则。经过测试,效果良好


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

    要添加到已经接受的答案:检查当前源代码(aspnetwebstack.codeplex.com)中的
    System.Web.Http.AuthorizeAttribute
    ,文档似乎已过时。Base
    OnAuthorization()
    只调用/检查私有静态
    SkipAuthorization()
    (它只检查上下文中是否使用了
    AllowAnonymousAttribute
    ,以绕过其余的身份验证检查)。然后,如果未跳过,
    OnAuthorization()
    调用public
    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响应。

    要添加到Kevin绝对正确的答案中,我想说的是,我可能会稍微修改它,以利用响应对象的现有.NET framework路径,确保框架(或其他使用者)中的下游代码不会受到某些无法预测的奇怪特性的不利影响

    具体而言,这意味着使用以下代码:

    actionContext.Response = actionContext.ControllerContext.Request.CreateErrorResponse(HttpStatusCode.Unauthorized, REQUEST_NOT_AUTHORIZED);
    
    而不是:

    actionContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.Unauthorized);
    
    其中,
    未授权的请求是:

    private const string REQUEST_NOT_AUTHORIZED = "Authorization has been denied for this request.";
    
    我从
    SRResources.RequestNotAuthorized
    def中提取了
    string