11

I have a WCF service which contains a Login method that validates a username and password against the local machine credentials, and after a seemingly random period of time it will stop working for some users.

The actual login command looks like this:

public UserModel Login(string username, string password, string ipAddress)
{
    // Verify valid parameters
    if (username == null || password == null)
        return null;

    try
    {
        using (var pContext = new PrincipalContext(ContextType.Machine))
        {
            // Authenticate against local machine
            if (pContext.ValidateCredentials(username, password))
            {
                // Authenticate user against database
                using (var context = new MyEntities(Connections.GetConnectionString()))
                {
                    var user = (from u in context.Users
                                where u.LoginName.ToUpper() == username.ToUpper()
                                      && u.IsActive == true
                                      && (string.IsNullOrEmpty(u.AllowedIpAddresses)
                                        || u.AllowedIpAddresses.Contains(ipAddress))
                                select u).FirstOrDefault();

                    // If user failed to authenticate against database
                    if (user == null)
                        return null;

                    // Map entity object to return object and assign session token
                    var userModel = Mapper.Map<User, UserModel>(user);
                    userModel.Token = Guid.NewGuid();
                    userModel.LastActivity = DateTime.Now;

                    // Authenticated users are added to a list on the server
                    // and their login expires after 20 minutes of inactivity
                    authenticatedUsers.Add(userModel);
                    sessionTimer.Start();

                    // User successfully authenticated, so return UserModel to client
                    return userModel;
                }
            }
        }
    }
    catch(Exception ex)
    {
        // This is getting hit
        MessageLog.WriteMessage(string.Format("Exception occurred while validating user: {0}\n{1}", ex.Message, ex.StackTrace));
        return null;
    }

    // If authentication against local machine failed, return null
    return null;
}

This appears to work fine for a few days, then it will abruptly stop working for some users and throw this exception:

Multiple connections to a server or shared resource by the same user, using more than one user name, are not allowed. Disconnect all previous connections to the server or shared resource and try again. (Exception from HRESULT: 0x800704C3)

at System.DirectoryServices.AccountManagement.CredentialValidator.BindSam(String target, String userName, String password)

at System.DirectoryServices.AccountManagement.CredentialValidator.Validate(String userName, String password)

at System.DirectoryServices.AccountManagement.PrincipalContext.ValidateCredentials(String userName, String password)

at MyNamespace.LoginService.Login(String username, String password, String ipAddress) in C:\Users\me\Desktop\somefolder\LoginService.svc.cs:line 67

Line 67 is: if (pContext.ValidateCredentials(username, password))

I'm not sure if it matters or not, but the final line of the error message is the path of the VS solution on my development machine, not the path to the files on the production server.

When it fails, it only fails for some users, while others can continue to login just fine. The only thing I have found to temporarily fix the error is running iisreset. Stopping/starting the web site or recycling the app pool doesn't work.

I am unable to reproduce the error on demand. I've tried logging in with the same user from multiple sessions and IP addresses, logging in with different users from the same browser session at the same time, spamming the Login button to try and get it to run multiple times, etc but everything appears to work fine.

I can see from our logging that users have been successfully able to login in the past:

3/21/2013
o   9:03a   I logged in
o   1:54p   UserB logged in
o   1:55p   UserA logged in
o   2:38p   UserB logged in
o   3:48p   UserB logged in
o   5:18p   UserA logged in
o   6:11p   UserB logged in

3/22/2013
o   12:42p  UserA logged in
o   5:22p   UserB logged in
o   8:04p   UserB logged in

3/25/2013 (today)
o   8:47a   I logged in
o   12:38p  UserB tries logging in and fails. Repeated ~15 times over next 45 min
o   1:58p   I login successfully
o   2:08p   I try to login with UserB's login info and fail with the same error

The reason we authenticate against the local machine is because users have an account created locally for FTP access, and we didn't want to build our own custom login system or make our users remember two sets of credentials.

The code should only authenticate the user's credentials, and does not do do anything else with the user's credentials. There is no other code that uses System.DirectoryServices, no file IO going on, and no access to anything locally on the file system other than the files required to run the web application.

What can cause that error to appear, seemingly at random, after a few days? And how can I fix it?

The server is Windows Server 2003, which runs IIS 6.0, and it is setup to use .Net Framework 4.0

4

1 回答 1

5

我可以在网上找到最接近解释这个问题的是这个论坛帖子,用户遇到同样的错误并得到重播说明:

WinNT 提供程序在服务器环境中表现不佳。我实际上很惊讶你没有看到这个负载要小得多。我只需要 2 或 3 个用户就可以做到这一点。

这个SO评论说明

正确验证某人的最佳方法是使用 LogonUserAPI 作为@stephbu 编写。这篇文章中描述的所有其他方法都不会 100% 有效

其中“所有其他方法”包括使用的最高投票答案PrincipalContext.ValidateCredentials

它在 Windows Server 2003 和 IIS6.0 上听起来PrincipalContext.ValidateCredentials并不完全 100% 可靠,所以我重写了我的身份验证代码以改用LogonUser WinAPI 方法。

[DllImport("advapi32.dll", SetLastError = true)]
public static extern bool LogonUser(
    string lpszUsername,
    string lpszDomain,
    string lpszPassword,
    int dwLogonType,
    int dwLogonProvider,
    out IntPtr phToken
    );

IntPtr hToken;
if (LogonUser(username, "", password, 
    LOGON32_LOGON_NETWORK, LOGON32_PROVIDER_DEFAULT, out hToken))
{
    ...
}
于 2013-03-26T18:13:04.757 回答