.NET Framework 4.8下如何自定义RequestSecurityToken的Issuer格式?
我用以下C#代码创建RequestSecurityToken:
var request = new RequestSecurityToken { TokenType = "some:urn", AppliesTo = new EndpointReference($"some:other:urn"), Issuer = new EndpointReference("https://some.domain.net"), RequestType = RequestTypes.Issue };
生成的XML中Issuer标签结构如下:
<trust:Issuer> <wsa:EndpointReference xmlns:wsa="http://www.w3.org/2005/08/addressing"> <wsa:Address>https://some.domain.net/</wsa:Address> </wsa:EndpointReference> </trust:Issuer>
但目标SecurityTokenService(STS)期望Issuer标签仅包含wsa:Address标签(无需EndpointReference包装):
<trust:Issuer> <wsa:Address xmlns:wsa="http://www.w3.org/2005/08/addressing"> https://some.domain.net/ </wsa:Address> </trust:Issuer>
我已经实现了IClientMessageInspector用于添加请求头,但不想修改请求体内容;尝试过重写自定义类的Issuer属性,但序列化消息失败。请问除了修改发送前的XML外,还有其他解决方法吗?应用基于.NET Framework 4.8。
1. 自定义RequestSecurityToken派生类+自定义DataContract序列化
默认RequestSecurityToken的Issuer属性类型是EndpointReference,序列化时会自动套上<wsa:EndpointReference>节点。可以创建派生类,替换Issuer的序列化逻辑,直接输出wsa:Address:
[DataContract(Name = "RequestSecurityToken", Namespace = "http://docs.oasis-open.org/ws-sx/ws-trust/200512")] public class CustomRequestSecurityToken : RequestSecurityToken { // 隐藏基类Issuer属性,自定义序列化逻辑 [DataMember(Name = "Issuer")] public new EndpointAddress IssuerAddress { get; set; } // 保留其他属性的原有序列化逻辑 [DataMember(Name = "TokenType")] public new string TokenType { get => base.TokenType; set => base.TokenType = value; } [DataMember(Name = "AppliesTo")] public new EndpointReference AppliesTo { get => base.AppliesTo; set => base.AppliesTo = value; } [DataMember(Name = "RequestType")] public new string RequestType { get => base.RequestType; set => base.RequestType = value; } }
创建请求时使用该自定义类,直接将IssuerAddress赋值为EndpointAddress:
var request = new CustomRequestSecurityToken { TokenType = "some:urn", AppliesTo = new EndpointReference($"some:other:urn"), IssuerAddress = new EndpointAddress("https://some.domain.net"), RequestType = RequestTypes.Issue };
此方式序列化后,Issuer节点会直接输出wsa:Address,不会被EndpointReference包裹。
2. 使用IContractResolver修改序列化逻辑
若不想创建派生类,可通过自定义IContractResolver拦截RequestSecurityToken的Issuer属性序列化,替换为直接输出Address节点:
public class IssuerContractResolver : DataContractResolver { private readonly DataContractResolver _defaultResolver; public IssuerContractResolver(DataContractResolver defaultResolver) { _defaultResolver = defaultResolver; } public override bool TryResolveType(Type type, Type declaredType, DataContractResolver knownTypeResolver, out XmlDictionaryString typeName, out XmlDictionaryString typeNamespace) { return _defaultResolver.TryResolveType(type, declaredType, knownTypeResolver, out typeName, out typeNamespace); } public override Type ResolveName(string typeName, string typeNamespace, Type declaredType, DataContractResolver knownTypeResolver) { return _defaultResolver.ResolveName(typeName, typeNamespace, declaredType, knownTypeResolver); } public override object ResolveContract(object obj) { if (obj is EndpointReference epRef) { // 将EndpointReference替换为EndpointAddress,让序列化直接输出Address节点 return epRef.Address; } return _defaultResolver.ResolveContract(obj); } }
创建DataContractSerializer时传入该解析器:
var serializer = new DataContractSerializer( typeof(RequestSecurityToken), null, int.MaxValue, false, true, null, new IssuerContractResolver(DataContractSerializer.DefaultDataContractResolver) ); // 使用该序列化器处理请求消息
3. 自定义ClientMessageFormatter控制消息序列化
实现IClientMessageFormatter,在序列化阶段直接调整Issuer节点结构,无需修改原始XML字符串:
public class CustomMessageFormatter : IClientMessageFormatter { private readonly IClientMessageFormatter _originalFormatter; public CustomMessageFormatter(IClientMessageFormatter originalFormatter) { _originalFormatter = originalFormatter; } public Message SerializeRequest(MessageVersion messageVersion, object[] parameters) { var message = _originalFormatter.SerializeRequest(messageVersion, parameters); // 遍历消息内容,修改Issuer节点结构 var bodyReader = message.GetReaderAtBodyContents(); var xmlDoc = new XmlDocument(); xmlDoc.Load(bodyReader); var manager = new XmlNamespaceManager(xmlDoc.NameTable); manager.AddNamespace("trust", "http://docs.oasis-open.org/ws-sx/ws-trust/200512"); manager.AddNamespace("wsa", "http://www.w3.org/2005/08/addressing"); var issuerNode = xmlDoc.SelectSingleNode("//trust:Issuer/wsa:EndpointReference", manager); if (issuerNode != null) { var addressNode = issuerNode.SelectSingleNode("wsa:Address", manager); if (addressNode != null) { issuerNode.ParentNode.ReplaceChild(addressNode, issuerNode); } } var newBody = XmlReader.Create(new StringReader(xmlDoc.InnerXml)); return Message.CreateMessage(messageVersion, message.Headers.Action, newBody); } public object[] DeserializeReply(Message message, object[] parameters) { return _originalFormatter.DeserializeReply(message, parameters); } }
通过自定义IEndpointBehavior将格式化器附加到客户端端点:
public class CustomEndpointBehavior : IEndpointBehavior { public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters) { } public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime) { foreach (var operation in clientRuntime.Operations) { operation.Formatter = new CustomMessageFormatter(operation.Formatter); } } public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher) { } public void Validate(ServiceEndpoint endpoint) { } }
最后在客户端添加该行为:
var client = new YourStsClient(); client.Endpoint.Behaviors.Add(new CustomEndpointBehavior());
以上方法均无需直接修改发送前的XML字符串,而是通过WCF的序列化扩展点调整输出结构,更符合WCF设计规范。
内容的提问来源于stack exchange,提问作者Francesco Franzini

