.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
相关产品推荐
相关产品推荐

