如何以特定域用户身份运行Internet Explorer Selenium测试?

15
我有一个使用Windows身份验证控制访问的ASP.NET MVC网站。我想通过模拟非授权用户访问该站点来检查配置是否正确,使用Specflow Selenium测试实现。
由于我们使用域帐户来控制访问,因此没有用户名/密码登录屏幕。当前用户的凭据会自动传递给网站。
因此,对于我的Selenium测试,我需要能够以特定用户运行Internet Explorer。
我已经找到了一些关于Windows模拟的文章,并可以在测试运行期间切换到我的测试用户(使用http://support.microsoft.com/kb/306158中的代码)。但是,如果我创建InternetExplorerDriver,它将使用我的凭据启动Internet Explorer,而不是测试用户的凭据(尽管这个问题和答案表明它应该工作https://sqa.stackexchange.com/questions/2277/using-selenium-webdriver-with-windows-authentication)。
我还可以显式地以我的测试用户身份启动Internet Explorer进程,但我看不到将InternetExplorerDriver绑定到已经运行的Internet Explorer进程的方法,所以这可能是一个死胡同。
我的代码基本上是从上面的MSDN页面中获取的。在调试器中,我可以看到测试的所有步骤中WindowsIdentity.GetCurrent().Name都是“testUser”。
namespace MyProject.Specs
{
using NUnit.Framework;
using OpenQA.Selenium;
using OpenQA.Selenium.IE;
using System;
using System.Runtime.InteropServices;
using System.Security.Principal;
using TechTalk.SpecFlow;

[Binding]
public class AuthorisationSteps
{
    public const int LOGON32_LOGON_INTERACTIVE = 2;
    public const int LOGON32_PROVIDER_DEFAULT = 0;
    private static WindowsImpersonationContext impersonationContext;
    private static IWebDriver driver;

    [BeforeScenario]
    public static void impersonateUser()
    {
        if (!impersonateValidUser("testUser", "testDomain", "password"))
        {
            throw new Exception();
        }
        driver = new InternetExplorerDriver();
    }

    [AfterScenario]
    public static void cleanupUser()
    {
        undoImpersonation();
        driver.Quit();
    }

    [Given(@"I am an unauthorised user")]
    public void GivenIAmAnUnauthorisedUser()
    {
        var temp = WindowsIdentity.GetCurrent().Name;
    }

    [When(@"I go to the home page")]
    public void WhenIGoToTheHomePage()
    {
        var temp = WindowsIdentity.GetCurrent().Name;
        driver.Navigate().GoToUrl(BaseUrl);
    }

    [Then(@"I should see an error page")]
    public void ThenIShouldSeeAnErrorPage()
    {
        var temp = WindowsIdentity.GetCurrent().Name;
        Assert.That(driver.Title.Contains("Error"));
    }

    [DllImport("advapi32.dll")]
    public static extern int LogonUserA(String lpszUserName,
                                        String lpszDomain,
                                        String lpszPassword,
                                        int dwLogonType,
                                        int dwLogonProvider,
                                        ref IntPtr phToken);

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

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

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

    private static bool impersonateValidUser(String userName, String domain, String password)
    {
        WindowsIdentity tempWindowsIdentity;
        var token = IntPtr.Zero;
        var tokenDuplicate = IntPtr.Zero;

        if (RevertToSelf())
        {
            if (LogonUserA(userName, domain, password, LOGON32_LOGON_INTERACTIVE,
                LOGON32_PROVIDER_DEFAULT, ref token) != 0)
            {
                if (DuplicateToken(token, 2, ref tokenDuplicate) != 0)
                {
                    tempWindowsIdentity = new WindowsIdentity(tokenDuplicate);
                    impersonationContext = tempWindowsIdentity.Impersonate();
                    if (impersonationContext != null)
                    {
                        CloseHandle(token);
                        CloseHandle(tokenDuplicate);
                        return true;
                    }
                }
            }
        }
        if (token != IntPtr.Zero)
        {
            CloseHandle(token);
        }
        if (tokenDuplicate != IntPtr.Zero)
        {
            CloseHandle(tokenDuplicate);
        }
        return false;
    }

    private static void undoImpersonation()
    {
        impersonationContext.Undo();
    }
}

}


也许可以尝试一下:在 impersonateValidUser(...) 之前实例化您的 driver - ddavison
谢谢你的想法,但恐怕它不能解决问题。 - Dan
也许可以尝试通过 Windows 命令行使用 runas /user:USER@DOMAIN path\to\mstest.exe ... 启动测试。 - Greg Burghardt
那可能会起作用,但您需要为每个用户启动一个运行。目前我已经停止研究这个问题,因为我只有两个需要它的测试,并且我可以在几分钟内手动运行它们。 - Dan
有人在Java的上下文中问了一个类似的问题:https://dev59.com/oXXYa4cB1Zd3GeqP3zgC,但它仍然是“未回答”的。 - immutabl
8个回答

5
我们有很多企业客户使用Windows身份验证来面向内部应用程序,我们开始运行许多Selenium测试以进行确认、回归等。
我们将Steven的有用代码重新整理成了一个可重用的类,类似于其他“模拟”帖子,但对我们来说这些帖子都不起作用,因为我们希望测试既可以在开发环境中本地工作,也可以作为Visual Studio团队系统发布过程的一部分部署。
使用uri方法时,在本地工作,而使用Win32本机方法进行模拟却不起作用。
这个方案有效,因此在这里提供示例,演示如何使用Steven的代码重构为Helper。
[TestMethod]
public void ThisApp_WhenAccessedByUnathorizedUser_ShouldDisallowAccess()
{
    string userName = "ThisAppNoAccess";
    string password = "123456";
    string domainName = Environment.MachineName;
    using (new Perkins.Impersonator(userName, domainName, password))
    {
        // - Use Remote Web Driver to hook up the browser driver instance launched manually.
        using (var driver = new RemoteWebDriver(new Uri("http://localhost:9515"), DesiredCapabilities.Chrome()))
        {
            var desiredUri = Helper.Combine(Helper.BaseURL, "/ThisApp/#/appGrid");
            TestContext.WriteLine("desiredUri: {0}", desiredUri);
            driver.Navigate().GoToUrl(desiredUri);
            Helper.WaitForAngular(driver);
            var noPermissionNotificationElement = driver.FindElementByXPath("//div[@ng-show='!vm.authorized']/div/div/div/p");
            var showsNoPermissionNotification = noPermissionNotificationElement.Text.Contains("You do not have permissions to view ThisApp.");
            Assert.AreEqual(true, showsNoPermissionNotification, "The text `You do not have permissions to view ThisApp.` is not being displayed!");
        }
    }
}

辅助类
// Idea from https://dev59.com/wZHea4cB1Zd3GeqPwPbN#34406336
// - Launch the browser driver manually with other user's credentials in background
public class Perkins
{
    public class Impersonator : IDisposable
    {
        Process _driverProcess = null;
        string _driverPath = @"chromedriver.exe";
        /// <summary>
        /// Impersonates the specified user account by launching the selenium server under that account.  Connect to it via RemoteWebDriver and localhost on port 9515.
        /// </summary>
        /// <remarks>
        /// We may later want to enhance this by allowing for different ports, etc.
        /// </remarks>
        /// <param name="userName">Name of the user</param>
        /// <param name="domainName">Name of the domain or computer if using a local account.</param>
        /// <param name="password">The password</param>
        public Impersonator(string userName, string domainName, string password)
        {
            ProcessStartInfo processStartInfo = new ProcessStartInfo(_driverPath);
            processStartInfo.UserName = userName;
            System.Security.SecureString securePassword = new System.Security.SecureString();
            foreach (char c in password)
            {
                securePassword.AppendChar(c);
            }
            processStartInfo.Password = securePassword;
            processStartInfo.Domain = domainName; // this is important, mcollins was getting a 'stub received bad data' without it, even though rglos was not
            processStartInfo.UseShellExecute = false;
            processStartInfo.LoadUserProfile = true; // this seemed to be key, without this, I get Internal Server Error 500
            Thread startThread = new Thread(() =>
            {
                _driverProcess = Process.Start(processStartInfo);
                _driverProcess.WaitForExit();
            })
            { IsBackground = true };
            startThread.Start();
        }
        public void Dispose()
        {
            // - Remember to close/exit/terminate the driver process and browser instance when you are done.
            if (_driverProcess != null)
            {
                // Free managed resources
                if (!_driverProcess.HasExited)
                {
                    _driverProcess.CloseMainWindow();
                    _driverProcess.WaitForExit(5000);
                    // Kill the process if the process still alive after the wait
                    if (!_driverProcess.HasExited)
                    {
                        _driverProcess.Kill();
                    }
                    _driverProcess.Close();
                }
                _driverProcess.Dispose();
                _driverProcess = null;
            }
        }
    }
}

也许这会帮助其他遇到相同问题的人。

谢谢这门课程,它真的帮助我弄清楚了我想做什么。但是我在登录信息方面遇到了麻烦,并在这里发布了一个帖子:https://stackoverflow.com/questions/47687030/selenium-user-impersonation-failure你知道我该如何解决这个问题或者我做错了什么吗?即使使用您未修改的代码,我也会收到相同的错误。 - Rescis
这段代码的逻辑非常清晰:首先,使用模拟器启动Chrome驱动程序,然后通过Json Wire协议和RemoteWebDriver连接到驱动程序。非常聪明,干得好! - Richard

3

事实上这是可以实现的。我也曾经遇到过你所提到的问题。基本上,以下是需要执行的步骤。

  1. Launch the browser driver manually with other user's credentials in background

    Process driverProcess;
    string driverPath; // The path to Selenium's IE driver.
    ProcessStartInfo info = new ProcessStartInfo(driverPath)
    {
        UserName = "UserName", // The user name.
        Password = new SecureString(), // The password for the user.
        UseShellExecute = false,
        LoadUserProfile = true,
        Arguments = "about:blank"
    };
    // Start the driver in background thread
    Thread startThread = new Thread(
        () => {
            try
            {
                driverProcess = Process.Start(info);
                driverProcess.WaitForExit();
            }
            catch
            {
                // Close the process.
            }
        })
    {
        IsBackground = true
    };
    startThread.Start();
    
  2. Use Remote Web Driver to hook up the browser driver instance launched manually.

    var remoteDriver = new RemoteWebDriver(Uri("http://localhost:5555"), DesiredCapabilities.InternetExplorer());
    
  3. Remember to close/exit/terminate the driver process and browser instance when you are done.

    // Close the process when done.
    if (driverProcess != null)
    {
        // Free managed resources
        if (!driverProcess.HasExited)
        {
            driverProcess.CloseMainWindow();
            driverProcess.WaitForExit(5000);
            // Kill the process if the process still alive after the wait
            if (!driverProcess.HasExited)
            {
                driverProcess.Kill();
            }
    
            driverProcess.Close();
        }
    
        driverProcess.Dispose();
        driverProcess = null;
    }
    

第一步缺少手动启动过程的详细说明 - 具体来说,是info对象。 - Rick Glos
@RickGlos 我更新了第一步,包括我如何获取 info 对象。 - Steven
谢谢,有了这个额外的清晰信息,我成功地让它工作了。虽然我们是用Chrome,但似乎只需更改driverPath以使用chromedriver而不是IEDriverServer就可以了。我们还需要反复重用它,所以我很快就会发布一个答案,我们现在在许多方法中使用它来构建。再次感谢! - Rick Glos

0

这个类似的问题链接到了这个微软支持文章。基本上你需要

System.Security.Principal.WindowsImpersonationContext impersonationContext;
impersonationContext = 
((System.Security.Principal.WindowsIdentity)User.Identity).Impersonate();
IWebDriver webDriver = new InternetExplorerDriver();
// do your stuff here.
impersonationContext.Undo();

关于模拟特定用户的支持文章中,有额外的代码。


1
我认为那是丹已经在使用的代码。那是他在问题中链接的同一篇知识库文章。 - Rup
1
模拟身份验证可以使用,但是对我来说,Internet Explorer用户没有更改。 - ColacX

0
在进行需要窗口身份验证的基于Web的应用程序自动化项目时,我遇到了同样的问题。然而,使用Firefox我已经解决了这个问题,以下是实现此目标的步骤。
火狐设置
  1. 打开系统的运行对话框,输入'firefox.exe -p'(在运行此命令之前关闭Firefox浏览器)http://www.wikihow.com/Create-a-Firefox-Profile
  2. 点击创建配置文件,并按要求给出一个名称
  3. 选择已创建的配置文件,启动浏览器并打开附加组件管理器(工具 - 附加组件)
  4. 搜索'AutoAuth'并安装它。它会要求重新启动,请执行
  5. 一旦Firefox重新启动,然后打开URL,它会要求您进行身份验证
  6. 输入用户名和密码 - 提交,Firefox会询问是否记住密码
  7. 点击记住,它将保存密码在Firefox配置文件中
  8. 复制创建的Firefox配置文件并保存到所需的文件夹中
  9. 在您的Selenium脚本中使用Firefox驱动程序调用上述创建的配置文件,并传递相同的URL,它将不会要求身份验证对话框

这在我的项目中非常成功地工作。


0

0

这可能有/可能没有用。

  • 尝试在“CHROME”中启动您的网站。
  • 按F-12,转到应用程序选项卡 -> cookies -> 单击您的网站链接,在左侧查找代表用户会话的类似JSESSIONID的内容,复制它。
  • 现在打开您的Internet Explorer,
  • 按F-12并手动创建JSESSIONID(或类似键),通过在控制台窗口中运行此命令:

document.cookie = "JSESSIONID=your-session-id-from-chrome"

  • 点击播放按钮执行脚本
  • 刷新浏览器

0

你有几台旧电脑吗?或者有一些虚拟机的容量吗?

如果是这样,可以建立一个Selenium Grid设置,并配置一个自动登录为所需域用户,另一个则为非域用户。
http://code.google.com/p/selenium/wiki/Grid2


0

看起来问题与NTLM自动登录有关。请参见使用Windows验证的Google Chrome和NTLM自动登录

上面的解决方案对我不起作用,因为自动登录可以成功地验证系统上的任何用户,所以无论我使用哪个用户进行模拟,都没有关系。

但是,我发现您可以通过将localhost替换为任何其他域名(例如本地IP地址)来欺骗自动登录。无需模拟 :)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接