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

.NET6中如何将SAML响应转换为SecurityToken对象?

将SOAP响应转换为System.IdentityModel.Tokens.SecurityToken(.NET 6迁移场景)

问题背景

我正把.NET 4.5的遗留系统升级到.NET 6,系统需要和要求SecurityToken的第三方SOAP服务通信,令牌由WS-Trust服务颁发。由于.NET 5+中WSTrustChannelFactory相关功能受限,只能用HttpClient手动构造请求头和请求体。目前已成功调用令牌服务并获取SOAP格式响应,但不知道如何将响应转换为System.IdentityModel.Tokens.SecurityToken。


遗留代码(.NET 4.5)

public static IUSIService OpenWithM2M()
{
    var token = GetStsToken(60);
    var clientSection = (ClientSection) ConfigurationManager.GetSection("system.serviceModel/client");
    var endpointElement = clientSection.Endpoints.OfType<ChannelEndpointElement>()
        .First(endpoint => string.Equals("USIServiceReference.IUSIService", endpoint.Contract, StringComparison.OrdinalIgnoreCase));
    if (endpointElement == null)
    {
        throw new Exception("No endpoint matching service contract was found");
    }

    var channelFactory = new ChannelFactory<IUSIService>(endpointElement.Name);
    channelFactory.Open();
    return channelFactory.CreateChannelWithIssuedToken(token);
}

private static SecurityToken GetStsToken(int tokenLifeTimeMinutes)
{
    var factory = new WSTrustChannelFactory("S007SecurityTokenServiceEndpoint");
    if (factory.Credentials != null)
    {
        factory.Credentials.ClientCertificate.Certificate = GetClientCertificateFromKeystore();
        factory.Credentials.SupportInteractive = false;
    }

    var appliesTo = ConfigurationManager.AppSettings["appliesTo"];
    var rst = new RequestSecurityToken
    {
        Claims =
        {
            new RequestClaim("http://vanguard.ebusiness.gov.au/2008/06/identity/claims/abn", false),
            new RequestClaim("http://vanguard.ebusiness.gov.au/2008/06/identity/claims/credentialtype", false)
        },
        AppliesTo = new EndpointReference(appliesTo),
        Lifetime = new Lifetime(DateTime.UtcNow, DateTime.UtcNow.AddMinutes(tokenLifeTimeMinutes)),
        RequestType = RequestTypes.Issue,
        KeyType = KeyTypes.Symmetric,
        TokenType = "http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV1.1",
    };

    var client = (WSTrustChannel) factory.CreateChannel();
    var response = client.Issue(rst);
    return response;
}

.NET 6新代码(待完善)

public static async Task<IUSIService> OpenWithM2MAsync()
{
    var token = GetStsToken();
    var configuration = new ConfigurationBuilder()
          .AddJsonFile("appsettings.json")
          .Build();

    var serviceProvider = ConfigurationsManager.ConfigureServices(configuration);
    var clientSettings = serviceProvider.GetRequiredService<IOptions<ClientSection>>().Value;

    var endpointElement = clientSettings.Endpoints
        .First(endpoint => string.Equals("USIServiceReference.IUSIService", endpoint.Contract, StringComparison.OrdinalIgnoreCase));

    if (endpointElement == null)
    {
        throw new Exception("No endpoint matching service contract was found");
    }

    var binding = ConfigurationsManager.CreateBinding(endpointElement.Binding, endpointElement.BindingConfiguration);
    var endpointAddress = new EndpointAddress(endpointElement.Address);

    var factory = new ChannelFactory<IUSIService>(binding, endpointAddress);
    factory.Open();
    factory.Endpoint.EndpointBehaviors.Add(new MyIssuedTokenEndpointBehavior(token)); // 需要传入token

    var channel = factory.CreateChannel();
    return channel;
}

public static System.IdentityModel.Tokens.SecurityToken GetStsToken()
{
    HttpClient Client = new HttpClient();
    Uri uri = new Uri("https://softwareauthorisations.acc.ato.gov.au/R3.0/S007v1.3/service.svc");
    var envelope = BuildEnvelope(GetClientCertificateFromKeystore());
    
    using (HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, uri))
    {
        request.Content = new StringContent(envelope, Encoding.UTF8, "application/soap+xml");
        using (HttpResponseMessage response = Client.SendAsync(request).Result)
        {
            string responseContent = response.Content.ReadAsStringAsync().Result;

            XmlDocument xmlDocument = new XmlDocument();
            xmlDocument.LoadXml(responseContent);

            // 此处需要将响应转换为SecurityToken
        }
    }            
    //return token;
}

解决方案

1. 提取SOAP响应中的SAML令牌节点

首先通过XML命名空间和XPath定位到响应中的SAML断言节点,注意匹配实际响应的命名空间:

XmlNamespaceManager nsManager = new XmlNamespaceManager(xmlDocument.NameTable);
nsManager.AddNamespace("s", "http://www.w3.org/2003/05/soap-envelope");
nsManager.AddNamespace("wst", "http://docs.oasis-open.org/ws-sx/ws-trust/200512");
nsManager.AddNamespace("saml", "urn:oasis:names:tc:SAML:1.0:assertion");

XmlNode samlAssertionNode = xmlDocument.SelectSingleNode(
    "/s:Envelope/s:Body/wst:RequestSecurityTokenResponse/wst:RequestedSecurityToken/saml:Assertion",
    nsManager);

if (samlAssertionNode == null)
{
    throw new InvalidOperationException("响应中未找到有效的SAML令牌");
}

2. 将XML节点转换为SecurityToken

使用SecurityTokenHandlerCollection加载SAML令牌处理程序,解析XML节点:

var tokenHandlers = SecurityTokenHandlerCollection.CreateDefault();
tokenHandlers.Add(new SamlSecurityTokenHandler());

using XmlNodeReader reader = new XmlNodeReader(samlAssertionNode);
if (!tokenHandlers.CanReadToken(reader))
{
    throw new InvalidOperationException("无法解析SAML令牌格式");
}

SecurityToken token = tokenHandlers.ReadToken(reader);

3. 完整的GetStsToken异步实现

将上述逻辑整合,同时修复同步调用的死锁问题:

public static async Task<System.IdentityModel.Tokens.SecurityToken> GetStsTokenAsync()
{
    using HttpClient client = new HttpClient();
    Uri uri = new Uri("https://softwareauthorisations.acc.ato.gov.au/R3.0/S007v1.3/service.svc");
    var cert = GetClientCertificateFromKeystore();
    var envelope = BuildEnvelope(cert);
    
    using HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, uri);
    request.Content = new StringContent(envelope, Encoding.UTF8, "application/soap+xml");
    
    using HttpResponseMessage response = await client.SendAsync(request);
    response.EnsureSuccessStatusCode();
    string responseContent = await response.Content.ReadAsStringAsync();

    XmlDocument xmlDocument = new XmlDocument();
    xmlDocument.LoadXml(responseContent);

    XmlNamespaceManager nsManager = new XmlNamespaceManager(xmlDocument.NameTable);
    nsManager.AddNamespace("s", "http://www.w3.org/2003/05/soap-envelope");
    nsManager.AddNamespace("wst", "http://docs.oasis-open.org/ws-sx/ws-trust/200512");
    nsManager.AddNamespace("saml", "urn:oasis:names:tc:SAML:1.0:assertion");

    XmlNode samlAssertionNode = xmlDocument.SelectSingleNode(
        "/s:Envelope/s:Body/wst:RequestSecurityTokenResponse/wst:RequestedSecurityToken/saml:Assertion",
        nsManager);

    if (samlAssertionNode == null)
    {
        throw new InvalidOperationException("响应中未找到有效的SAML令牌");
    }

    var tokenHandlers = SecurityTokenHandlerCollection.CreateDefault();
    tokenHandlers.Add(new SamlSecurityTokenHandler());
    
    using XmlNodeReader reader = new XmlNodeReader(samlAssertionNode);
    if (!tokenHandlers.CanReadToken(reader))
    {
        throw new InvalidOperationException("无法解析SAML令牌格式");
    }

    return tokenHandlers.ReadToken(reader);
}

注意事项

  • 需安装NuGet包:System.IdentityModel.Tokens.Saml、Microsoft.IdentityModel.Protocols
  • 需根据实际SOAP响应调整命名空间和XPath表达式
  • 调用GetStsTokenAsync时需使用await,避免同步阻塞引发死锁

内容的提问来源于stack exchange,提问作者Umair Zafar

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 08:09:53