从 JavaScript 访问 WCF WebService - 对预检请求的响应未通过访问控制检查

Posted

技术标签:

【中文标题】从 JavaScript 访问 WCF WebService - 对预检请求的响应未通过访问控制检查【英文标题】:Access to WCF WebService from JavaScript - Response to preflight request doesn't pass access control check 【发布时间】:2018-01-07 23:33:46 【问题描述】:

我正在使用 WCF (.svc) WebService,它运行良好 - 我可以毫无问题地从 Postman、php 等调用它的方法。但是,当我尝试从 JavaScript/jQuery调用它时> 使用 AJAX,有一个明显的问题——我是从 WS 以外的其他域做的,所以它不会让我做。

这都是关于 POST 方法的。但是,即使我的页面首先发送一个 OPTIONS 方法,也会出现问题:

选项'WS ADDRESS' 405(不允许的方法)

XMLHttpRequest 无法加载 'WS ADDRESS' 对预检请求的响应没有 通过访问控制检查:没有“Access-Control-Allow-Origin”标头 出现在请求的资源上。因此,来源 'MY ADDRESS' 是不允许的 使用权。响应的 HTTP 状态代码为 405。

有详细回复:

好的,我阅读了有关跨域的信息,并且 WS 的 Web.config 包含所有必要的内容(?)

请让我知道我做错了什么,我无法从 javascript 访问我的 WS,即使在我看来它配置得很好。但是,它似乎没有发送这些特殊的标头作为响应......提前致谢。

【问题讨论】:

该 405 代码意味着您的服务器已配置为禁止 OPTIONS 请求。这就是 Method Not Allowed 消息的含义。您必须将其配置为使用 200 或 204 成功消息和正确的 Access-Control-Allow-* 标头来响应 OPTIONS 请求。至于如何做到这一点,听起来您需要阅读更多文档并进一步研究它——从***.com/questions/16024347/… 和***.com/questions/43911702/…开始 【参考方案1】:

XML HttpRequest 允许将数据从 Web 服务器传输到浏览器。然而,浏览器默认阻止CORS。 GET 请求不会更改服务器端的任何内容(没有创建/更新/删除任何内容) - 它只会返回一些数据。

但是,POST/PUT/DELETE 请求将处理请求并在服务器端更改某些内容并向浏览器发出响应。如果响应没有正确的Access-Control-Allow-Origin 标头,浏览器将阻止响应。但这并不重要,因为在发出响应时,服务器已经处理了请求并进行了更改,可能对数据库进行了更改。

为了防止服务器端处理POST/PUT/DELETE请求,浏览器会发送preflight request。

prefligth 请求是一个带有 OPTIONS 方法的 http 请求。因此,在发送 POST 请求之前,浏览器会发送一个 OPTIONS 请求,其中包含一个名为 Access-Control-Request-Method 的附加标头,其值为 POST。

405 (Method Not Allowed) 错误表示服务器未配置为接受 OPTIONS 请求。

您可以通过为您的网络调用方法使用通配符来解决此问题,类似于:

[OperationContract]
[WebInvoke(Method = "*", UriTemplate = "/Path", ResponseFormat = WebMessageFormat.Json)]

或者通过向您的[ServiceContract] 添加一个额外的[OperationContract] 来处理类似于这样的OPTIONS 请求:

 [OperationContract(Name = "OptionsMyFunction")]
 [WebInvoke(Method = "OPTIONS", UriTemplate = "/Path", ResponseFormat = WebMessageFormat.Json)]

【讨论】:

【参考方案2】:

您可以为此工作实现 .NET IDispatchMessageInspector。

创建一个实现IDispatchMessageInspector的类 创建一个实现Attribute,IEndpointBehavior,IOperationBehavior的类

只允许在你的类中实现IDispatchMessageInspector的OPTIONS

代码如下所示

using System;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Dispatcher;
using System.ServiceModel.Description;
using System.Collections.Generic;
using System.Net;

namespace WapstyPrintService

    public class MessageInspector : IDispatchMessageInspector
    
        private ServiceEndpoint _serviceEndpoint;
        Dictionary<string, string> requiredHeaders;

        public MessageInspector(ServiceEndpoint serviceEndpoint)
        
            _serviceEndpoint = serviceEndpoint;
            requiredHeaders = new Dictionary<string, string>();

            requiredHeaders.Add("Access-Control-Allow-Origin", "*");
            requiredHeaders.Add("Access-Control-Request-Method", "POST,GET,PUT,DELETE,OPTIONS");
            requiredHeaders.Add("Access-Control-Allow-Headers", "X-Requested-With,Content-Type");
        

        /// <summary>
        /// Called when an inbound message been received
        /// </summary>
        /// <param name="request">The request message.</param>
        /// <param name="channel">The incoming channel.</param>
        /// <param name="instanceContext">The current service instance.</param>
        /// <returns>
        /// The object used to correlate stateMsg. 
        /// This object is passed back in the method.
        /// </returns>
        public object AfterReceiveRequest(ref Message request,
                                              IClientChannel channel,
                                              InstanceContext instanceContext)
        
            var httpRequest = (HttpRequestMessageProperty)request
            .Properties[HttpRequestMessageProperty.Name];
            return new
            
                origin = httpRequest.Headers["Origin"],
                handlePreflight = httpRequest.Method.Equals("OPTIONS",
                StringComparison.InvariantCultureIgnoreCase)
            ;
        

        /// <summary>
        /// Called after the operation has returned but before the reply message
        /// is sent.
        /// </summary>
        /// <param name="reply">The reply message. This value is null if the 
        /// operation is one way.</param>
        /// <param name="correlationState">The correlation object returned from
        ///  the method.</param>
        public void BeforeSendReply(ref Message reply, object correlationState)
        
            var state = (dynamic)correlationState;
            if (state.handlePreflight)
            
                reply = Message.CreateMessage(MessageVersion.None, "PreflightReturn");

                var httpResponse = new HttpResponseMessageProperty();
                reply.Properties.Add(HttpResponseMessageProperty.Name, httpResponse);

                httpResponse.SuppressEntityBody = true;
                httpResponse.StatusCode = HttpStatusCode.OK;
            

            var httpHeader = reply.Properties["httpResponse"] as HttpResponseMessageProperty;
            foreach (var item in requiredHeaders)
            
                httpHeader.Headers.Add(item.Key, item.Value);
            
        
    

using System;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;

namespace WapstyPrintService

    public class BehaviorAttribute : Attribute, IEndpointBehavior,
                                   IOperationBehavior
    
        public void Validate(ServiceEndpoint endpoint)  

        public void AddBindingParameters(ServiceEndpoint endpoint,
                                 BindingParameterCollection bindingParameters)
         

        /// <summary>
        /// This service modify or extend the service across an endpoint.
        /// </summary>
        /// <param name="endpoint">The endpoint that exposes the contract.</param>
        /// <param name="endpointDispatcher">The endpoint dispatcher to be
        /// modified or extended.</param>
        public void ApplyDispatchBehavior(ServiceEndpoint endpoint,
                                          EndpointDispatcher endpointDispatcher)
        
            // add inspector which detects cross origin requests
            endpointDispatcher.DispatchRuntime.MessageInspectors.Add(
                                                   new MessageInspector(endpoint));
        

        public void ApplyClientBehavior(ServiceEndpoint endpoint,
                                        ClientRuntime clientRuntime)
         

        public void Validate(OperationDescription operationDescription)  

        public void ApplyDispatchBehavior(OperationDescription operationDescription,
                                          DispatchOperation dispatchOperation)
         

        public void ApplyClientBehavior(OperationDescription operationDescription,
                                        ClientOperation clientOperation)
         

        public void AddBindingParameters(OperationDescription operationDescription,
                                  BindingParameterCollection bindingParameters)
         

    

然后将您的消息检查器添加到服务端点行为

ServiceHost host = new ServiceHost(typeof(myService), _baseAddress);
foreach (ServiceEndpoint EP in host.Description.Endpoints)
            EP.Behaviors.Add(new BehaviorAttribute());

【讨论】:

以上是关于从 JavaScript 访问 WCF WebService - 对预检请求的响应未通过访问控制检查的主要内容,如果未能解决你的问题,请参考以下文章

如何从 javascript 调用 WCF 服务?

从服务内的 JavaScript HTML 调用 WCF 服务

WCF 服务端点无法从本地访问

稳步减慢从 javascript 对 WCF 服务的 ajax 调用

从 Excel 访问 WCF 服务的最佳方式是啥?

从客户端调用 WCF 服务