c# – 使用wsHttpBinding的WCF服务 – 操作HTTP请求标头
我一直在关注
this教程,以便在我的WCF服务中使用传输安全性进行用户名身份验证.然而,教程指的是使用basicHttpBinding这是不可接受的 – 我需要wsHttpBinding.
我们的想法是在WCF服务上有一个自定义的BasicAuthenticationModule,它将从HTTP请求中读取“Authorization”头,并根据“Authorization”头内容执行auth过程.问题是缺少“授权”标题! 我已经通过自定义行为实现了IClientMessageInspector,以便操作传出消息并添加自定义SOAP标头.我在BeforeSendRequest函数中添加了以下代码: HttpRequestMessageProperty httpRequest = request.Properties.Where(x => x.Key == "httpRequest").Single().Value; httpRequest.Headers.Add("CustomHeader","CustomValue"); 这应该工作,并根据许多Web资源,它适用于basicHttpBinding但不适用于wsHttpBinding.当我说“工作”时,我的意思是WCF服务成功接收了标头. 这是在WCF服务端检查收到的HTTP消息的简化函数: public void OnAuthenticateRequest(object source,EventArgs eventArgs) { HttpApplication app = (HttpApplication)source; //the Authorization header is checked if present string authHeader = app.Request.Headers["Authorization"]; if (string.IsNullOrEmpty(authHeader)) { app.Response.StatusCode = 401; app.Response.End(); } } 2011年9月的this帖子的底部帖子说wsHttpBinding是不可能的.我不想接受这种回应. 作为旁注,如果我使用IIS中内置的基本身份验证模块而不是自定义模块,我会得到
可能是因为我的PrimaryIdentity.Name属性包含证书使用者名称,因为我正在使用带有基于证书的消息安全性的TransportWithMessageCredential安全性. 我愿意接受建议以及解决问题的方法.谢谢. UPDATE 看起来,HTTP标头稍后在整个WCF服务代码中被正确读取. 更新2 编辑 以下是我的system.servicemodel(来自web.config)的相关部分 – 我很确定我已正确配置. <serviceBehaviors> <behavior name="ServiceBehavior"> <serviceMetadata httpsGetEnabled="true" httpGetEnabled="false" /> <serviceDebug includeExceptionDetailInFaults="true" /> <serviceCredentials> <clientCertificate> <authentication certificateValidationMode="ChainTrust" revocationMode="NoCheck" /> </clientCertificate> <serviceCertificate findValue="server.uprava.djurkovic-co.me" x509FindType="FindBySubjectName" storeLocation="LocalMachine" storeName="My" /> </serviceCredentials> <serviceAuthorization principalPermissionMode="UseAspNetRoles" roleProviderName="AspNetSqlRoleProvider" /> </behavior> </serviceBehaviors> ................ <wsHttpBinding> <binding name="EndPointWSHTTP" closeTimeout="00:01:00" openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00" bypassProxyOnLocal="false" transactionFlow="false" hostNameComparisonMode="StrongWildcard" maxBufferPoolSize="20480000" maxReceivedMessageSize="20480000" messageEncoding="Text" textEncoding="utf-8" useDefaultWebProxy="true" allowCookies="false"> <readerQuotas maxDepth="20480000" maxStringContentLength="20480000" maxArrayLength="20480000" maxBytesPerRead="20480000" maxNameTableCharCount="20480000" /> <reliableSession ordered="true" inactivityTimeout="00:10:00" enabled="false" /> <security mode="TransportWithMessageCredential"> <transport clientCredentialType="Basic" /> <message clientCredentialType="Certificate" negotiateServiceCredential="true" algorithmSuite="Default" /> </security> </binding> </wsHttpBinding> ............ <service behaviorConfiguration="ServiceBehavior" name="DjurkovicService.Djurkovic"> <endpoint address="" binding="wsHttpBinding" bindingConfiguration="EndPointWSHTTP" name="EndPointWSHTTP" contract="DjurkovicService.IDjurkovic" /> </service> 该服务返回的异常是:
解决方法
有趣的是,当我写下关于上述答案的最后评论时,我停了一会儿.我的评论包含“……如果HTTP标头不包含”授权“标头,我将状态设置为401,这会导致异常.”我把状态设置为401.知道了吗?解决方案一直存在.
即使我明确添加它,初始数据包也不包含授权标头.但是,每个后续数据包都包含它,因为我在授权模块处于非活动状态时进行了测试.所以我,为什么我不尝试将这个初始数据包与其他数据包区分开来?因此,如果我发现它是初始数据包,请将HTTP状态代码设置为200(OK),如果不是,请检查身份验证标头.这很容易,因为初始数据包在SOAP信封中发送了对安全令牌的请求(包含< t:RequestSecurityToken>标记). 好的,让我们来看看我的实现,以防其他人需要它. 这是BasicAuthenticationModule实现,它实现了IHTTPModule: public class UserAuthenticator : IHttpModule { public void Dispose() { } public void Init(HttpApplication application) { application.AuthenticateRequest += new EventHandler(this.OnAuthenticateRequest); application.EndRequest += new EventHandler(this.OnEndRequest); } public void OnAuthenticateRequest(object source,EventArgs eventArgs) { HttpApplication app = (HttpApplication)source; // Get the request stream Stream httpStream = app.Request.InputStream; // I converted the stream to string so I can search for a known substring byte[] byteStream = new byte[httpStream.Length]; httpStream.Read(byteStream,(int)httpStream.Length); string strRequest = Encoding.ASCII.GetString(byteStream); // This is the end of the initial SOAP envelope // Not sure if the fastest way to do this but works fine int idx = strRequest.IndexOf("</t:RequestSecurityToken></s:Body></s:Envelope>",0); httpStream.Seek(0,SeekOrigin.Begin); if (idx != -1) { // Initial packet found,do nothing (HTTP status code is set to 200) return; } //the Authorization header is checked if present string authHeader = app.Request.Headers["Authorization"]; if (!string.IsNullOrEmpty(authHeader)) { if (authHeader == null || authHeader.Length == 0) { // No credentials; anonymous request return; } authHeader = authHeader.Trim(); if (authHeader.IndexOf("Basic",0) != 0) { // the header doesn't contain basic authorization token // we will pass it along and // assume someone else will handle it return; } string encodedCredentials = authHeader.Substring(6); byte[] decodedBytes = Convert.FromBase64String(encodedCredentials); string s = new ASCIIEncoding().GetString(decodedBytes); string[] userPass = s.Split(new char[] { ':' }); string username = userPass[0]; string password = userPass[1]; // the user is validated against the SqlMemberShipProvider // If it is validated then the roles are retrieved from // the role provider and a generic principal is created // the generic principal is assigned to the user context // of the application if (Membership.ValidateUser(username,password)) { string[] roles = Roles.GetRolesForUser(username); app.Context.User = new GenericPrincipal(new GenericIdentity(username,"Membership Provider"),roles); } else { DenyAccess(app); return; } } else { app.Response.StatusCode = 401; app.Response.End(); } } public void OnEndRequest(object source,EventArgs eventArgs) { // The authorization header is not present. // The status of response is set to 401 Access Denied. // We will now add the expected authorization method // to the response header,so the client knows // it needs to send credentials to authenticate if (HttpContext.Current.Response.StatusCode == 401) { HttpContext context = HttpContext.Current; context.Response.AddHeader("WWW-Authenticate","Basic Realm"); } } private void DenyAccess(HttpApplication app) { app.Response.StatusCode = 403; app.Response.StatusDescription = "Forbidden"; // Write to response stream as well,to give the user // visual indication of error app.Response.Write("403 Forbidden"); app.CompleteRequest(); } } 重要说明:为了使我们能够读取http请求流,不得启用ASP.NET兼容性. 要使IIS加载此模块,必须将其添加到< system.webServer> web.config的部分,如下所示: <system.webServer> <modules runAllManagedModulesForAllRequests="true"> <remove name="BasicAuthenticationModule" /> <add name="BasicAuthenticationModule" type="UserAuthenticator" /> </modules> 但在此之前,您必须确保BasicAuthenticationModule部分未被锁定,并且默认情况下应该锁定它.如果锁定,您将无法更换它. 解锁模块:(注意:我使用的是IIS 7.5) >打开IIS管理器 在客户端,您需要能够向传出消息添加自定义HTTP标头.执行此操作的最佳方法是实现IClientMessageInspector并使用BeforeSendRequest函数添加标头.我不会解释如何实现IClientMessageInspector,在线提供了大量关于该主题的资源. 要在消息中添加“Authorization”HTTP标头,请执行以下操作: public object BeforeSendRequest(ref Message request,IClientChannel channel) { // Making sure we have a HttpRequestMessageProperty HttpRequestMessageProperty httpRequestMessageProperty; if (request.Properties.ContainsKey(HttpRequestMessageProperty.Name)) { httpRequestMessageProperty = request.Properties[HttpRequestMessageProperty.Name] as HttpRequestMessageProperty; if (httpRequestMessageProperty == null) { httpRequestMessageProperty = new HttpRequestMessageProperty(); request.Properties.Add(HttpRequestMessageProperty.Name,httpRequestMessageProperty); } } else { httpRequestMessageProperty = new HttpRequestMessageProperty(); request.Properties.Add(HttpRequestMessageProperty.Name,httpRequestMessageProperty); } // Add the authorization header to the WCF request httpRequestMessageProperty.Headers.Add("Authorization","Basic " + Convert.ToBase64String(Encoding.ASCII.GetBytes(Service.Proxy.ClientCredentials.UserName.UserName + ":" + Service.Proxy.ClientCredentials.UserName.Password))); return null; } 你去了,需要一段时间才能解决,但这是值得的,因为我在整个网络上发现了许多类似的未解答的问题. (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |