自托管WCF REST应用:如何禁用OPTIONS预检请求的身份验证?
解决WCF启用Basic认证后OPTIONS请求401的问题
问题背景
我有一个采用C#、C++混合开发的自托管WCF应用,提供REST风格API供Web客户端访问。已实现CORS以支持OPTIONS请求,未启用身份验证时功能正常,但启用Basic身份验证后,OPTIONS请求返回HTTP/1.1 401 Unauthorized和WWW-Authenticate: Basic realm="",需要禁用OPTIONS请求的身份验证。
现有配置文件:
<?xml version="1.0" encoding="utf-8"?> <configuration> <system.serviceModel> <bindings> <webHttpBinding> <binding name="BasicSecurity" maxBufferSize="1000000" maxReceivedMessageSize="1000000"> <readerQuotas maxDepth="1000000" maxStringContentLength="65535"/> <security mode="Transport"> <transport clientCredentialType="Basic"/> </security> </binding> </webHttpBinding> </bindings> <services> <service behaviorConfiguration="JustMetaData" name="soapcon.SimpleContract"> <endpoint address="https://localhost:8888/Simple" binding="webHttpBinding" bindingConfiguration="BasicSecurity" name="SimpleContract" contract="soapcon.ISimpleContract" behaviorConfiguration="web"/> <host> <baseAddresses> <add baseAddress="https://localhost:8888/Simple"/> </baseAddresses> </host> </service> </services> <behaviors> <endpointBehaviors> <behavior name="web"> <webHttp/> <crossOriginResourceSharingBehavior/> </behavior> </endpointBehaviors> </behaviors> <extensions> <behaviorExtensions> <add name="crossOriginResourceSharingBehavior" type="soapcon.EnableCorsBehavior, server_console, Version=1.0.0.0, Culture=neutral"/> </behaviorExtensions> </extensions> </system.serviceModel> </configuration>
现有C代码(C/CLI):
#pragma once using namespace System; using namespace System::Collections::Generic; using namespace System::ServiceModel::Channels; using namespace System::ServiceModel::Dispatcher; using namespace System::ServiceModel::Description; using namespace System::ServiceModel::Configuration; using namespace System::Text; using namespace System::Threading::Tasks; namespace soapcon { public ref class CustomHeaderMessageInspector : public IDispatchMessageInspector { Dictionary<System::String^, System::String^>^ requiredHeaders; public: CustomHeaderMessageInspector(Dictionary<System::String^, System::String^>^ headers) { if (headers) requiredHeaders = headers; else requiredHeaders = gcnew Dictionary<System::String^, System::String^>(); } virtual System::Object^ AfterReceiveRequest(System::ServiceModel::Channels::Message^% request, System::ServiceModel::IClientChannel^ channel, System::ServiceModel::InstanceContext^ instanceContext) { return nullptr; } virtual void BeforeSendReply(System::ServiceModel::Channels::Message^% reply, System::Object^ correlationState) { if (!reply->Properties->ContainsKey("httpResponse")) { HttpResponseMessageProperty ^httpProp = gcnew HttpResponseMessageProperty(); reply->Properties->Add("httpResponse", httpProp); } { HttpResponseMessageProperty^ httpHeader = dynamic_cast<HttpResponseMessageProperty^>(reply->Properties["httpResponse"]); for each (auto item in requiredHeaders) { httpHeader->Headers->Add(item.Key, item.Value); } } } }; public ref class EnableCorsBehavior : public BehaviorExtensionElement, IEndpointBehavior { public: // IEndpointBehavior virtual void Validate(System::ServiceModel::Description::ServiceEndpoint^) { } virtual void AddBindingParameters(System::ServiceModel::Description::ServiceEndpoint^, System::ServiceModel::Channels::BindingParameterCollection^) { } virtual void ApplyDispatchBehavior(System::ServiceModel::Description::ServiceEndpoint^, System::ServiceModel::Dispatcher::EndpointDispatcher^ endpointDispatcher) { auto requiredHeaders = gcnew Dictionary<System::String^, System::String^>(); requiredHeaders->Add("Access-Control-Allow-Origin", "*"); requiredHeaders->Add("Access-Control-Request-Method", "POST,GET,PUT,DELETE,OPTIONS"); requiredHeaders->Add("Access-Control-Allow-Headers", "Origin, X-Api-Key, X-Requested-With, Content-Type, Authorization, Access-Control-Allow-Headers, Accept, Access-Control-Request-Method, Access-Control-Request-Headers, Cache-Control"); endpointDispatcher->DispatchRuntime->MessageInspectors->Add(gcnew CustomHeaderMessageInspector(requiredHeaders)); } virtual void ApplyClientBehavior(System::ServiceModel::Description::ServiceEndpoint^, System::ServiceModel::Dispatcher::ClientRuntime^) { } // BehaviorExtensionElement property System::Type^ BehaviorType { System::Type^ get() override { return EnableCorsBehavior::typeid; } }; System::Object^ CreateBehavior() override { return gcnew EnableCorsBehavior(); } }; }
解决方案
WCF的Basic认证会在CORS逻辑执行前触发校验,导致OPTIONS请求直接被拦截。需要在消息检查器的请求接收阶段识别OPTIONS请求,直接构造200响应并终止后续流程,跳过认证校验。
1. 修改CustomHeaderMessageInspector的AfterReceiveRequest方法
更新该方法,添加OPTIONS请求的拦截与响应逻辑:
virtual System::Object^ AfterReceiveRequest(System::ServiceModel::Channels::Message^% request, System::ServiceModel::IClientChannel^ channel, System::ServiceModel::InstanceContext^ instanceContext) { HttpRequestMessageProperty^ httpRequest = dynamic_cast<HttpRequestMessageProperty^>(request->Properties[HttpRequestMessageProperty::Name]); // 识别OPTIONS请求 if (httpRequest != nullptr && httpRequest->Method->Equals("OPTIONS", StringComparison::OrdinalIgnoreCase)) { // 构造200响应 HttpResponseMessageProperty^ httpResponse = gcnew HttpResponseMessageProperty(); httpResponse->StatusCode = System::Net::HttpStatusCode::OK; // 添加CORS响应头 httpResponse->Headers->Add("Access-Control-Allow-Origin", "*"); httpResponse->Headers->Add("Access-Control-Allow-Methods", "POST,GET,PUT,DELETE,OPTIONS"); httpResponse->Headers->Add("Access-Control-Allow-Headers", "Origin, X-Api-Key, X-Requested-With, Content-Type, Authorization, Access-Control-Allow-Headers, Accept, Access-Control-Request-Method, Access-Control-Request-Headers, Cache-Control"); // 创建空响应消息,终止后续认证与服务调用流程 Message^ reply = Message::CreateMessage(MessageVersion::None, String::Empty); reply->Properties->Add(HttpResponseMessageProperty::Name, httpResponse); request = reply; } return nullptr; }
2. 优化BeforeSendReply方法(可选)
由于已经在AfterReceiveRequest中处理了OPTIONS的CORS头,可移除原方法中重复的头添加逻辑,避免重复设置:
virtual void BeforeSendReply(System::ServiceModel::Channels::Message^% reply, System::Object^ correlationState) { if (!reply->Properties->ContainsKey("httpResponse")) { HttpResponseMessageProperty ^httpProp = gcnew HttpResponseMessageProperty(); reply->Properties->Add("httpResponse", httpProp); } // 移除原有的CORS头循环添加代码 }
原理说明
- WCF消息处理管道中,
AfterReceiveRequest是认证校验之前的早期阶段,在此拦截OPTIONS请求并直接返回响应,可绕过后续的Basic认证逻辑。 - 构造的空响应会终止后续服务调用流程,确保OPTIONS请求不会触发身份校验。
内容的提问来源于stack exchange,提问作者Simon Callan
相关产品推荐
相关产品推荐

