☰
Windows-classic-samples 深入解析:基于 Windows Web Services API 的 HTTP 服务器 Username-over-SSL 混合安全认证示例
2026/10/16 2:29:46 网站建设 项目流程
  • 示例工程

【免费下载链接】Windows-classic-samples

This repo contains samples that demonstrate the API used in Windows classic desktop applications.

项目地址:https://gitcode.com/gh_mirrors/wi/Windows-classic-samples
点击查看免费下载

本篇文章以 Windows-classic-samples 仓库中的 HttpServerWithUsernameOverSsl 示例 为核心,系统讲解如何用 Windows Web Services API(WWSAPI)构建一个采用「SSL 传输安全 + 用户名密码消息安全」混合模式(mixed-mode)的 HTTP 服务端:SSL 负责对传输通道进行签名与加密并提供服务器身份认证,而客户端身份则由消息中 WS-Security 头携带的用户名/密码对来验证。读完本文,你将掌握 WWSAPI 服务端的完整编程模型(Listener → Channel → Message)、双安全绑定(WS_SECURITY_DESCRIPTION)的组装方式、密码校验回调的编写、WSDL 消息契约的代码生成流程,以及该示例在 Visual Studio 中的构建、URL 预留与证书注册等实战细节。

示例概览:什么是 Username-over-SSL 混合模式安全

该示例的官方定位在 README.md 中描述为:使用 Windows Web Services API 创建一个「接受通道、接收消息并回复消息」的服务器,并启用 username over SSL mixed-mode security。需要说明的是,虽然 README 原文将服务器描述为 "TCP server",但从 主程序源码 可以确认,它实际使用WS_HTTP_CHANNEL_BINDING创建的是 HTTP 通道,因此本质上是 HTTP 服务器示例。

在该安全模型中,两道防线分工明确:

安全层次承担的安全职责对应 WWSAPI 绑定
传输层(SSL/TLS)对传输连接进行签名与加密,并提供服务器身份认证(防止中间人冒充服务器)WS_SSL_TRANSPORT_SECURITY_BINDING
消息层(WS-Security)在 SOAP 消息的 WS-Security 头中携带用户名/密码对,完成客户端身份认证WS_USERNAME_MESSAGE_SECURITY_BINDING

之所以称为「混合模式」,是因为它将传输级安全与消息级安全组合在同一份安全描述(security description)中:WS_SSL_TRANSPORT_SECURITY_BINDING与WS_USERNAME_MESSAGE_SECURITY_BINDING通过WS_SECURITY_BINDING*数组一并注册到监听器上。这种组合常见于企业级 Web 服务场景:HTTPS 通道保证数据机密性与服务器可信性,消息头中的用户名/密码则允许服务端对调用方做业务层面的身份鉴别,而无需依赖复杂的证书双向认证。

仓库结构与工程文件

该示例的完整目录结构如下:

Samples/WebServices/HttpServerWithUsernameOverSsl/ ├── README.md # 示例说明(本文主体文档) ├── readme.txt # 补充的构建/URL 预留/安全说明 └── cpp/ ├── HttpServerWithUsernameOverSsl.cpp # 服务端主程序 ├── HttpServerWithUsernameOverSsl.sln # Visual Studio 解决方案 ├── HttpServerWithUsernameOverSsl.vcxproj # 工程文件 ├── HttpServerWithUsernameOverSsl.vcxproj.filters └── PurchaseOrder.wsdl # 服务消息契约(WSDL 定义)

从 工程文件 可以看出几个关键工程配置:

  • WSDL 代码生成:PurchaseOrder.wsdl配置了一个自定义构建步骤,命令为Wsutil.exe /wsdl:PurchaseOrder.wsdl /out:.,输出PurchaseOrder.wsdl.c与PurchaseOrder.wsdl.h两个生成文件,分别加入ClCompile与ClInclude项。这是 WWSAPI 的典型流程——先用 Wsutil 工具把 WSDL 编译成 C 头文件与源码,再在业务代码中#include "PurchaseOrder.wsdl.h"使用生成的消息描述符。
  • 链接依赖库:webservices.lib(WWSAPI 核心库)、crypt32.lib(加密 API)、rpcrt4.lib、Iphlpapi.lib。
  • 平台与配置:支持 Win32 / ARM / x64 三种平台,Debug 与 Release 两种配置,工具集为 v110(Visual Studio 2012 对应版本),字符集为 Unicode,子系统为控制台(Console)应用。

核心安全配置:双绑定安全描述的组装

安全配置集中在 HttpServerWithUsernameOverSsl.cpp 的wmain()函数入口处,共分为三步。

1. 用户名消息安全绑定(客户端认证)

// declare and initialize a username message security binding WS_USERNAME_MESSAGE_SECURITY_BINDING usernameBinding = {}; // zero out the struct usernameBinding.binding.bindingType = WS_USERNAME_MESSAGE_SECURITY_BINDING_TYPE; // set the binding type usernameBinding.bindingUsage = WS_SUPPORTING_MESSAGE_SECURITY_USAGE; // set the binding usage usernameBinding.passwordValidator = MyPasswordValidator;

要点说明:

  • bindingType必须设置为WS_USERNAME_MESSAGE_SECURITY_BINDING_TYPE,标识这是一个基于用户名/密码的消息安全绑定。
  • bindingUsage = WS_SUPPORTING_MESSAGE_SECURITY_USAGE表示该绑定是「支撑型」消息安全:它不单独建立安全通道,而是作为 SSL 传输安全之上的附加认证层,把凭证放进消息的 WS-Security 头。
  • passwordValidator指向自定义的密码校验回调MyPasswordValidator,服务端在此回调中完成对收到的用户名/密码对的验证。

2. SSL 传输安全绑定(传输保护与服务器认证)

// declare and initialize an SSL transport security binding WS_SSL_TRANSPORT_SECURITY_BINDING sslBinding = {}; // zero out the struct sslBinding.binding.bindingType = WS_SSL_TRANSPORT_SECURITY_BINDING_TYPE; // set the binding type // NOTE: At the server, the SSL certificate for the listen URI must be // registered with http.sys using a tool such as httpcfg.exe.

源码注释特别强调了一个运维前提:服务端监听 URI 对应的 SSL 证书必须先注册到 http.sys。httpcfg.exe 是早期工具,现代 Windows 上等价操作为netsh http add sslcert(见下文「运行前的准备」一节)。

3. 安全描述(Security Description)的组装

// declare and initialize the array of all security bindings WS_SECURITY_BINDING* securityBindings[2] = { &sslBinding.binding, &usernameBinding.binding }; // declare and initialize the security description WS_SECURITY_DESCRIPTION securityDescription = {}; // zero out the struct securityDescription.securityBindings = securityBindings; securityDescription.securityBindingCount = WsCountOf(securityBindings);

最终,包含两个绑定的securityDescription在创建监听器时作为参数传入WsCreateListener,从而把「HTTPS 传输加密 + WS-Security 用户名认证」组合应用到整个监听器生命周期内。

密码校验回调:MyPasswordValidator 的实现

服务端的客户端认证并非由 WWSAPI 自行完成,而是通过回调把凭证交给应用层验证。MyPasswordValidator 展示了完整的回调签名与硬编码校验逻辑:

static HRESULT CALLBACK MyPasswordValidator( _In_ void* callbackState, _In_ const WS_STRING* username, _In_ const WS_STRING* password, _In_ const WS_ASYNC_CONTEXT* asyncContext, _In_opt_ WS_ERROR* error) { UNREFERENCED_PARAMETER(callbackState); UNREFERENCED_PARAMETER(asyncContext); UNREFERENCED_PARAMETER(error); const WS_STRING fixedUsername = WS_STRING_VALUE(L"usr1"); const WS_STRING fixedPassword = WS_STRING_VALUE(L"pwd1"); if (CompareWsString(username, &fixedUsername) && CompareWsString(password, &fixedPassword)) { return S_OK; } return S_FALSE; }

行为要点:

  • 回调返回S_OK表示凭证通过验证;返回S_FALSE表示验证失败,通道握手将被拒绝。
  • 示例中凭证硬编码为usr1/pwd1,仅供演示。生产环境中应将callbackState指向真实的账号数据库或认证服务,避免硬编码。
  • 比较操作使用自定义的 CompareWsString:它基于CompareStringW(LOCALE_INVARIANT, ...)按不区分大小写(0为区分大小写标志位)逐字符比较WS_STRING,并兼容非 NULL 结尾的字符串(长度由WS_STRING.length显式给出,这也是WS_STRING与普通 C 字符串的重要区别)。

服务端主流程:Listener → Channel → Message 全链路

主程序在wmain()中完整演示了 WWSAPI 服务端编程模型的每个阶段。

初始化阶段:Error、Heap 与 Listener

hr = WsCreateError(NULL, 0, &error); // 错误对象,用于收集富错误信息 hr = WsCreateHeap(/*maxSize*/ 2048, /*trimSize*/ 512, NULL, 0, &heap, error); hr = WsCreateListener( WS_CHANNEL_TYPE_REPLY, // 应答型通道 WS_HTTP_CHANNEL_BINDING, // HTTP 传输绑定 NULL, 0, &securityDescription, // 双安全绑定描述 &listener, error); hr = WsOpenListener(listener, &uri, NULL, error); // 监听 https://localhost:8443/example
  • 监听 URI 定义为WS_STRING_VALUE(L"https://localhost:8443/example"),协议必须是https才能匹配 SSL 传输安全绑定。
  • WS_CHANNEL_TYPE_REPLY表明这是服务端应答型通道;客户端对应的是WS_CHANNEL_TYPE_REQUEST。

随后通过WsCreateChannelForListener为监听器创建通道,再用WsCreateMessageForChannel分别创建requestMessage与replyMessage两个消息对象。

请求处理循环:Accept → Receive → Dispatch → Reply

主循环固定运行300 次(for (int i = 0; i < 300; i++)),每次迭代的完整流程如下:

// 1. 接受客户端连接 hr = WsAcceptChannel(listener, channel, NULL, error); // 2. 声明期望收到的消息描述:采购订单 或 订单状态查询 const WS_MESSAGE_DESCRIPTION* requestMessageDescriptions[] = { &PurchaseOrder_wsdl.messages.PurchaseOrder, // 消息体含 _PurchaseOrderType &PurchaseOrder_wsdl.messages.GetOrderStatus, // 消息体含 GetOrderStatus }; // 3. 接收消息并按描述反序列化消息体 hr = WsReceiveMessage(channel, requestMessage, requestMessageDescriptions, WsCountOf(requestMessageDescriptions), WS_RECEIVE_REQUIRED_MESSAGE, WS_READ_REQUIRED_POINTER, heap, &requestBodyPointer, sizeof(requestBodyPointer), &indexOfMatchedMessageDescription, NULL, error);

WsReceiveMessage会把收到的 SOAP 消息体反序列化到由WS_HEAP分配的内存中,并通过indexOfMatchedMessageDescription返回命中的消息描述索引,从而在代码里区分两种请求。

分发与应答逻辑:

  • 命中PurchaseOrder:将_PurchaseOrderType的quantity、productName打印到控制台,构造orderConfirmation(expectedShipDate = L"1/1/2006"、orderID = 123),以PurchaseOrder_wsdl.messages.OrderConfirmation作为应答描述调用WsSendReplyMessage回发。
  • 命中GetOrderStatus:若orderID != 123,走 Fault 分支——用WsSetFaultErrorDetail写入_OrderNotFoundFaultType的详情(Action 为http://example.com/fault),用WsAddErrorString追加L"Invalid order ID"错误字符串,再将hr置为E_FAIL以触发 Fault 发送;若orderID == 123,则以GetOrderStatusResponse(status = L"Pending")正常应答。

Fault 与正常应答的分支处理:

if (FAILED(hr)) { // 把错误对象中累积的信息序列化为 Fault 消息体发送 hr = WsSendFaultMessageForError(channel, replyMessage, error, hr, WS_FULL_FAULT_DISCLOSURE, requestMessage, NULL, error); hr = WsResetError(error); // 重置错误对象以供复用 hr = WsResetMessage(replyMessage, error); } else { hr = WsSendReplyMessage(channel, replyMessage, replyMessageDescription, WS_WRITE_REQUIRED_VALUE, replyBodyPointer, replyBodySize, requestMessage, NULL, error); hr = WsResetMessage(replyMessage, error); } // 迭代收尾:重置请求消息、重置堆、关闭并重置通道 hr = WsResetMessage(requestMessage, error); hr = WsResetHeap(heap, error); // 释放堆上的反序列化数据 hr = WsCloseChannel(channel, NULL, error); hr = WsResetChannel(channel, error); // 通道复位后可在下一轮迭代复用

这里的资源管理手法值得借鉴:消息对象(WsResetMessage)、堆(WsResetHeap)与通道(WsResetChannel)都通过「重置」而非「重建」的方式复用,避免高频创建/销毁对象的开销;WsCloseChannel与WsResetChannel组合则实现了通道的一次性会话语义。

退出清理

Exit标签处依次执行WsCloseChannel/WsFreeChannel、WsFreeMessage×2、WsCloseListener/WsFreeListener、WsFreeError、WsFreeHeap,并在失败时通过 PrintError 打印富错误信息(利用WsGetErrorProperty读取错误字符串计数、WsGetErrorString逐条输出)。

消息契约:PurchaseOrder.wsdl 与 Wsutil 代码生成

PurchaseOrder.wsdl 定义了本示例的 SOAP 服务契约,命名空间为http://example.org:

WSDL 元素类型结构用途
PurchaseOrderTypequantity: xsd:int+productName: xsd:string采购订单请求体
OrderConfirmationTypeorderID: xsd:unsignedInt+expectedShipDate: xsd:string订单确认应答体
GetOrderStatusTypeorderID: xsd:unsignedInt订单状态查询请求体
GetOrderStatusResponseTypeorderID: xsd:unsignedInt+status: xsd:string订单状态应答体
OrderNotFoundFaultTypeorderID: xsd:unsignedInt订单不存在 Fault 详情

portType定义了两个操作:

  • Order:输入PurchaseOrder(Actionhttp://example.org/purchaseorder),输出OrderConfirmation(Actionhttp://example.org/orderconfirmation);
  • OrderStatus:输入GetOrderStatus(Actionhttp://example.org/getorderstatus),输出GetOrderStatusResponse(Actionhttp://example.org/getorderstatusresponse),并声明 FaultOrderNotFound(Actionhttp://example.org/ordernotfound)。

主程序直接引用PurchaseOrder_wsdl.messages.PurchaseOrder、PurchaseOrder_wsdl.messages.GetOrderStatus等生成符号,因此每次修改 WSDL 后都必须重新执行Wsutil.exe /wsdl:PurchaseOrder.wsdl /out:.重新生成代码(在 Visual Studio 中构建时由自定义构建步骤自动完成)。

配套客户端:如何与服务端对接

仓库中有一个与之配套的客户端示例 HttpClientWithUsernameOverSsl,运行在https://localhost:8443/example上,与服务端构成完整的请求-应答闭环。其安全配置与服务端严格对称:

// 客户端凭证(与服务端硬编码校验的 usr1/pwd1 一致) WS_STRING_USERNAME_CREDENTIAL usernameCredential = {}; static const WS_STRING userName = WS_STRING_VALUE(L"usr1"); static const WS_STRING passWord = WS_STRING_VALUE(L"pwd1"); usernameCredential.credential.credentialType = WS_STRING_USERNAME_CREDENTIAL_TYPE; usernameCredential.username = userName; usernameCredential.password = passWord; // 用户名消息安全绑定:把 clientCredential 挂到绑定上 WS_USERNAME_MESSAGE_SECURITY_BINDING usernameBinding = {}; usernameBinding.binding.bindingType = WS_USERNAME_MESSAGE_SECURITY_BINDING_TYPE; usernameBinding.bindingUsage = WS_SUPPORTING_MESSAGE_SECURITY_USAGE; usernameBinding.clientCredential = &usernameCredential.credential; // SSL 传输绑定 + 安全描述组装,顺序同为 {ssl, username} WS_SSL_TRANSPORT_SECURITY_BINDING sslBinding = {}; sslBinding.binding.bindingType = WS_SSL_TRANSPORT_SECURITY_BINDING_TYPE; WS_SECURITY_BINDING* securityBindings[2] = { &sslBinding.binding, &usernameBinding.binding };

客户端通过WsCreateChannel(WS_CHANNEL_TYPE_REQUEST, WS_HTTP_CHANNEL_BINDING, ...)、WsOpenChannel建立 HTTPS 连接,再用WsRequestReply完成「发送采购订单 → 读取订单确认」「发送状态查询 → 读取状态应答」两轮交互;当请求orderID = 321(服务端不认识的订单号)时,客户端收到WS_E_ENDPOINT_FAULT_RECEIVED,随后用WsGetFaultErrorDetail反序列化 Fault 详情并打印Order 321 was not found。把两个示例对照阅读,即可完整掌握 WWSAPI 在「用户名 + SSL 混合安全」下客户端与服务端的全部握手与通信细节。

构建与运行

系统要求

按 README.md 的说明:

  • 客户端操作系统:Windows 7
  • 服务端操作系统:Windows Server 2008 R2
  • 示例创建时面向 Windows 8.1 / Windows Server 2012 R2,使用 Visual Studio 2013 开发,但多数情况下可直接在更高版本的 Visual Studio 与 Windows 上无改动运行。
  • 若要在更早期的系统(Windows XP、Windows Vista、Windows Server 2003/2008)上运行,可能需要安装包含 Windows Web Services API 运行时 DLL 的 Windows Update(详见 readme.txt 的 Prerequisites 说明)。

构建步骤

  1. 打开解决方案文件 HttpServerWithUsernameOverSsl.sln。
  2. 按F7(Visual Studio 2013 为 F6),或通过菜单 Build → Build Solution 构建。
  3. 可在 Build 菜单的 Configuration Manager 中切换目标平台(Win32 / x64 / ARM)与配置(Debug / Release)。
  4. 构建产物输出到工程默认的Debug或Release目录(实际输出路径为$(SolutionDir)$(Platform)\bin\$(Configuration)\,见 vcxproj)。

运行步骤

构建完成后按F5(带调试运行)或Ctrl+F5(不带调试运行)启动;也可以在 Debug 菜单中选择对应选项。

运行前的准备:URL 预留与证书注册

服务端程序通过 http.sys 监听 HTTP(S) 端口,非提升权限的 Visual Studio 进程默认无权注册监听 URL,因此需要先在提升权限(管理员)的命令提示符中执行 URL 预留命令(来源:readme.txt 的 Reserving the HTTP URLs 一节):

netsh http add urlacl url=http://+:80/example user=%USERDOMAIN%\%USERNAME% netsh http add urlacl url=https://+:443/example user=%USERDOMAIN%\%USERNAME%

使用提升权限的 PowerShell 时等价写法为:

netsh http add urlacl url=http://+:80/example user=$env:USERDOMAIN\$env:USERNAME netsh http add urlacl url=https://+:443/example user=$env:USERDOMAIN\$env:USERNAME

不再需要时,删除预留:

netsh http delete urlacl url=http://+:80/example netsh http delete urlacl url=https://+:443/example

另外,根据源码注释与 readme.txt 的安全提示,https://localhost:8443/example的监听 URI 必须先用httpcfg.exe或现代等价命令netsh http add sslcert将 SSL 证书注册到 http.sys,否则 SSL 传输绑定无法生效。

安全注意事项

readme.txt 明确声明:本示例仅用于教学目的,演示 WWSAPI 的用法,未在生产环境测试,未经修改不得直接部署到生产环境。结合源码可以归纳出两个必须改造的点:一是密码校验回调中硬编码的usr1/pwd1应替换为真实认证后端;二是WS_FULL_FAULT_DISCLOSURE会向客户端完整披露 Fault 详情,生产环境应评估是否改用受限的 Fault 披露策略。

关键 API 参考清单

原 README 的 Related topics 中列出的 API 均在本示例源码中有实际调用,整理如下(含调用位置):

API在本示例中的作用
WsCreateListener创建应答型 HTTP 监听器并绑定安全描述(L188)
WsOpenListener在指定 URI 上开始监听(L201)
WsCreateChannelForListener为监听器创建接受进来的通道(L207)
WsCreateMessageForChannel为通道创建请求/应答两个消息对象(L218)
WsAcceptChannel接受客户端连接(L245)
WsReceiveMessage接收消息并按描述反序列化消息体(L265)
WsSendReplyMessage回发正常应答消息(L408)

相关的安全与支撑结构还包括WS_SECURITY_DESCRIPTION、WS_USERNAME_MESSAGE_SECURITY_BINDING、WS_SSL_TRANSPORT_SECURITY_BINDING、WS_SECURITY_BINDING、WS_ERROR(富错误信息)与WS_HEAP(反序列化数据堆)等,它们共同构成了本示例所演示的 Windows Web Services API 核心技术栈。

  • 示例工程

【免费下载链接】Windows-classic-samples

This repo contains samples that demonstrate the API used in Windows classic desktop applications.

项目地址:https://gitcode.com/gh_mirrors/wi/Windows-classic-samples
点击查看免费下载
上一篇:Duix-Mobile 移动端实时数字人 SDK 上手指南
下一篇:Astropy 8.1 新特性解析:Time 字符串格式化提速、Table 多引擎连接与整数精度保护

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询