我的问题如下。
客户端通过Web界面(HTTP)与我的WCF服务进行交互。 某些服务操作要求客户端通过提供用户名和密码进行身份验证。 让我们假设这些信息是通过查询字符串参数传递的(或者在HTTP Basic Auth中的Authorization标头中)。
例如,可以通过http://myhost.com/myservice/myop?user=xxx&password=yyy
调用服务操作由于多个服务操作需要这种身份验证,我想将验证代码从单个操作中分解出来。
环顾四周,我阅读了有关服务行为的内容,并提出了以下代码:
public class MyAuthBehaviorAttribute : Attribute, IServiceBehavior, IDispatchMessageInspector {
/********************/
/* IServiceBehavior */
public void ApplyDispatchBehavior(ServiceDescription serviceDescription,
System.ServiceModel.ServiceHostBase serviceHostBase) {
// It’s called right after the runtime was initialized
foreach (ChannelDispatcher chDisp in serviceHostBase.ChannelDispatchers) {
foreach (EndpointDispatcher epDisp in chDisp.Endpoints) {
epDisp.DispatchRuntime.MessageInspectors.Add(new MyAuthBehaviorAttribute());
}
}
}
/*...*/
/*****************************/
/* IDispatchMessageInspector */
public object AfterReceiveRequest(ref System.ServiceModel.Channels.Message request,
System.ServiceModel.IClientChannel channel,
System.ServiceModel.InstanceContext instanceContext) {
object correlationState = null;
var prop = (HttpRequestMessageProperty)request.Properties[HttpRequestMessageProperty.Name];
var parts = HttpUtility.ParseQueryString(prop.QueryString);
string user = parts["user"];
string password = parts["password"];
if (AuthenticateUser(user,password)) {
// ???????????????????????????
}
else {
throw new Exception("...");
}
return correlationState;
}
/*...*/
}
然后,通过
注释该服务[MyAuthBehavior]
public class Service : IContract
{
// implementation of the IContract interface
}
现在,我设法在任何服务操作之前执行我的行为。 但是,我有以下问题:
关于最后一点,我查看了IOperationBehavior,但在这种情况下,我只能附加IParameterInspectors而不是IDispatchMessageInspectors。这是不可取的,因为我可能需要查看消息头,例如,如果我决定在支持HTTP基本身份验证时考虑授权HTTP头。
作为一个相关的问题,我也会问你对我的方法的看法,以及是否有更好的(非过于复杂的)方法。
答案 0 :(得分:1)
我建议将所有不需要身份验证的方法隔离到自己的服务中。例如:
IPublicService.cs和PublicService.svc
以及需要身份验证的内容:
IPrivateService.cs和PrivateService.svc
对于PrivateService.svc的身份验证,我建议使用MessageCredential使用Username进行绑定:
<wsHttpBinding>
<binding name="wsHttpEndpointBinding" closeTimeout="00:30:00" openTimeout="00:30:00" receiveTimeout="00:30:00" sendTimeout="00:30:00" maxReceivedMessageSize="500000000">
<readerQuotas maxDepth="500000000" maxStringContentLength="500000000" maxArrayLength="500000000" maxBytesPerRead="500000000" maxNameTableCharCount="500000000" />
<security mode="MessageCredential">
<message clientCredentialType="UserName" />
</security>
</binding>
</wsHttpBinding>
并添加自定义用户名验证器类:
public class CustomUserNameValidator : UserNamePasswordValidator
{
public override void Validate(string userName, string password)
{
if (username!="test" && password!="test")
{
throw new FaultException("Unknown username or incorrect password.");
}
return;
}
}
在web.config中注册你的课程:
<behaviors>
<serviceBehaviors>
<behavior>
<!-- To avoid disclosing metadata information, set the values below to false before deployment -->
<serviceMetadata httpsGetEnabled="true" />
<!-- To receive exception details in faults for debugging purposes, set the value below to true. Set to false before deployment to avoid disclosing exception information -->
<serviceDebug includeExceptionDetailInFaults="true" />
<serviceCredentials>
<userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="MyProgram.CustomUserNameValidator,MyProgram" />
</serviceCredentials>
</behavior>
</serviceBehaviors>
</behaviors>
答案 1 :(得分:1)
经过一番研究,这是我目前的解决方案。
首先,我使用自定义属性标记我的服务操作:
public class RequiresAuthAttribute : Attribute { }
public partial class MyService {
[RequiresAuth]
WebGet(UriTemplate = "...")]
public Tresult MyServiceOperation(...){ ... }
然后我检索此信息以决定是否必须执行该行为
public object AfterReceiveRequest(ref System.ServiceModel.Channels.Message request,
System.ServiceModel.IClientChannel channel,
System.ServiceModel.InstanceContext instanceContext) {
if(AuthenticationNeeded()){ ... }
}
public bool AuthenticationNeeded() {
// 1) Get the current operation's description
OperationDescription od = GetOperationDescription(OperationContext.Current);
// 2) Check if the service operation is annotated with the [RequiresAuth] attribute
Type contractType = od.DeclaringContract.ContractType;
object[] attr = contractType.GetMethod(od.Name).GetCustomAttributes(typeof(RequiresAuthAttribute), false);
if (attr == null || attr.Length == 0) return false;
return true;
}
// See http://www.aspnet4you.com/wcf/index.php/2013/01/30/message-interception-auditing-and-logging-at-wcf-pipeline/
private OperationDescription GetOperationDescription(OperationContext operationContext) {
OperationDescription od = null;
string bindingName = operationContext.EndpointDispatcher.ChannelDispatcher.BindingName;
string methodName;
if (bindingName.Contains("WebHttpBinding")) {
//REST request
methodName = (string)operationContext.IncomingMessageProperties["HttpOperationName"];
}
else {
//SOAP request
string action = operationContext.IncomingMessageHeaders.Action;
methodName = operationContext.EndpointDispatcher.DispatchRuntime.Operations.FirstOrDefault(o => o.Action == action).Name;
}
EndpointAddress epa = operationContext.EndpointDispatcher.EndpointAddress;
ServiceDescription hostDesc = operationContext.Host.Description;
ServiceEndpoint ep = hostDesc.Endpoints.Find(epa.Uri);
if (ep != null) {
od = ep.Contract.Operations.Find(methodName);
}
return od;
}
服务行为将执行某些操作
OperationContext.Current.IncomingMessageProperties.Add("myInfo", myInfo);
虽然服务操作将执行某些操作
object myInfo = null;
OperationContext.Current.IncomingMessageProperties.TryGetValue("myInfo", out myInfo);
或者,也可以通过
设置服务操作参数的值WebOperationContext.Current.IncomingRequest.UriTemplateMatch.BoundVariables["MYPARAM"] = myParam;