BasicHttpBinding 与自定义授权





5.00/5 (3投票s)
使用 Wireshark 监控 BasicHttpBinding WCF 调用的网络流量
引言
本文演示了 WCF 客户端调用 WCF 服务时,在两台计算机之间传递的网络流量,以及传递的用户名/密码。我使用了 Wireshark 来监控网络流量。
请注意,这仅用于演示目的。像这样使用 BasicHttpBinding **不**安全。
背景
WCF 拥有海量的配置组合,例如绑定、安全。这些组合的数量可能会让人不知所措。本文展示了使用 BasicHttpBinding 进行简单服务调用时,幕后发生的情况。
使用代码
共有 2 个项目 - 一个用于 WCF 服务,一个用于 WCF 客户端。
我使用交叉网线连接了我的两台 PC。然后,我为每台 PC 设置了 IP 地址。我为托管 WCF 服务的 PC 分配了 IP 地址 192.168.1.10,如下所示。
对于客户端,我分配了 IP 地址 192.168.1.11
WCF 服务
这是 WCF 服务的代码。请注意 CustomValidator 类的使用。我将让 WCF 客户端向 WCF 服务传输用户名和密码,如果密码不等于“Secret”,则身份验证将失败。除此之外,代码是相当标准的。但它指定了 CustomValidator 正在进行身份验证。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
using System.ServiceModel.Description;
using System.ServiceModel.Channels;
using System.IdentityModel.Selectors;
namespace JoesTestWCFService
{
public class CustomValidator : UserNamePasswordValidator
{
public override void Validate(string userName, string password)
{
if (password != "Secret")
throw new FaultException("Bad Password " + password);
return;
}
}
[ServiceContract]
public interface IJoesService
{
[OperationContract]
string TestMethod(string msg);
}
public class JoesService : IJoesService
{
Guid serviceId;
public JoesService()
{
serviceId = Guid.NewGuid();
}
public string TestMethod(string msg)
{
return "Hello " + msg + ": (" + serviceId.ToString() + ")";
}
}
class Program
{
static void Main(string[] args)
{
Uri[] baseAddress = null;
baseAddress = new Uri[] { new Uri("http://192.168.1.10:8082/") };
var binding = new BasicHttpBinding(BasicHttpSecurityMode.TransportCredentialOnly);
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;
ServiceHost host = new ServiceHost(typeof(JoesService), baseAddress);
host.Credentials.UserNameAuthentication.UserNamePasswordValidationMode = System.ServiceModel.Security.UserNamePasswordValidationMode.Custom;
host.Credentials.UserNameAuthentication.CustomUserNamePasswordValidator = new CustomValidator();
host.AddServiceEndpoint(typeof(IJoesService), binding, baseAddress.First());
host.Open();
Console.WriteLine("Ready ...");
Console.ReadLine();
}
}
}
WCF 客户端
这是 WCF 客户端的代码。请注意,我在调用 WCF 服务时传递了用户名/密码。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
using System.ServiceModel.Channels;
namespace JoesTestWCFClient
{
[ServiceContract]
public interface IJoesService
{
[OperationContract]
string TestMethod(string msg);
}
class Program
{
static void Main(string[] args)
{
string uri = null;
EndpointAddress endPA;
uri = "http://192.168.1.10:8082";
var binding = new BasicHttpBinding(BasicHttpSecurityMode.TransportCredentialOnly);
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;
endPA = new EndpointAddress(uri);
ChannelFactory<IJoesService> factory = new ChannelFactory<IJoesService>(binding, endPA);
factory.Credentials.UserName.UserName = "JJJOE";
factory.Credentials.UserName.Password = args[0];
IJoesService proxy = factory.CreateChannel();
string input;
do
{
Console.WriteLine(proxy.TestMethod("JOEJOEJOEJOEJOE"));
input = Console.ReadLine();
} while (input != "x");
}
}
}
网络流量
这是 WCF 客户端向 WCF 服务的一次调用的所有流量。
1) 前 3 行只是建立 TCP 连接。请注意,尽管 HTTP 被视为“无连接”,但在幕后,会在调用期间建立 TCP 连接。
请注意客户端的端口 (49569)。这个端口不是我分配的。目标端口 (8082) 是我分配的。
2) 在下一行中,WCF 客户端对 WCF 服务发出请求,即主机 192.168.1.10 - 端口 8082 上的 IJoesService 的 TestMethod。
4) 下一行 (41) 是 WCF 服务发出的挑战,因为请求未经身份验证。
请参阅 http://www.ietf.org/rfc/rfc2617.txt?number=2617
“基本”身份验证方案基于这样一个模型:客户端必须为每个领域(realm)向服务器提供用户 ID 和密码进行身份验证。领域值应被视为一个不透明的字符串,只能与该服务器上的其他领域进行相等性比较。只有当服务器能够验证请求 URI 保护空间的用户 ID 和密码时,它才会服务该请求。
没有可选的身份验证参数。
对于“基本”(Basic)身份验证,上述框架使用如下:
挑战 (challenge) = "Basic" 领域 (realm)
凭据 (credentials) = "Basic" 基本凭据 (basic-credentials)
在收到属于某个保护空间内的 URI 的未经授权的请求时,源服务器可以返回一个类似以下的挑战:
WWW-Authenticate: Basic realm="WallyWorld"
所以,这是我的 WCF 服务发出的挑战:
WWW-Authenticate: Basic realm="".
5) 在第 42 行中,WCF 客户端通过提供凭据来响应 WCF 服务的挑战。
为了获得授权,客户端会发送用户 ID 和密码,
在一个单独的冒号 (":") 字符分隔下,以 base64 [7] 编码的字符串形式
在凭据中。
basic-credentials = base64-user-pass
base64-user-pass = <base64 [4] 编码的用户-密码,
如果用户代理希望发送用户 ID "Aladdin" 和密码
"open sesame",它将使用以下头字段:
Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
请注意,凭据**不**是加密的——它们是编码的。正如我接下来将展示的,提取密码非常简单。
从 Wireshark 中可以看到,客户端已发送
Authorization: Basic SkpKT0U6U2VjcmV0
您可以使用诸如 http://string-functions.com/base64decode.aspx 之类的网站来解码。
6) 接下来,WCF 服务授予继续的权限。
7) WCF 客户端现在可以调用服务方法了。
8) WCF 服务响应。
关注点
1) 显然,此方法不安全,因为它很容易在拦截到用户名和密码时对其进行解码。
一种更安全的传递用户名和密码的方法是使用更安全的绑定,例如 netTcpBinding。这个设置稍微复杂一些。我使用的一种方法是在 WCF 服务 PC 上设置一个自签名证书。此证书的公钥从 WCF 服务发送到 WCF 客户端,以便客户端可以使用它来加密凭据。
下面是使用 netTcpBinding 时的 Wireshark 流量快照。您可以看到 WCF 服务将证书的公钥传递给客户端(我在流量中圈出了公钥的开头 - 您可以看到它与右侧屏幕中的证书密钥匹配)。
2) 重复调用上述代码的 BasicHttpBinding 版本,默认情况下是 PerCall,即每次调用 TestMethod 时都会调用 JoesService 的新实例。您会在每次方法调用时看到不同的 ServiceId 打印出来。
如果您希望为客户端使用 Per-session,您需要使用其他绑定,例如 netTcpBinding。
以上所有内容仅用于演示目的,希望能帮助揭示 WCF 在 PC 之间进行调用时幕后发生的情况。请进行自己的研究,以确定您在自己的项目中需要的适当安全模型。
历史
v1.0 初始版本