从 MVC 4 项目授权 ASMX .NET Web 服务的正确方法

Correct way to authorize an ASMX .NET web service from MVC 4 Project

我有一个 ASP.NET MVC 应用程序,它有一个 .asmx 网络服务

我写了一个操作过滤器属性,我想在网络服务的网络方法上使用,检查请求 headers 的用户 ID 和密码,如果无效或不存在则抛出未经授权的响应代码.

但是,他们似乎没有被跟注!断点不会被击中。

首先,使用 MVC 属性是否是授权在 ASMX Web 服务上调用的 Web 服务的可接受方式?

其次,是否有一种better/more授权网络服务方法调用的有效方式?

在回答您的第一个问题时,无法触发 MVC 过滤器和 Web API 过滤器通过 ASMX 网络服务.

  1. 动作过滤器是 MVC 管道的一部分,在控制器(或 API 控制器)上的动作方法之前(或之后)触发被执行。它们只能在 MVC 框架内使用。

    动作过滤器覆盖 MVC 控制器 (OnActionExecuting) 上的虚拟方法。因为只有 MVC 控制器有这样的方法,只有 MVC 管道检查它们

  2. 更糟糕的是,ASMX服务默认使用SOAP协议而不是HTTP 协议。 SOAP 服务无法访问 HTTP 上下文(例如 HttpContext.Current.User)或 HTTP 框架。

    Web 服务可以配置为使用HTTP 协议。但是,即使那样,MVC 特定属性对您也没有帮助。


验证遗留 ASMX 服务的方法

  • 理想的方法是 add a Service Reference 你的 MVC 4 项目,调用你的 ASMX 方法来自 [Authorize] 安全操作方法或 Web API 方法的任何 class 库方法。

    这样,您可以利用 MVCWeb API 身份验证过滤器。

  • 如果您希望直接保护您的 ASMX 服务,您可以通过配置 使用表单身份验证检查 HttpContext.Current.User ASMX 服务使用 HTTP 协议。

在你的 web.config

<location path="SecuredMethod.asmx">
  <system.web>
    <webServices>
      <protocols>
        <add name="HttpGet"/>
        <add name="HttpPost"/>
      </protocols>
    </webServices>
  </system.web>
</location> 

我认为为授权创建自定义属性过滤器是个好主意。 您可以像这样创建自定义授权的服装过滤器。

namespace CustomeFilters
{
    class CustomAuthorize : AuthorizeAttribute
    {
        private const string _securityParameter = "someCredentials"; // Name of the url parameter. 
        public override void OnAuthorization(AuthorizationContext filterContext)
        {
            if (Authorize(filterContext))
            {
                return;
            }

            HandleUnauthorizedRequest(filterContext);
        }


    protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
    {
        //Your logic for unauthorized access
        HttpRequestBase request = filterContext.RequestContext.HttpContext.Request;
        string deviceId = request.Params[_securityParameter]; //Your may have values in request headers

        if (!string.IsNullOrEmpty(_securityParameter ))
        {
            base.HandleUnauthorizedRequest(filterContext);

        }

        //You can also check if request is authorized as basic authentication or not
        //if(!filterContext.HttpContext.User.Identity.IsAuthenticated)
    }


    private bool Authorize(AuthorizationContext actionContext)
    {
            HttpRequestBase request = actionContext.RequestContext.HttpContext.Request;

            // Your authorisation logic goes here..                                     

           //actionContext.RequestContext.HttpContext.Response.StatusCode = 400;

           //actionContext.Result = new JsonResult { JsonRequestBehavior = JsonRequestBehavior.AllowGet, Data = "Request from invalid device !" };

            bool success = <true/false>;//Acording to authorisation logic
            return success;
    }
}

会这样使用

    [CustomAuthorize]
    public ActionResult Test()
    { 
        ViewBag.Message = "Hello World.";

        return View();
    }

此处您从 MVC 授权继承授权属性。 它重写了两种方法:

  1. OnAuthorisation : here your authorisation logic goes.Here it is checking for handelling unauthorized request.
  2. HandleUnauthorizedRequest :here logic for handelling unauthorized access goes.It is calling it's parrent's class HandleUnauthorizedRequest to get executed for handling unauthorized access.

问题的第一部分:

is using MVC attributes an acceptable way of authorizing web service called on an ASMX web service?

根据@DaveAlperovich

MVC 过滤器和 Web API 过滤器无法由 ASMX Web 服务触发。

但是从这个 SO 答案

Since ASMX are also server by the ASP.NET pipeline, you could just use HttpModules, which give you a lot of control on the way in and the way out.

Here's a reference and an example: http://msdn.microsoft.com/en-us/library/aa719858%28VS.71%29.aspx

If you want to make it very "MVC-like" then you would write a custom http module that check the webservice for attributes such as [Authorize] etc. Since ASP.NET MVC is open source you may just use parts of that as a reference how they check for attributes etc and then build it into your HTTPModule.

Link to SO Question

所以我仍然找不到关于您的第一个问题和 我仍然怀疑 某些事件在使用 MVC 属性时不会触发 Web 服务

更新

来到 ASMX 时 (source)

uses XML Information Set for its message format, and relies on application layer protocols, most notably Hypertext Transfer Protocol (HTTP) or Simple Mail Transfer Protocol (SMTP), for message negotiation and transmission

来自 Scott Hanselman Blog 的请求默认情况下不由 ASP.NET MVC 路由机制处理

Why doesn't ASP.NET MVC grab the request? Two reasons. First, there's an option on RouteCollection called RouteExistingFiles. It's set to false by default which causes ASP.NET MVC to automatically skip routing when a file exists on disk.

第二季:

is there a better/more efficient way of authorizing web service method calls?

您可以使用 SOAP header 中发送的凭据来授权 Web 服务

这是一个例子:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Services;
using System.Web.Services.Protocols;

namespace AuthExample
{

    public class Authentication : SoapHeader
    {
        public string User;
        public string Password;
    }

    /// <summary>
    /// Summary description for webrusterapi
    /// </summary>
    [WebService(Namespace = "http://xxxx.xxx")]
    [WebServiceBinding(ConformsTo = Profiles.BProfile1)]
    [System.ComponentModel.ToolboxItem(false)]
    public class Webrusterapi: System.Web.Services.WebService
    {
        public Authentication authHeader;

        [WebMethod]
        public string HelloWorld()
        {
            return "Hello World";
        }

        [WebMethod]
        [SoapHeader("authHeader")]
        public string HelloWorldWithCredentials()
        {
            if (authHeader.User != "Foouser" & authHeader.Password != "barPassword")
            {
                new SoapException("Fault occurred", SoapException.ClientFaultCode);
            }
            return string.Format("Hello {0}", authHeader.User);
        }
    }
}

.ASMX 服务和 AuthorizeAttribute

MVC 和 Web API 管道不直接与旧样式的 .ASMX Web 服务兼容。这就是为什么当您将它们放在您的 Web 方法上时,这些属性不会触发。根据您的代码库,您可以将代码转换(重写)到 Web API 2 平台,这是编写服务的新推荐方式。与传统的 Web 服务 (.asmx) 相比,它具有以下优点。

  1. 数据格式 - 客户端可以确定接收到的数据的格式。目前支持的 2 种开箱即用格式是 xmljson。这对于客户端来说更容易使用,因为发送和接收的有效负载通常更简单,更不用说重量轻了(.asmx 服务中使用的 SOAP 信封非常臃肿。)
  2. 动作过滤器 - 您可以利用授权属性和自定义动作过滤器来执行常见的动作预处理和 post 处理。
  3. HTTP 动词的意图 - 操作将基于 HTTP 而不是 SOAP。您可以利用 http 方法为公开的 Web 方法(GET、POST、PUT、DELETE)赋予直接意义。
  4. 易于跨开发平台使用 - 在所有其他条件相同的情况下,针对 Web api 方法编写比 ASMX 方法要简单得多,因为有效载荷和由于没有膨胀,响应更容易翻译。一些标准类型也更容易翻译,它们通常包含在 WSDL 定义语言中,例如可空类型和 .net DateTime 实例。

问题就变成了"Should you convert your existing code?" 那要看:

  • 您是否有已经调用您的代码库的现有客户也需要转换?如果这些客户端不在您的控制范围内(例如,如果这些调用客户端是由您的客户创建的),这可能无法实现。
  • 您已经创建的方法的大小或数量。如果您刚刚开始您的项目,那么转换到 Web 可能仍然很容易 Api 但如果您已经拥有大量带有单元测试的代码库,这可能不符合成本效益。
  • 如果您已经与外部各方围绕您的代码定义了合同。可以在 WSDL 中创建定义,并根据该合同同时进行客户端开发和服务器端开发 运行,如果是这种情况,您需要说服您的客户端开发人员/各方进行转换到 Web API 合同,但这可能是不可能的。

我要避免的是在 Web API 中编写一个包装器来调用 Web 服务。

  • 它在调用之间添加了一个物理层(跨越另一个网络边界),这增加了发送消息和接收响应之间的延迟
  • 它添加了您必须创建和维护的另一层代码
  • 额外的(不必要的)层也像这样,当您必须实施更改时,很容易无意中引入缺陷

.ASMX 授权

现在假设您要继续使用 .ASMX Web 服务并解决问题 How can you execute Authorization on a web service

传统/事实上

传统的身份验证方法是在 SOAP header 中包含身份验证信息。这可以使用现有的 SoapHeaderAttribute 轻松完成。

  • SoapHeaderAttribute 放在您的网络方法上
  • 创建一个身份验证 object,它将包含用于身份验证的传入参数
  • 写入认证方式

我更喜欢创建我的其他服务继承的抽象基础服务。这应该会创建更少的重复代码。这是一个没有身份验证详细信息的完整示例。在这个例子中,我使用了传统的用户名和密码,但实际上它可以是任何东西(令牌、密码哈希、HMAC 信息等),因为这有点超出了问题的范围,我不会进入身份验证细节。

using System.Web.Services;
using System.Web.Services.Protocols;

public abstract class AuthorizedWebService : System.Web.Services.WebService 
{
    // authentication info
    public Authentication Authentication { get; set; }
    // execute the actual authentication and authorization check
    protected virtual void Authorize()
    {
        // check the Authentication instance object (passed in credentials)
        // if not authenticate or authorized
        // throw new System.Security.Authentication.AuthenticationException();
    }
}
// authentication info
public class Authentication : SoapHeader
{
    public string Username { get; set; }
    public string Password { get; set; }
}

[WebService(Namespace = "http://tempuri.org/")]
public class MyTraditionalWebService : AuthorizedWebService
{

    [WebMethod(Description = "Some web method.")]
    [SoapHeader("Authentication")]
    public string HelloWorld()
    {
        base.Authorize();
        return "Hello " + base.Authentication.Username;
    }
}

活动目录身份验证

您可以使用 Active Directory 身份验证。在 c# 中,客户端将使用 NetworkCredential class. Essentially what the client does is apply the authentication credentials to the HTTP header. I found this rather good SO answer 传递凭据,NetworkCredential class 在进行 HTTP 调用时将实际转换为什么。您必须配置 IIS,以便在请求到达您的方法之前进行身份验证和授权。您也可以直接在您的网络方法中执行自定义代码以进行授权(与上述类似),但不执行身份验证。

表单身份验证

至于表单身份验证,没有比上面指定的 Traditional / defacto 方法有任何优势的 Web 服务来执行此操作的好方法。但是,如果您已经有一个网站设置(现在)包括您的 .asmx 服务,那么您可以在 web.config 中包括对服务的授权,假设客户端已经通过您的站点的身份验证。

自定义 HttpModule

您还可以编写自定义 HttpModule 来处理身份验证和可能的授权。这种方法的优点是您将 Web 服务中的业务逻辑与 authentication/authorization 分离。这是一把双刃剑,因为您必须维护一个模块来解析 url 以及查看请求是否被授权的预期方法。这可能会导致脆弱的耦合。