HttpListener访问被拒绝。

225

我正在用C#编写一个HTTP服务器。

当我尝试执行函数HttpListener.Start()时,会出现HttpListenerException,错误显示为:

"拒绝访问"

在Windows 7中以管理员模式运行应用程序时,它可以正常工作。

我能否使其在非管理员模式下运行?如果可以,该怎么做? 如果不行,我该如何在启动后将应用程序切换到管理员模式?

using System;
using System.Net;

namespace ConsoleApplication1
{
    class Program
    {
        private HttpListener httpListener = null;

        static void Main(string[] args)
        {
            Program p = new Program();
            p.Server();
        }

        public void Server()
        {
            this.httpListener = new HttpListener();

            if (httpListener.IsListening)
                throw new InvalidOperationException("Server is currently running.");

            httpListener.Prefixes.Clear();
            httpListener.Prefixes.Add("http://*:4444/");

            try
            {
                httpListener.Start(); //Throws Exception
            }
            catch (HttpListenerException ex)
            {
                if (ex.Message.Contains("Access is denied"))
                {
                    return;
                }
                else
                {
                    throw;
                }
            }
        }
    }
}

如果有人想避免这个错误,可以尝试使用TcpListener编写它。它不需要管理员权限。 - Vlad
我遇到了同样的问题,在Visual Studio 2008 + Windows 7中,它会产生“访问被拒绝”的错误,解决方法是以管理员模式运行Visual Studio 2008。 - Mark Khor
1
这是什么疯狂?我从未见过在任何其他语言或操作系统中,这些 hoops 打开非特权端口。我甚至不知道他们试图实现什么。 - Michael Mrozek
12个回答

359

是的,您可以在非管理员模式下运行HttpListener。您需要做的就是授予特定URL的权限。例如:

netsh http add urlacl url=http://+:80/MyUri user=DOMAIN\user

文档可以在这里找到。


58
这很有帮助,但为了完整起见,代码中这一行指定的URL:httpListener.Prefixes.Add("http://*:4444/");必须与netsh命令中的URL完全匹配。例如,我使用了httpListener.Prefixes.Add("http://127.0.0.1:80/");和相同的netsh命令,但仍会抛出HttpListenerException异常。我需要将其更改为httpListener.Prefixes.Add("http://+:80/");感谢@Darrel Miller的帮助,因为你让我找到了正确解决方法的方向! - psyklopz
13
如果"MyUri"为空,请不要忘记添加尾部斜杠,否则会出现“参数不正确”的错误。例如:url=http://+:80/ - Igor Brejc
16
即使对于 http://localhost:80/ 这样的 URL,是否有任何方法可以让非管理员用户执行这个操作?我有一个桌面应用程序需要在这样的 URL 上接收一个请求,要求管理员在50台桌面上安装它仅为了这个目的似乎有些浪费。 - John Saunders
7
显然不是这样。文档页面也没有提到需要高程或管理员特权。因此,很容易假设它将作为“更智能”的TCPListener,但实际上有一个重大原因不使用它 - 然而这并没有被记录在案。 - David Ford
4
@DarrelMiller 这并不能阻止恶意软件开启本地服务器。您仍然可以创建一个HTTP服务器,只是无法使用HTTP.sys或HttpListener实现,例如可以调用TcpListener.Create(4444).Start(),然后在其上实现HTTP/1.1协议。所以我不明白为什么会有这个(似乎未记录的)权限检查存在。 - Qwertie
显示剩余6条评论

35
如果您使用 http://localhost:80/ 作为前缀,您就可以无需管理员权限来监听 http 请求。

3
你能发一些示例代码吗?我刚刚尝试使用 http://localhost:80/,但是出现了“拒绝访问”的错误。 - John Saunders
2
抱歉,似乎无法正常工作。请检查您是否以管理员身份运行,这不应该是正常情况。 - Jonno
如果这个能够工作,我会认为这是一个Windows的漏洞。无论你对Windows有什么看法,我认为这是非常基础的问题,他们极有可能没有正确地处理好。 - hoijui
40
两年多以后,我现在在安装了.NET framework 4.5的Windows Server 2008 R2上可以使用。httpListener.Prefixes.Add("http://*:4444/"); 会显示“访问被拒绝”的错误,但是 httpListener.Prefixes.Add("http://localhost:4444/"); 可以正常工作。这似乎是因为Microsoft在限制中排除了“localhost”。有趣的是,httpListener.Prefixes.Add("http://127.0.0.1:4444/");仍然会显示访问被拒绝的错误,所以唯一可行的方法是localhost:{任意端口} - Tony
3
@Tony 谢谢你的评论,对我非常有价值。我的一些配置文件中都有"127.0.0.1"这个地址。测试人员反馈在Windows 8.1上无法工作,但在Windows 10上却没有问题(我自己在Win10上测试过)。奇怪的是,在Win10中,Microsoft似乎已经授权Everyone使用"127.0.0.1",但在8.1(以及之前的版本)中没有授权,不过使用localhost则没问题。感谢。 - Adam Plocher
显示剩余4条评论

31
我可以吗不使用管理员模式运行?如果可以,怎么做?如果不行,如何在程序开始运行后将应用程序更改为管理员模式?
你不能这样做,它必须以提升的权限启动。您可以使用“runas”动词重新启动它,这将提示用户切换到管理员模式。
static void RestartAsAdmin()
{
    var startInfo = new ProcessStartInfo("yourApp.exe") { Verb = "runas" };
    Process.Start(startInfo);
    Environment.Exit(0);
}

编辑:实际上,那不是真的;HttpListener可以在没有提升权限的情况下运行,但你需要为你想要监听的URL授予权限。更多详情请参见Darrel Miller的答案


在“Process.Start(startInfo);”之前,您还需要添加此行代码:“startInfo.UseShellExecute = false;”。 - Randall Flagg
@Randall:因为这就是Windows的工作方式……一个进程在运行时无法切换到管理员模式。关于UseShellExecute:它取决于你要执行什么。我用“notepad.exe”测试了我的代码,如果不使用UseShellExecute = false,它可以正常工作。 - Thomas Levesque
关于UseShellExecute:我尝试运行我发布的代码。另一个问题是,由于某种原因,它会问我一次是否要以管理员身份运行,之后每次都不会再问了。我重新启动了,进行了调试以确保它到达那里,但仍然没有解决。有什么建议吗?谢谢。 - Randall Flagg
不是这样的...也许第二次你已经以管理员身份运行它了? - Thomas Levesque
1
@Thomas 在非管理员模式下运行HttpListener没有问题。 - Darrel Miller
显示剩余2条评论

26

对我来说,语法是错误的,你必须包含引号:

netsh http add urlacl url="http://+:4200/" user=everyone

否则我收到了“参数不正确”的错误信息。


6
如果你在URL结尾没有指定斜杠“/”,也可能会返回“参数不正确”的提示信息。请注意,翻译后的内容保持原意、通俗易懂,且不要添加解释或其他额外内容。 - LostSalad

18
作为一种不需要提升权限或使用netsh的替代方案,您也可以使用TcpListener。
以下是此示例的修改摘录: https://github.com/googlesamples/oauth-apps-for-windows/tree/master/OAuthDesktopApp
// Generates state and PKCE values.
string state = randomDataBase64url(32);
string code_verifier = randomDataBase64url(32);
string code_challenge = base64urlencodeNoPadding(sha256(code_verifier));
const string code_challenge_method = "S256";

// Creates a redirect URI using an available port on the loopback address.
var listener = new TcpListener(IPAddress.Loopback, 0);
listener.Start();
string redirectURI = string.Format("http://{0}:{1}/", IPAddress.Loopback, ((IPEndPoint)listener.LocalEndpoint).Port);
output("redirect URI: " + redirectURI);

// Creates the OAuth 2.0 authorization request.
string authorizationRequest = string.Format("{0}?response_type=code&scope=openid%20profile&redirect_uri={1}&client_id={2}&state={3}&code_challenge={4}&code_challenge_method={5}",
    authorizationEndpoint,
    System.Uri.EscapeDataString(redirectURI),
    clientID,
    state,
    code_challenge,
    code_challenge_method);

// Opens request in the browser.
System.Diagnostics.Process.Start(authorizationRequest);

// Waits for the OAuth authorization response.
var client = await listener.AcceptTcpClientAsync();

// Read response.
var response = ReadString(client);

// Brings this app back to the foreground.
this.Activate();

// Sends an HTTP response to the browser.
WriteStringAsync(client, "<html><head><meta http-equiv='refresh' content='10;url=https://google.com'></head><body>Please close this window and return to the app.</body></html>").ContinueWith(t =>
{
    client.Dispose();
    listener.Stop();

    Console.WriteLine("HTTP server stopped.");
});

// TODO: Check the response here to get the authorization code and verify the code challenge

读取和写入方法如下:
private string ReadString(TcpClient client)
{
    var readBuffer = new byte[client.ReceiveBufferSize];
    string fullServerReply = null;

    using (var inStream = new MemoryStream())
    {
        var stream = client.GetStream();

        while (stream.DataAvailable)
        {
            var numberOfBytesRead = stream.Read(readBuffer, 0, readBuffer.Length);
            if (numberOfBytesRead <= 0)
                break;

            inStream.Write(readBuffer, 0, numberOfBytesRead);
        }

        fullServerReply = Encoding.UTF8.GetString(inStream.ToArray());
    }

    return fullServerReply;
}

private Task WriteStringAsync(TcpClient client, string str)
{
    return Task.Run(() =>
    {
        using (var writer = new StreamWriter(client.GetStream(), new UTF8Encoding(false)))
        {
            writer.Write("HTTP/1.0 200 OK");
            writer.Write(Environment.NewLine);
            writer.Write("Content-Type: text/html; charset=UTF-8");
            writer.Write(Environment.NewLine);
            writer.Write("Content-Length: " + str.Length);
            writer.Write(Environment.NewLine);
            writer.Write(Environment.NewLine);
            writer.Write(str);
        }
    });
}

这真的很有用,因为我们在为IdentityModel.OidcClient库实现IBrowser时遇到了HttpListener问题,所以与上述情况非常相似。 - mackie
1
值得注意的是,上面的代码存在一些错误。首先,使用UTF8的StreamWriter在某些浏览器中会出现问题,我猜测是因为输出了BOM之类的东西。我将其更改为ASCII,一切正常。 我还添加了一些代码来等待数据在流中可读,因为有时它会返回没有数据的情况。 - mackie
谢谢@mackie - 那部分实际上是直接从微软自己的示例中提取的。我猜他们没有真正测试它。如果您可以编辑我的答案,请继续修复方法 - 否则,也许将更改发送给我,我可以更新它。 - Michael Olsen
3
不应使用ASCII,而应使用以下构造函数new UTF8Encoding(false),以禁用BOM的发射。我已经在答案中进行了更改。 - Sebastian
我更喜欢这个解决方案而不是被接受的答案。运行netsh看起来像是一个hack。这是一个可靠的编程解决方案。谢谢! - Jim Gomes

15

如果您想使用标志“user=Everyone”,您需要将它调整为您的系统语言。在英语中,它如下所述:

netsh http add urlacl url=http://+:80/ user=Everyone

在德语中,它会是这样:

netsh http add urlacl url=http://+:80/ user=Jeder

1
在西班牙语系统中,执行以下操作:user=Todos - Rodrigo Rodrigues
此外,正如另一个答案中提到的那样,我需要将URL放在引号中。 - Carsten Führmann
2
在波兰语中,user=wszyscy。 - GigaKatowice

9
默认情况下,Windows定义了以下前缀,对所有人都可用:http://+:80/Temporary_Listen_Addresses/ 因此,您可以通过以下方式注册您的HttpListenerPrefixes.Add("http://+:80/Temporary_Listen_Addresses/" + Guid.NewGuid().ToString("D") + "/"; 这有时会导致与Skype等软件的问题,因为它们默认尝试利用端口80。

1
Skype是罪魁祸首。我将端口更改为80,然后它正常工作了。 - GoTo

8

如果您向项目中添加应用程序清单,就可以作为管理员启动应用程序。

只需向项目添加新项目,并选择“应用程序清单文件”。将<requestedExecutionLevel>元素更改为:

<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />

8

这就是我想要的。使用.Add("http://+:9000/")对我有用。 - John Gietzen

3

我也遇到过类似的问题。如果你已经预留了URL,那么在非管理员模式下运行之前必须先删除URL,否则它将失败并显示“访问被拒绝”错误。

netsh http delete urlacl url=http://+:80

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