Gene S
Gene S

Reputation: 2773

Programmatically impersonate fixed windows identity for web service

I have a web service (old web service, not WCF) and I am using IIS 7.0 to communicate to that web service. Only Windows Authentication is enabled in IIS 7.0 (even Anonymous is disabled). I need to be able to specify a specific windows identity within code when making the service call. I have found many places that indicates this can be done in the config file in the following manner...

<authentication mode="Windows" />
<identity impersonate="true" userName="UserName" password="P@ssw0rd" />

But I need to do this same thing in code. I am sure that are many people thinking "Why would you want to do it that way". Without going into the big long explanation the simpliest answer is because those are my requirements.

Here is what my code looks like...

HttpTransportBindingElement transport = useHttps ? new HttpsTransportBindingElement() : new HttpTransportBindingElement();
transport.ManualAddressing = false;
transport.MaxBufferPoolSize = 134217728; // 128MB
transport.MaxReceivedMessageSize = 134217728; // 128MB
transport.AllowCookies = false;
transport.AuthenticationScheme = AuthenticationSchemes.Negotiate;
transport.BypassProxyOnLocal = false;
transport.DecompressionEnabled = true;
transport.HostNameComparisonMode = HostNameComparisonMode.StrongWildcard;
transport.KeepAliveEnabled = true;
transport.MaxBufferSize = 134217728; // 128MB,
transport.ProxyAuthenticationScheme = AuthenticationSchemes.Negotiate;
transport.Realm = "";
transport.TransferMode = TransferMode.Buffered;
transport.UnsafeConnectionNtlmAuthentication = false;
transport.UseDefaultWebProxy = false;

TextMessageEncodingBindingElement encoding = new TextMessageEncodingBindingElement
{
    MaxReadPoolSize = 64,
    MaxWritePoolSize = 16,
    MessageVersion = MessageVersion.Soap12,
    WriteEncoding = Encoding.UTF8,
    ReaderQuotas = new XmlDictionaryReaderQuotas
    {
        MaxDepth = 32,
        MaxStringContentLength = 134217728, // 128MB
        MaxArrayLength = 134217728, // 128MB
        MaxBytesPerRead = 4096,
        MaxNameTableCharCount = 16384
    }
};

CustomBinding binding = new CustomBinding();
binding.Elements.Add(encoding);
binding.Elements.Add(transport);

ServicePointManager.Expect100Continue = false;

generalSoapClient general = new generalSoapClient(binding, new EndpointAddress("http://localhost/site/ws/general.asmx"));
NetworkCredential iisCredentials = new NetworkCredential("UserName", "P@ssw0rd");
general.ClientCredentials.Windows.AllowedImpersonationLevel = System.Security.Principal.TokenImpersonationLevel.Impersonation;
general.ClientCredentials.Windows.ClientCredential = iisCredentials;
string session = general.CreateDomainUserSessionFromInstance();

There is nothing defined in the .config file on the client side. Everything is configured in code.

My web service method looks like this (some code missing that is not related to authentication)...

[WebMethod(EnableSession = true)]
[OperationBehavior(Impersonation = ImpersonationOption.Allowed)]
public string CreateDomainUserSessionFromInstance()
{
    if(HttpContext.Current.User != null && HttpContext.Current.User.Identity.IsAuthenticated)
    {
        WindowsIdentityRequest authenticationRequest = new WindowsIdentityRequest(instanceName, HttpContext.Current.User.Identity as WindowsIdentity);
        response = authManager.Login(authenticationRequest);
    }

    return response.SessionContext.SessionToken;
}

My web.config on the server side looks like this...

<system.web>
    <authentication mode="Windows" />
    <identity impersonate="true" />
    <authorization>
        <!--<allow users="*" />-->
        <deny users="?" />
    </authorization>
</system.web>

<customBinding>
    <binding name="textHttpBinding" receiveTimeout="00:05:00" sendTimeout="00:05:00">
      <textMessageEncoding>
        <readerQuotas maxArrayLength="1024000000" maxStringContentLength="1024000000" />
      </textMessageEncoding>
      <httpTransport maxReceivedMessageSize="1024000000" maxBufferSize="1024000000" authenticationScheme="Negotiate" />
  </customBinding>

When I have the <deny users="?" /> I get the following error..."The HTTP request is unauthorized with client authentication scheme 'Negotiate'. The authentication header received from the server was ''" Someone told me it should be <allow users="*" /> but when I do this I can get into the web service but the HttpContext.Current.User.Identity.IsAuthenticated is false and .Name is empty and what I read on the internet is that it needs to be deny users="?" /> to deny anonymous access.

I am new to web services and so unfortunately most of this code is greek to me. Our web service initially allowed anonymous authentication but the requirement has changed to require windows authentication.

I have spent several days reading numerous web sites trying to get everything configured correctly and just can't seem to find the right combination.
What am I doing wrong? Is it something simple or am I way off base?

Upvotes: 1

Views: 3962

Answers (1)

DJ Burb
DJ Burb

Reputation: 2364

Here's a class I use to impersonate:

public class Impersonator :
    IDisposable
{
    private const int LOGON32_LOGON_NEW_CREDENTIALS = 9;


    public Impersonator(
        string userName,
        string domainName,
        string password )
    {
        ImpersonateValidUser( userName, domainName, password );
    }


    public void Dispose()
    {
        UndoImpersonation();
    }


    #region P/Invoke.
    // ------------------------------------------------------------------

    [DllImport("advapi32.dll", SetLastError=true)]
    private static extern int LogonUser(
        string lpszUserName,
        string lpszDomain,
        string lpszPassword,
        int dwLogonType,
        int dwLogonProvider,
        ref IntPtr phToken);

    [DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true)]
    private static extern int DuplicateToken(
        IntPtr hToken,
        int impersonationLevel,
        ref IntPtr hNewToken);

    [DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true)]
    private static extern bool RevertToSelf();

    [DllImport("kernel32.dll", CharSet=CharSet.Auto)]
    private static extern  bool CloseHandle(
        IntPtr handle);

    private const int LOGON32_LOGON_INTERACTIVE = 2;
    private const int LOGON32_PROVIDER_DEFAULT = 0;


    #region Private member.
    // ------------------------------------------------------------------

    /// <summary>
    /// Does the actual impersonation.
    /// </summary>
    /// <param name="userName">The name of the user to act as.</param>
    /// <param name="domainName">The domain name of the user to act as.</param>
    /// <param name="password">The password of the user to act as.</param>
    private void ImpersonateValidUser(
        string userName, 
        string domain, 
        string password )
    {
        WindowsIdentity tempWindowsIdentity = null;
        IntPtr token = IntPtr.Zero;
        IntPtr tokenDuplicate = IntPtr.Zero;

        try
        {
            if ( RevertToSelf() )
            {
                if ( LogonUser(
                    userName, 
                    domain, 
                    password,
                    LOGON32_LOGON_NEW_CREDENTIALS,
                    LOGON32_PROVIDER_DEFAULT, 
                    ref token ) != 0 )
                {
                    if ( DuplicateToken( token, 2, ref tokenDuplicate ) != 0 )
                    {
                        tempWindowsIdentity = new WindowsIdentity( tokenDuplicate );
                        impersonationContext = tempWindowsIdentity.Impersonate();
                    }
                    else
                    {
                        throw new Win32Exception( Marshal.GetLastWin32Error() );
                    }
                }
                else
                {
                    throw new Win32Exception( Marshal.GetLastWin32Error() );
                }
            }
            else
            {
                throw new Win32Exception( Marshal.GetLastWin32Error() );
            }
        }
        finally
        {
            if ( token!= IntPtr.Zero )
            {
                CloseHandle( token );
            }
            if ( tokenDuplicate!=IntPtr.Zero )
            {
                CloseHandle( tokenDuplicate );
            }
        }
    }

    /// <summary>
    /// Reverts the impersonation.
    /// </summary>
    private void UndoImpersonation()
    {
        if ( impersonationContext.IsNotNull() )
        {
            impersonationContext.Undo();
        }   
    }

    private WindowsImpersonationContext impersonationContext = null;

    // ------------------------------------------------------------------
    #endregion
}

Upvotes: 1

Related Questions