cannot POST to IHTTPHandler class, origin not allo

2019-05-07 13:19发布

问题:

I have a C# SOAP web service along with some custom IHTTPHandler classes deployed to https://localhost:123. I have an HTML page on http://localhost trying to use jQuery to send an AJAX POST request to one of those handlers. I get the following every time:

XMLHttpRequest cannot load https://localhost:123/(S(the_session_id))/MyHandler.ashx. Origin http://localhost is not allowed by Access-Control-Allow-Origin.

I have tried creating a CrossOriginModule module and CrossOriginHandler handler like in this question, updating my web.config as they suggest. I have tried adding context.Response.AppendHeader("Access-Control-Allow-Headers", "x-requested-with"); to my handler's ProcessRequest, as suggested in this question. I tried following the steps in this blog post about adding OPTIONS to SimpleHandlerFactory-Integrated-4.0 in system.webServer/handlers in the web.config. Nothing helps.

Here is my handler's ProcessRequest:

    public void ProcessRequest(HttpContext context)
    {
        context.Response.ContentType = "application/json";
        context.Response.AppendHeader("Access-Control-Allow-Origin", "*");
        context.Response.AppendHeader("Access-Control-Allow-Headers", "x-requested-with");
        context.Response.Write( /* some JSON string */ );
    }

Here's a relevant section of my web.config:

<system.webServer>
  <httpProtocol>
    <customHeaders>
      <add name="Access-Control-Allow-Origin" value="*" />
    </customHeaders>
  </httpProtocol>
  <modules runAllManagedModulesForAllRequests="true">
    <add name="CrossOriginModule" preCondition="managedHandler" type="MyNamespace.CrossOriginModule, MyNamespace, Version=1.0.0.0, Culture=neutral" />
  </modules>
  <handlers>
    <add name="CrossOrigin" verb="OPTIONS" path="*" type="MyNamespace.CrossOriginHandler, MyNamespace, Version=1.0.0.0, Culture=neutral" />
    <remove name="SimpleHandlerFactory-Integrated-4.0" />
    <add name="SimpleHandlerFactory-Integrated-4.0" path="*.ashx" verb="GET,HEAD,POST,DEBUG,OPTIONS" type="System.Web.UI.SimpleHandlerFactory" resourceType="Unspecified" requireAccess="Script" preCondition="integratedMode,runtimeVersionv4.0" />
    <add name="MyHandler" verb="*" path="MyHandler.ashx" type="MyNamespace.MyHandler, MyNamespace, Version=1.0.0.0, Culture=neutral" />
  </handlers>
</system.webServer>

Using jQuery 1.7.2, here is how I'm making the request to my handler:

$.ajax({
    url: url,
    data: {user: userName, pass: password},
    type: 'POST',
    dataType: 'json',
    success: function(data, textStatus, jqXHR) {
        ...
    }
});

I also am not using cookies for sessions on the server, so <sessionState cookieless="true" /> is in my web.config, if this matters. My server is IIS 7.5.

I'm able to use JSONP for most requests because I'm not passing sensitive data in the URL. However, for this one handler, I need to do a regular POST because it involves sending a plain-text password. It's over SSL, but this post recommends not passing sensitive data in the URL even over SSL. So I need to do a POST request via AJAX to an https URL and get back JSON, and the requesting page won't be on the same host as the server.

Edit: some other things I've tried:

Changing my web.config httpProtocol section:

<httpProtocol>
  <customHeaders>
    <add name="Access-Control-Allow-Methods" value="OPTIONS,POST,GET"/>
    <add name="Access-Control-Allow-Headers" value="x-requested-with"/>
    <add name="Access-Control-Allow-Origin" value="*" /><!-- Also tried http://localhost -->
  </customHeaders>
</httpProtocol>

I added context.Response.AppendHeader("Access-Control-Allow-Methods", "POST"); to ProcessRequest. I also tried context.Response.AppendHeader("Access-Control-Allow-Methods", "POST,OPTIONS"); and changing my jQuery ajax options to include contentType: 'application/json', which causes it to make an OPTIONS request instead of POST--this still fails.

Am I supposed to list all my custom IHTTPHandler classes in web.config in <handlers>? I have MyHandler there now, with its verb="*", but it doesn't seem to help.

Edit: so some further strangeness. It looks like my request is actually going through on the server side when I POST to it, based on my logging output, but the browser acts like it got rejected, still telling me the 'origin localhost not allowed'. So my AJAX POST to the server authenticates me like I want it to do, but the browser cancels the request and an error shows up in the Chrome console, so my success event handler doesn't trigger.

回答1:

Your handler's ProcessRequest should also set the following header:

Access-Control-Allow-Methods: POST

(Basically this header should be set on the OPTIONS response, along with the Access-Control-Allow-Headers response header).



回答2:

This is dumb, and I'm not sure what's going on, but my POST is going through, my authentication is happening on the server, but the browser thinks it's not allowed, so it fails. I already had a handler that supports JSONP that will tell if the session is still active and the user is authenticated, so I changed my AJAX call to the following:

$.ajax({
    url: url,
    data: {user: userName, pass: password},
    type: 'POST',
    dataType: 'json',
    complete: function(jqXHR, textStatus) {
        checkIfAuthenticated(
            function() { // authenticated
                onComplete(true, null)
            },
            function() { // not authenticated
                onComplete(false, textStatus);
            }
        );
    }
});

So I still have a "XMLHttpRequest cannot load https://.../AuthHandler.ashx. Origin http://localhost is not allowed by Access-Control-Allow-Origin" error showing up in my Chrome console, and the Network tab in Chrome still shows the POST request as cancelled. I end up having to make an extra request here, via the checkIfAuthenticated function, to see if the authentication failed or not, but it works.

My ProcessRequest on the server in MyHandler looks like this:

public void ProcessRequest(HttpContext context)
{
    context.Response.ClearHeaders();
    string origin = context.Request.Headers["Origin"];
    context.Response.AppendHeader("Access-Control-Allow-Origin",
        string.IsNullOrEmpty(origin) ? "*" : origin);
    string requestHeaders = context.Request.Headers["Access-Control-Request-Headers"];
    context.Response.AppendHeader("Access-Control-Allow-Headers",
        string.IsNullOrEmpty(requestHeaders) ? "*" : requestHeaders);
    context.Response.AppendHeader("Access-Control-Allow-Methods", "POST, OPTIONS");
    context.Response.ContentType = "application/json";
    context.Response.Write(/* JSON response */);
    context.Response.Flush();
}

And here's what the relevant section of my web.config ended up looking like:

  <system.webServer>
    <httpProtocol>
      <customHeaders>
        <add name="Access-Control-Allow-Methods" value="OPTIONS,POST,GET"/>
        <add name="Access-Control-Allow-Headers" value="x-requested-with"/>
        <add name="Access-Control-Allow-Origin" value="*" />
      </customHeaders>
    </httpProtocol>
    <handlers>
      <add name="MyHandler" verb="*" path="MyHandler.ashx" type="MyNamespace.MyHandler, MyNamespace, Version=1.0.0.0, Culture=neutral" />
    </handlers>
  </system.webServer>

I would much prefer a solution that convinces my browser that the request was indeed allowed, because this is a stupid workaround.