Windows-classic-samples 深入解析:基于 Windows Web Services API 的 HTTP 服务器 Username-over-SSL 混合安全认证示例

发布时间:2026/10/16 2:29:47
Windows-classic-samples 深入解析:基于 Windows Web Services API 的 HTTP 服务器 Username-over-SSL 混合安全认证示例
示例工程【免费下载链接】Windows-classic-samplesThis 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 APIWWSAPI构建一个采用「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.libWWSAPI 核心库、crypt32.lib加密 API、rpcrt4.lib、Iphlpapi.lib。平台与配置支持 Win32 / ARM / x64 三种平台Debug 与 Release 两种配置工具集为 v110Visual 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(Lusr1); const WS_STRING fixedPassword WS_STRING_VALUE(Lpwd1); 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 与 Listenerhr 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(Lhttps://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打印到控制台构造orderConfirmationexpectedShipDate L1/1/2006、orderID 123以PurchaseOrder_wsdl.messages.OrderConfirmation作为应答描述调用WsSendReplyMessage回发。命中GetOrderStatus若orderID ! 123走 Fault 分支——用WsSetFaultErrorDetail写入_OrderNotFoundFaultType的详情Action 为http://example.com/fault用WsAddErrorString追加LInvalid order ID错误字符串再将hr置为E_FAIL以触发 Fault 发送若orderID 123则以GetOrderStatusResponsestatus LPending正常应答。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.orgWSDL 元素类型结构用途PurchaseOrderTypequantity: xsd:intproductName: xsd:string采购订单请求体OrderConfirmationTypeorderID: xsd:unsignedIntexpectedShipDate: xsd:string订单确认应答体GetOrderStatusTypeorderID: xsd:unsignedInt订单状态查询请求体GetOrderStatusResponseTypeorderID: xsd:unsignedIntstatus: xsd:string订单状态应答体OrderNotFoundFaultTypeorderID: xsd:unsignedInt订单不存在 Fault 详情portType定义了两个操作Order输入PurchaseOrderActionhttp://example.org/purchaseorder输出OrderConfirmationActionhttp://example.org/orderconfirmationOrderStatus输入GetOrderStatusActionhttp://example.org/getorderstatus输出GetOrderStatusResponseActionhttp://example.org/getorderstatusresponse并声明 FaultOrderNotFoundActionhttp://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(Lusr1); static const WS_STRING passWord WS_STRING_VALUE(Lpwd1); 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 说明。构建步骤打开解决方案文件 HttpServerWithUsernameOverSsl.sln。按F7Visual Studio 2013 为 F6或通过菜单 Build → Build Solution 构建。可在 Build 菜单的 Configuration Manager 中切换目标平台Win32 / x64 / ARM与配置Debug / Release。构建产物输出到工程默认的Debug或Release目录实际输出路径为$(SolutionDir)$(Platform)\bin\$(Configuration)\见 vcxproj。运行步骤构建完成后按F5带调试运行或CtrlF5不带调试运行启动也可以在 Debug 菜单中选择对应选项。运行前的准备URL 预留与证书注册服务端程序通过 http.sys 监听 HTTP(S) 端口非提升权限的 Visual Studio 进程默认无权注册监听 URL因此需要先在提升权限管理员的命令提示符中执行 URL 预留命令来源readme.txt 的 Reserving the HTTP URLs 一节netsh http add urlacl urlhttp://:80/example user%USERDOMAIN%\%USERNAME% netsh http add urlacl urlhttps://:443/example user%USERDOMAIN%\%USERNAME%使用提升权限的 PowerShell 时等价写法为netsh http add urlacl urlhttp://:80/example user$env:USERDOMAIN\$env:USERNAME netsh http add urlacl urlhttps://:443/example user$env:USERDOMAIN\$env:USERNAME不再需要时删除预留netsh http delete urlacl urlhttp://:80/example netsh http delete urlacl urlhttps://: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 监听器并绑定安全描述L188WsOpenListener在指定 URI 上开始监听L201WsCreateChannelForListener为监听器创建接受进来的通道L207WsCreateMessageForChannel为通道创建请求/应答两个消息对象L218WsAcceptChannel接受客户端连接L245WsReceiveMessage接收消息并按描述反序列化消息体L265WsSendReplyMessage回发正常应答消息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-samplesThis repo contains samples that demonstrate the API used in Windows classic desktop applications.项目地址https://gitcode.com/gh_mirrors/wi/Windows-classic-samples点击查看免费下载相关推荐XiaoMusic 完整使用教程Docker 部署小爱音箱音乐播放语音点播任意歌曲XiaoMusic 完整使用教程Docker 部署小爱音箱音乐播放语音点播任意歌曲 XiaoMusic 是一个免费开源的音乐服务能让小爱音箱脱离内置平台限示例工程Windows Web Services API基于 SSL 传输安全的 HTTP 计算器服务托管示例详解Windows-classic-samplesWindows Web Services API基于 SSL 传输安全的 HTTP 计算器服务托管示例详解Windows classic samples示例工程Windows-classic-samples 实战使用 Windows Web Services API 构建基于 Kerberos over SSL 的 HTTP 客户端Windows classic samples 实战使用 Windows Web Services API 构建基于 Kerberos over SSL 的示例工程上一篇Duix-Mobile 移动端实时数字人 SDK 上手指南下一篇Astropy 8.1 新特性解析Time 字符串格式化提速、Table 多引擎连接与整数精度保护创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考