【问题标题】:WCF Authentication not workingWCF 身份验证不起作用
【发布时间】:2012-04-29 14:20:14
【问题描述】:

我已经在我的 WCF 应用程序中实现了我的自定义 userNamePasswordValidationMode,如下所示:

<system.serviceModel>
    <bindings>
        <wsHttpBinding>
            <binding>
                <security mode ="Message">
                    <message clientCredentialType="UserName"/>
                </security>
            </binding>
        </wsHttpBinding>
    </bindings>
    <behaviors>
        <serviceBehaviors>
            <behavior>
                <serviceMetadata httpGetEnabled="True"/>
                <serviceCredentials>
                    <userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="MyProject.Validator.MyValidator, MyProject" />
                </serviceCredentials>
            </behavior>
        </serviceBehaviors>
    </behaviors>
    <serviceHostingEnvironment multipleSiteBindingsEnabled="true" aspNetCompatibilityEnabled="true" />
</system.serviceModel>

这不会引发任何错误,但是当我在客户端中引用我的服务并设置用户名/密码凭据时,我的方法仍然会被调用,即使我输入了错误的密码:

Testing.myAPIClient.client = new Testing.myAPIClient();
client.ClientCredentials.UserName.UserName = "test";
client.ClientCredentials.UserName.Password = "wrongpassword";

Console.WriteLine(client.StockQuery("123"));
Console.ReadLine();

StockQuery 方法仍然被调用,MyValidator 中的代码甚至没有被调用:

public class MyValidator : UserNamePasswordValidator
    {
        public override void Validate(string userName, string password)
        {
            using (var ax = new AXConnector())
            {
                if (!(bool)ax.CallStaticClassMethod("OnlineUsers", "validateLogon", userName, password))
                {
                    throw new UnauthorizedAccessException("Not Authorised");
                }
            }
        }
    }

编辑

这是我的 app.config:

    <system.serviceModel>
    <bindings>
        <basicHttpBinding>
            <binding name="BasicHttpBinding_IMyAPI" closeTimeout="00:01:00"
                openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
                allowCookies="false" bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard"
                maxBufferSize="65536" maxBufferPoolSize="524288" maxReceivedMessageSize="65536"
                messageEncoding="Text" textEncoding="utf-8" transferMode="Buffered"
                useDefaultWebProxy="true">
                <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
                    maxBytesPerRead="4096" maxNameTableCharCount="16384" />
                <security mode="None">
                    <transport clientCredentialType="None" proxyCredentialType="None"
                        realm="" />
                    <message clientCredentialType="UserName" algorithmSuite="Default" />
                </security>
            </binding>
        </basicHttpBinding>
    </bindings>
    <client>
        <endpoint address="http://myServer:89/MyAPI.svc" binding="basicHttpBinding"
            bindingConfiguration="BasicHttpBinding_IMyAPI" contract="Testing.IMyAPI"
            name="BasicHttpBinding_IMyAPI" />
    </client>
</system.serviceModel>

编辑 2

服务接口:

[ServiceContract]
public interface IMyAPI
{
    string UserName { [OperationContract] get; [OperationContract] set; }
    string Password { [OperationContract] get; [OperationContract] set; }

    [OperationContract]
    bool StockQuery(string partNo);

    [OperationContract]
    decimal PriceQuery(string partNo, string accNo);
}

服务类:

[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required),
ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession, ConcurrencyMode = ConcurrencyMode.Single)]
public class MyAPI : IMyAPI
{
    public string UserName { get; set; }
    public string Password { get; set; }
    public MyAPI()
    {
        this.CheckSecurity();
    }

    private void CheckSecurity()
    {
        if (this.UserName != "test" && this.Password != "123")
        {
            throw new UnauthorizedAccessException("Not Authorised");
        }
    }

    // StockQuery and PriceQuery methods...
}

【问题讨论】:

  • 您能否验证从添加服务引用创建的客户端配置是否包含与您的服务端相同的安全设置?它是您服务的完整配置吗?
  • 你的程序集是 MyValidator 被命名为 MyProject 的地方吗?
  • 是的,我相信,这是我项目的名称?

标签: c# wcf wcf-security


【解决方案1】:

您的客户Security.Mode 设置为"None",它应该是"Message"

<security mode ="Message">
    <message clientCredentialType="UserName"/>
</security>

编辑:您可能需要certificate 才能使用它。可以关注this walkthrough,但不建议在生产中使用。

[1]另一种选择是实现您自己的安全性。这是一个基本示例。

WCF 服务

在您的服务中,将ServiceBehaviorInstanceContextMode 更改为PerSessionConcurrencyMode 更改为Single

[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession, ConcurrencyMode = ConcurrencyMode.Single)]
public class SomeService : ISomeService
{ 
    // ...
}

在您的服务中添加 UsernamePassword 属性。

public string UserName { [OperationContract] get; [OperationContract] set; }
public string Password { [OperationContract] get; [OperationContract] set; }

添加用于检查安全性的私有方法。

public void CheckSecurity()
{
    if ((this.UserName == null || this.Password == null) ||
        this.UserName == "username" && this.Password == "password"))
    {
        throw new FaultException("Unknown username or incorrect password.");
    }
}

然后在你的每个服务[2]类构造函数方法中调用CheckSecurity方法。

public void SomeServiceMethod()
{
    this.CheckSecurity();
    // some method code
}

客户端应用程序

在您的客户端应用程序代码中,为每个实例设置服务用户名和密码,或创建一个静态类来为您执行此操作。

您也可以尝试在用户名和密码中使用加密来增加安全性。

[1] 摘自我在How to put a password on a WCF Service? 中的回答
[2] 您需要在每个服务方法的开头调用CheckSecuirty 方法(s )。

【讨论】:

  • 如何在客户端默认设置安全模式为消息?这可以从我的 WCF 应用程序中实现吗?因为这意味着他们只需将其设置为 None 即可绕过身份验证?
  • 我还收到以下错误:BasicHttp binding requires that BasicHttpBinding.Security.Message.ClientCredentialType be equivalent to the BasicHttpMessageCredentialType.Certificate credential type for secure messages. Select Transport or TransportWithMessageCredential security for UserName credentials. 如果我使用其中任何一个,我需要使用 Https,而我没有。
  • 我是将这些属性放在我的服务接口还是服务中?我已将它们放入服务中,但它会引发异常,因为我的服务上方没有 ServiceContract 属性,它在我的界面上方。
  • 把它放在接口string UserName { [OperationContract] get; [OperationContract] set; }和服务public string UserName { get; set; }中。要在客户端访问它,您需要这样做:myServiceClient.set_Username("Username").
  • 好的,我已经这样做了,但是当我调试时,我的用户名和密码都是 null,因为当我调用 client.set_Username("test") 时调用了构造函数?
【解决方案2】:

多年来,我们遇到了一切运行良好的问题,但现在我们正在迁移到一个环境,在该环境中,一切都在负载平衡器后面进行 SSL 卸载,因此我们不再能够依赖传输。我遇到了 OP 描述的所有相同问题。

我们仍在使用正则规则 basicHttpBinding。得到它的工作方式:

创建了一个可以解析标头 XML 的类:

[DataContract(Namespace = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd", Name = "Security")]
public partial class SecurityHeaderType
{
    [XmlElement(Namespace = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd")]
    [DataMember]
    public UsernameToken UsernameToken { get; set; }
}

public class UsernameToken : IXmlSerializable
{
    public string Username { get; set; }
    public string Password { get; set; }

    public XmlSchema GetSchema()
    {
        throw new NotImplementedException();
    }

    public void ReadXml(XmlReader reader)
    {
        Dictionary<string, string> secDictionary;
        string xml = reader.ReadOuterXml();

        using (var s = GenerateStreamFromString(xml))
        {
            secDictionary =
                XElement.Load(s).Elements()
                    .ToDictionary(e => e.Name.LocalName, e => e.Value);
        }

        Username = secDictionary["Username"];
        Password = secDictionary["Password"];

    }

    public Stream GenerateStreamFromString(string s)
    {
        var stream = new MemoryStream();
        var writer = new StreamWriter(stream);
        writer.Write(s);
        writer.Flush();
        stream.Position = 0;
        return stream;
    }

    public void WriteXml(XmlWriter writer)
    {
        throw new NotImplementedException();
    }
}

我按照@john-isaiah-carmona 的用户名/密码字段示例,然后像这样写信给他们:

public void CheckSecurity()
{
    if (OperationContext.Current.IncomingMessageHeaders.FindHeader("Security", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd") != -1)
    {
        var securityHeader = OperationContext.Current.IncomingMessageHeaders.GetHeader<SecurityHeaderType>("Security", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
        Username = securityHeader.UsernameToken.Username;
        Password = securityHeader.UsernameToken.Password;
    }
    else
        throw new FaultException("Unknown username or incorrect password.");
}

【讨论】:

    猜你喜欢
    • 2018-05-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-11-28
    • 1970-01-01
    • 2013-08-26
    • 1970-01-01
    相关资源
    最近更新 更多