You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

自托管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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 12:19:58