c# – 绑定安全性为TransportCredentialOnly时,在WCF REST中创建
我需要实现一个使用HTTP基本身份验证的REST服务.由于它是在现有基础架构上构建的,因此我需要将其实现为WCF服务.出于向后兼容性和集成到现有生态系统的原因,我需要将用户名和密码都传递给服务(此时请不要考虑可能的安全隐患).由于默认情况下WCF运行时从标头中删除了身份验证信息,因此我的解决方案是创建一个包含密码信息的自定义IIdentity,我可以在服务级别访问该信息:
public class UserIdentity : GenericIdentity { private readonly bool m_isAuthenticated; public string Password { get; } public override bool IsAuthenticated { get { return base.IsAuthenticated && m_isAuthenticated; } } public UserIdentity(IIdentity existingIdentity,string password) : base(existingIdentity.Name) { m_isAuthenticated = existingIdentity.IsAuthenticated; Password = password; } } 我试图通过以下方式转发密码,所有这些都没有运气: >实现自定义UserNamePasswordValidator,它可以访问密码,但只能处理身份验证.没有办法创建或修改IIdentity. 自定义ServiceCredentials和AuthorizationPolicy实现如下: public class UserServiceCredentials : ServiceCredentials { public UserServiceCredentials() { } protected UserServiceCredentials(ServiceCredentials other) : base(other) { } protected override ServiceCredentials CloneCore() { return new UserServiceCredentials(this); } public override SecurityTokenManager CreateSecurityTokenManager() { if (UserNameAuthentication.UserNamePasswordValidationMode == UserNamePasswordValidationMode.Custom) { return new UserSecurityTokenManager(this); } return base.CreateSecurityTokenManager(); } } internal class UserSecurityTokenManager : ServiceCredentialsSecurityTokenManager { public UserSecurityTokenManager(UserServiceCredentials credentials) : base(credentials) { } public override SecurityTokenAuthenticator CreateSecurityTokenAuthenticator(SecurityTokenRequirement tokenRequirement,out SecurityTokenResolver outOfBandTokenResolver) { outOfBandTokenResolver = null; UserNamePasswordValidator validator = ServiceCredentials.UserNameAuthentication.CustomUserNamePasswordValidator; return new UserSecurityTokenAuthenticator(validator ?? new Validator()); } } internal class UserSecurityTokenAuthenticator : CustomUserNameSecurityTokenAuthenticator { public UserSecurityTokenAuthenticator(UserNamePasswordValidator validator) : base(validator) { } protected override ReadOnlyCollection<IAuthorizationPolicy> ValidateUserNamePasswordCore(string userName,string password) { ReadOnlyCollection<IAuthorizationPolicy> currentPolicies = base.ValidateUserNamePasswordCore(userName,password); List<IAuthorizationPolicy> policies = new List<IAuthorizationPolicy>(currentPolicies); policies.Add(new UserAuthorizationPolicy(userName,password)); return policies.AsReadOnly(); } } public class UserAuthorizationPolicy : IAuthorizationPolicy { private string m_userName; private string m_password; //Called when used with service credentials public UserAuthorizationPolicy(string userName,string password) { m_userName = userName; m_password = password; } //Called when directly configured in the config file public UserAuthorizationPolicy() { } public ClaimSet Issuer { get; } = ClaimSet.System; public string Id { get; } = Guid.NewGuid().ToString(); public bool Evaluate(EvaluationContext evaluationContext,ref object state) { bool hasIdentities = evaluationContext.Properties.TryGetValue("Identities",out object rawIdentities); if (rawIdentities is IList<IIdentity> identities) { var identityQry = from id in identities where String.Equals(id.Name,m_userName,StringComparison.OrdinalIgnoreCase) select id; IIdentity identity = identityQry.FirstOrDefault(); if (identity == null) { return false; } UserIdentity userIdentity = new UserIdentity(identity,m_password); identities.Remove(identity); identities.Add(userIdentity); evaluationContext.Properties["PrimaryIdentity"] = userIdentity; evaluationContext.Properties["Principal"] = new GenericPrincipal(userIdentity,null); return true; } else { return false; } } } 我正在使用的app.config是这样的: <?xml version="1.0" encoding="utf-8" ?> <configuration> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" /> </startup> <system.serviceModel> <bindings> <webHttpBinding> <binding name="TestBinding"> <security mode="TransportCredentialOnly"> <transport clientCredentialType="Basic"> </transport> </security> </binding> </webHttpBinding> </bindings> <behaviors> <serviceBehaviors> <behavior name="TestServiceBehavior"> <serviceMetadata httpGetEnabled="true" httpsGetEnabled="true" /> <serviceDebug includeExceptionDetailInFaults="true"/> <!-- Custom service credentials: Works when binding security is Transport. Is not invoked when security TransportCredentialOnly--> <serviceCredentials type="WcfTestServices.UserServiceCredentials,WcfTestServices"> <userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="WcfTestServices.Validator,WcfTestServices"/> </serviceCredentials> <serviceAuthorization principalPermissionMode="Custom"> <!-- Authorization policy works when binding security is TransportCredentialOnly,but has no password --> <authorizationPolicies> <add policyType="WcfTestServices.UserAuthorizationPolicy,WcfTestServices"/> </authorizationPolicies> </serviceAuthorization> </behavior> </serviceBehaviors> <endpointBehaviors> <behavior name="TestEndpointBehavior"> <webHttp/> </behavior> </endpointBehaviors> </behaviors> <services> <service name="WcfTestServices.TestService" behaviorConfiguration="TestServiceBehavior"> <endpoint address="" binding="webHttpBinding" bindingConfiguration="TestBinding" behaviorConfiguration="TestEndpointBehavior" contract="WcfTestServices.ITestService"/> <host> <baseAddresses> <add baseAddress="http://localhost:12700/"/> </baseAddresses> </host> </service> </services> </system.serviceModel> </configuration> 有没有办法可以将密码信息转发到这个星座中的服务?我首选的解决方案是自定义IIdentity,但我愿意接受其他建议. 解决方法
通过cookie发送信息也可能是一个选项,你可以尝试以下,
服务方 创建一个实现IDispatchMessageInspector的类 public class IdentityMessageInspector : IDispatchMessageInspector { public object AfterReceiveRequest(ref Message request,System.ServiceModel.IClientChannel channel,System.ServiceModel.InstanceContext instanceContext) { var messageProperty = (HttpRequestMessageProperty) OperationContext.Current.IncomingMessageProperties[HttpRequestMessageProperty.Name]; string cookie = messageProperty.Headers.Get("Set-Cookie"); if (cookie == null) // Check for another Message Header - SL applications { cookie = messageProperty.Headers.Get("Cookie"); } if (cookie == null) cookie = string.Empty; //You can get the credentials from here,do something to them,on the service side } 注意,根据链接的MSDN链接,行OperationContext.IncomingMessageProperties Property可用于获取消息的incomming消息属性,
,然后创建一个实现IServiceBehvaior的类,例如
你需要实现界面,并修改
方法如下 public void ApplyDispatchBehavior(ServiceDescription serviceDescription,System.ServiceModel.ServiceHostBase serviceHostBase) { foreach (ChannelDispatcher dispatcher in serviceHostBase.ChannelDispatchers) { foreach (var endpoint in dispatcher.Endpoints) { endpoint.DispatchRuntime.MessageInspectors.Add(new IdentityMessageInspector()); } } } ,然后procceed将其添加到您的web.config / app.config文件中 <extensions> <behaviorExtensions> <add name="interceptorBehaviorExtension" type="test.InterceptorBehaviorExtension,test,Version=1.0.0.0,Culture=neutral,PublicKeyToken=null"/> </behaviorExtensions> </extensions> ,然后包括该行 <interceptorBehaviorExtension /> 在您的行为元素标记中. 客户 在客户端,您需要使用IClientMessageInspector修改httpmessage并修改
将凭据添加到客户端代码的方法. 接下来,将其添加到实现IEndpointBehavior的类中,
并修改 public void ApplyClientBehavior(ServiceEndpoint endpoint,System.ServiceModel.Dispatcher.ClientRuntime clientRuntime) { clientRuntime.MessageInspectors.Add(new CookieMessageInspector()); } 方法,然后将上面的代码添加到WCF客户端代码中的端点行为列表中, 更新: 解决方案的关键是从此行中的原始HTTP消息中获取标头: var messageProperty = (HttpRequestMessageProperty)OperationContext.Current .IncomingMessageProperties[HttpRequestMessageProperty.Name]; 这允许您访问授权标头,如下所示: string authorization = message.Headers.Get("Authorization"); 由于OperationContext可从服务本身读取,因此可以直接从服务读取和解析授权数据.在基本身份验证的情况下,这包括用户名和密码.不需要消息检查器(尽管您需要在验证时忽略密码的其他UserNamePasswordValidator). (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |