65.9K
CodeProject 正在变化。 阅读更多。
Home

在 Microsoft IssueVision 中实现 WS-SecureConversation

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.61/5 (11投票s)

2005年6月24日

6分钟阅读

viewsIcon

75427

downloadIcon

776

使用 WSE 2.0 为 Microsoft IssueVision 示例应用程序添加安全通信。

引言

Microsoft IssueVision 是一个演示构建智能客户端应用程序最佳实践的开发人员示例应用程序。它专门为 DevDays 2004 智能客户端轨道开发,并提供了一个示例帮助台管理应用程序。原始源代码可在此处 下载

在本文中,我将介绍使用 WSE 2.0 在 Microsoft IssueVision 示例应用程序中实现 WS-SecureConversation 的过程。

WS-SecureConversation 规范允许客户端和 Web 服务在会话期间建立基于令牌的安全会话。它类似于安全套接字层 (SSL) 协议,该协议通过 HTTP 传输通道提供按需安全通信。安全会话基于由安全令牌提供商获取的安全令牌。此过程涉及一些初始开销,但一旦建立了通道,客户端和服务将交换轻量级的签名安全上下文令牌,与使用常规安全令牌相比,这优化了消息传递时间。安全上下文令牌具有与 UsernameToken 或 X509SecurityToken 等其他安全令牌相同的签名和加密功能。

系统要求

  • Microsoft® Windows 2000、Microsoft® Windows XP Professional 或 Microsoft® Windows Server™ 2003
  • Microsoft® Internet Information Services (IIS) 5.1 或 6.0
  • Microsoft® Visual Studio .NET 2003
  • Microsoft® .NET Framework 1.1
  • Microsoft® SQL Server 2000(需要启用 SQL Server 身份验证)
  • Microsoft® WSE 2.0 SP3

安装

将源代码 zip 文件内容复制到本地磁盘的某个位置后,在编译和运行应用程序之前,我们必须完成以下设置步骤。

1. 安装服务器证书

此应用程序使用 WSE 2.0 中包含的示例证书。请按照以下步骤安装服务器证书。(注意:您不应在生产环境中使用这些示例证书。而是联系证书颁发机构,并申请您自己的证书。)

  • 开始,按运行,键入mmc,然后单击确定打开 MMC 控制台。
  • 文件菜单上,单击添加/删除管理单元
  • 管理单元下,单击添加,然后双击证书
  • 单击我的用户帐户为当前用户添加证书。单击完成
  • 管理单元下,单击添加,然后双击证书
  • 单击计算机帐户以获取本地计算机的证书。单击完成
  • 关闭对话框。
  • 在控制台树中,展开证书 (本地计算机)\个人,然后单击证书
  • 通过选择操作 | 所有任务并选择导入来打开证书导入向导
  • 按照向导操作。当被询问要导入的文件时,请指定:C:\Program Files\Microsoft WSE\v2.0\Samples\Sample Test Certificates\Server Private.pfx。当被询问私钥密码时,请指定:wse2qs。完成向导。
  • 现在您的 MMC 窗口应类似于此

server certificate

    注意:此证书将用于加密应用程序之间的消息。客户端应用程序将使用公钥加密消息,服务将使用私钥解密消息。客户端需要在当前用户存储中包含证书的公钥部分。

  • 在控制台树中,展开证书 - 当前用户\其他用户,然后单击证书

    注意:如果您在当前用户下没有其他用户存储,请打开 Internet Explorer,选择工具Internet 选项内容,然后按证书按钮。您应该在证书对话框中看到其他用户选项卡。您可以通过此界面在此处导入证书,或者可以返回 MMC 并刷新当前用户树,此时其他用户应该会显示出来。

  • 通过选择操作 | 所有任务并选择导入来打开证书导入向导
  • 按照向导操作。当被询问要导入的文件时,请指定:C:\Program Files\Microsoft WSE\v2.0\Samples\Sample Test Certificates\Server Public.cer。完成向导。
  • 现在您的 MMC 窗口应类似于此

client certificate

  • 在 Windows 2000 计算机上,文件 Server Public.cer 应安装在证书 - 当前用户\个人下。现在您的 MMC 窗口应类似于此

client certificate

    注意:此证书仅包含 Server Private.pfx 的公钥部分。客户端将使用它来加密消息,服务器将使用安装在本地计算机存储中的私钥来解密消息。

  • 启动 WSE 2.0 X.509 证书工具。将证书位置更改为本地计算机,将存储名称更改为个人,然后按打开证书按钮。选择WSE2QuickStartServer证书,然后按确定。然后,按查看私钥文件属性…按钮。导航到安全选项卡,并使用添加…按钮为本地计算机的 **ASPNET** 帐户授予对私钥的读取权限。按确定关闭对话框。

2. 安装 IssueVision 示例数据库

要安装示例数据库,请运行源代码 zip 文件中包含的 DataReset.cmd。IssueVision XML Web Service 要求在 SQL Server 2000 服务器上启用 SQL Server 身份验证(混合模式)。请按照以下步骤进行验证

  • 单击开始程序Microsoft SQL Server,然后单击SQL 企业管理器以从 Microsoft SQL Server 程序组运行 SQL 企业管理器。
  • 选择要操作的服务器,然后在工具菜单中,选择SQL Server 配置属性,然后选择安全性页面。
  • 您的 SQL Server 属性窗口应类似于此

SQL Server mixed mode

3. 创建所需的虚拟目录

最后,运行源代码 zip 文件中包含的 CreateSampleVdir.vbs 脚本以自动创建所需的虚拟目录。(您以后可以使用 DeleteSampleVdir.vbs 脚本卸载虚拟目录。)

代码描述

安全会话由需要与 Web 服务进行按需安全通信会话的客户端发起,它包含以下三个步骤

1. 客户端向安全令牌服务提供商发出签名的请求,以获取安全上下文令牌

客户端通过向安全令牌服务 (STS) 提供商发出签名的请求来请求安全上下文令牌,从而启动安全会话。客户端使用 UsernameToken 对安全令牌请求进行签名,并使用 X509SecurityToken 加密 SOAP 消息发送者的熵值。如果请求成功,客户端会将安全上下文令牌缓存起来以供进一步通信。以下函数 RequestSCTByUsername() 实现了这一点

public static SecurityContextToken RequestSCTByUsername(String username, 
                                                        String password)
{
    SecurityContextToken sct = null;

    // Request a new security context token
    // if one was not available from m_sctData
    if (m_sctData.m_username == string.Empty || 
        m_sctData.m_password == string.Empty || 
        m_sctData.m_username != username || m_sctData.m_password != 
        password || m_sctData.m_sct.IsExpired)
    {
        // Create a UsernameToken to use as the base
        // for the security context token
        SecurityToken token = new UsernameToken(username, 
                              password, PasswordOption.SendPlainText);

        // Retrieve the server certificate
        SecurityToken issuerToken = GetServerToken(true);

        // Create a SecurityContextTokenServiceClient
        // (STSClient) that will get the SecurityContextToken
        String secureConvEndpoint = ConfigurationSettings.AppSettings["tokenIssuer"];
        SecurityContextTokenServiceClient STSClient = new 
          SecurityContextTokenServiceClient(new Uri(secureConvEndpoint));

        // Request the security context token,
        // use the client's signing token as the base
        sct = STSClient.IssueSecurityContextTokenAuthenticated(token, issuerToken);

        // Cache the security context token in m_sctData
        m_sctData = new SctData(username, password, sct);
    }

    return m_sctData.m_sct;
}

2. 安全令牌服务提供商验证请求,并将安全上下文令牌发放回客户端

在服务器端,为了验证使用 UsernameToken 创建的传入 SOAP 消息的数字签名,我们重写了 UsernameTokenManager 类的 AuthenticateToken 方法。用户名和密码通过检查用户是否存在于数据库中以及比较存储在 IssueVision 数据库中的密码哈希值来验证。

[SecurityPermissionAttribute(SecurityAction.Demand, 
           Flags=SecurityPermissionFlag.UnmanagedCode)]
public class CustomUsernameTokenManager : UsernameTokenManager
{
    protected override String AuthenticateToken( UsernameToken token )
    {
        // This method returns the password for the provided username
        // WSE will make the determination if they match
        DataSet dataSet = new DataSet();
        string dbPasswordHash;

        try 
        {
            SqlConnection conn = new SqlConnection(Common.ConnectionString);
            SqlCommand cmd = new SqlCommand("GetUser", conn);
            cmd.Parameters.Add("@UserName", token.Username);
            cmd.CommandType = CommandType.StoredProcedure;
            SqlDataAdapter da = new SqlDataAdapter(cmd);
            da.Fill(dataSet);
        } 
        catch (Exception ex) 
        {
            EventLogHelper.LogFailureAudit(string.Format("The GetUser" + 
                " stored procedure encounted a problem: {0}", 
                ex.ToString()));
            throw new SoapException(string.Empty, 
                      SoapException.ServerFaultCode, "Database");
        }

        // does the user exist?
        if (dataSet.Tables[0].Rows.Count == 0) 
        {
            EventLogHelper.LogFailureAudit(string.Format("The username" + 
                                " {0} does not exist.", token.Username));
            throw new SoapException(string.Empty, 
                      SoapException.ClientFaultCode, "Security");
        } 
        else 
        {
            // we found the user, verify the password
            // hash by compare the Salt + PasswordHash
            DataRow dataRow = dataSet.Tables[0].Rows[0];
            dbPasswordHash = (string)dataRow["PasswordHash"];
            string dbPasswordSalt = (string)dataRow["PasswordSalt"];
            // create a hash based on the user's salt and the input password
            string passwordHash = HashString(dbPasswordSalt + token.Password);

            // does the computed hash match the database hash?
            if (string.Compare(dbPasswordHash, passwordHash) != 0)
            {
                EventLogHelper.LogFailureAudit(string.Format("The password" + 
                    " for the username {0} was incorrect.", 
                    token.Username));
                throw new SoapException(string.Empty, 
                          SoapException.ClientFaultCode, "Security");
            }
            else
            {
                return token.Password;
            }
        }
    }

    // generates a hash of the input plain text
    private static string HashString(string textToHash) 
    {
        SHA1CryptoServiceProvider SHA1 = new SHA1CryptoServiceProvider();
        byte[] byteValue = System.Text.Encoding.UTF8.GetBytes(textToHash);
        byte[] byteHash = SHA1.ComputeHash(byteValue);
        SHA1.Clear();

        return Convert.ToBase64String(byteHash);
    }
}

3. 客户端和 Web 服务使用安全上下文令牌进行安全通信

从客户端的角度来看,每次调用 Web 服务都始于请求从上述步骤中缓存的安全上下文令牌。然后,我们使用安全上下文令牌对 SOAP 请求消息进行签名和加密。

public static IVDataSet SendReceiveIssues(DataSet changedIssues, 
                                          DateTime lastAccessed)
{
    IVDataSet data = null;
    IssueVisionServicesWse dataService = GetWebServiceReference();

    try
    {
        // Request the security context token
        SecurityContextToken sct = 
          Wse2HelperClient.RequestSCTByUsername(UserSettings.Instance.Username,
          UserSettings.Instance.Password);

        // Use the security context token to sign
        // and encrypt a request to the Web service
        SoapContext requestContext = dataService.RequestSoapContext;
        requestContext.Security.Tokens.Add(sct);
        requestContext.Security.Elements.Add( new MessageSignature( sct ) );
        requestContext.Security.Elements.Add( new EncryptedData( sct ) );

        data = dataService.SendReceiveIssues(changedIssues, lastAccessed);
    }
    catch (WebException)
    {
        HandleWebServicesException(WebServicesExceptionType.WebException);
    }
    catch (SoapException soapEx)
    {
        if (soapEx.Actor == "Security")
        {
            HandleWebServicesException(WebServicesExceptionType.SoapException);
        }
        else
        {
            HandleWebServicesException(WebServicesExceptionType.WebException);
        }
    }
    catch (Exception)
    {
        HandleWebServicesException(WebServicesExceptionType.Exception);
    }
            
    return data;
}

在服务器端,每个传入请求首先会被验证是否已签名和加密。此外,我们还会检查它是否由安全上下文令牌签名。当所有这些条件都满足时,我们将使用相同的安全上下文令牌对响应 SOAP 消息进行签名和加密。

[WebMethod(Description="Synchronize data by" + 
    " send and recieving from the remote client.")]
public IVDataSet SendReceiveIssues(DataSet changedIssues, DateTime lastAccessed)
{
    SoapContext requestContext = RequestSoapContext.Current;

    // Reject any requests which are not valid SOAP requests
    Wse2HelperServer.VerifyMessageParts(requestContext);
    Wse2HelperServer.VerifyMessageSignature(requestContext);
    Wse2HelperServer.VerifyMessageEncryption(requestContext);

    // Check if the Soap Message is Signed with an SCT.
    SecurityContextToken sct = 
            Wse2HelperServer.GetSigningToken(requestContext) 
            as SecurityContextToken;
    if (sct == null)
    {
        throw new SoapException("The request is not signed with an SCT.", 
                              SoapException.ServerFaultCode, "Security");
    }

    // Use the SCT to sign and encrypt the response
    SoapContext responseContext = ResponseSoapContext.Current;
    responseContext.Security.Tokens.Add(sct);
    responseContext.Security.Elements.Add(new MessageSignature(sct));
    responseContext.Security.Elements.Add(new EncryptedData(sct));

    return new IVData().SendReceiveIssues(changedIssues, lastAccessed);
}

要监视客户端或 Web 服务器端之间实际传输的已加密和签名 SOAP 消息,我们可以检查输入和输出消息跟踪文件 InputTrace.webinfoOutputTrace.webinfo

进一步工作

  • 为了更高的安全性,最好在 HashString() 函数中使用 SHA-256、SHA-384 或 SHA-512 而不是 SHA1 来计算密码哈希值。这还需要更改存储在 IssueVision 示例数据库中的密码哈希值。有关更多信息,请参阅此链接

历史

  • 2005 年 6 月 23 日 - 初始发布。
  • 2005 年 9 月 12 日 - 添加了对 Windows 2000 的支持。
© . All rights reserved.