C#中HttpListener忽略请求#后内容,如何获取OAuth的access_token?
如何在C# HttpListener中获取OAuth隐式流的access_token(Hash片段)?
我是C#新手,正在搭建处理StackOverflow OAuth隐式流重定向的服务器,构造的重定向URL是localhost:8080/#access_token=12345。
我写了一段简单的服务器代码来处理请求并保存access_token:
public class ImplicitFlowHttpServer { public int Port = 8080; private HttpListener _listener; public void Start() { var prefix = $"http://*:8080/"; _listener = new HttpListener(); _listener.Prefixes.Add(prefix); _listener.Start(); _listener.BeginGetContext(new AsyncCallback(ListenerCallback), _listener); } private async void ListenerCallback(IAsyncResult result) { if (_listener.IsListening) { string oauthCode; var context = _listener.EndGetContext(result); var request = context.Request; // 发送响应的代码,和问题无关 HttpListenerResponse response = context.Response; byte[] page = Encoding.ASCII.GetBytes( "You can close this browser tab now."); response.ContentLength64 = page.Length; Stream output = response.OutputStream; output.Write(page, 0, page.Length); output.Close(); // 调试输出 Console.WriteLine($"应该收到请求:localhost:8080/#access_token=<token>"); Console.WriteLine($"怎么才能拿到access_token?"); Console.WriteLine($"request.Url: {request.Url}"); Console.WriteLine($"request.Url.Fragment: {request.Url.Fragment}"); Console.WriteLine($"request.RawUrl: {request.RawUrl}"); Console.WriteLine($"request.Url.AbsoluteUri: {request.Url.AbsoluteUri}"); Console.WriteLine($"request.Url.OriginalString: {request.Url.OriginalString}"); // 这里本来是解析access_token的代码,但现在拿不到内容 // 这里是停止服务器的代码 } } }
我原本期望在request对象里找到#后面的内容,但发现这部分完全被清空了。哪怕直接用curl测试curl "localhost:8080/?q=123&hello=world\#access_token=123456",请求对象的相关属性也不包含Fragment部分:
request.Url: http://localhost:8080/?q=123&hello=world%5C request.Url.Fragment: request.RawUrl: /?q=123&hello=world\ request.Url.AbsoluteUri: http://localhost:8080/?q=123&hello=world%5C request.Url.OriginalString: http://localhost:8080/?q=123&hello=world%5C
因为StackOverflow的access_token只能放在Hash里,请问我该怎么从重定向中获取access_token?
解决方法
这不是C#或者HttpListener的问题,而是HTTP协议的特性:浏览器在发送请求时,不会把URL的Hash(#后面的部分)发送给服务器。Hash部分只在浏览器端可见,用来做页面内锚点或者前端路由,不会被包含在HTTP请求头里。
所以要获取Hash里的access_token,必须通过前端JavaScript来处理,再把数据传给服务器:
- 修改服务器返回的响应页面,加入JS代码读取URL的Hash
- 把Hash中的access_token通过AJAX POST或者表单提交回服务器
- 服务器接收并保存这个token
修改后的ListenerCallback中响应部分的代码示例:
// 修改响应内容,加入处理Hash的JS代码 string responseHtml = @" <!DOCTYPE html> <html> <head> <script> // 读取URL Hash部分 const hash = window.location.hash.substring(1); // 去掉# const params = new URLSearchParams(hash); const accessToken = params.get('access_token'); if (accessToken) { // 通过AJAX把token传给服务器 fetch('/save-token', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: `access_token=${accessToken}` }).then(() => { document.body.innerHTML = 'Token已保存,可以关闭此标签页。'; }); } else { document.body.innerHTML = '未获取到Token,可以关闭此标签页。'; } </script> </head> <body>正在处理Token...</body> </html>"; byte[] page = Encoding.UTF8.GetBytes(responseHtml); response.ContentLength64 = page.Length; response.ContentType = "text/html; charset=utf-8"; Stream output = response.OutputStream; output.Write(page, 0, page.Length); output.Close();
然后需要在服务器中添加处理/save-token路径的逻辑,接收POST过来的access_token:
// 在ListenerCallback中添加判断,处理/save-token请求 if (request.Url.AbsolutePath == "/save-token" && request.HttpMethod == "POST") { using (var reader = new StreamReader(request.InputStream, request.ContentEncoding)) { string body = reader.ReadToEnd(); var paramsDict = new Dictionary<string, string>(); foreach (var pair in body.Split('&')) { var keyValue = pair.Split('='); if (keyValue.Length == 2) { paramsDict[keyValue[0]] = Uri.UnescapeDataString(keyValue[1]); } } if (paramsDict.TryGetValue("access_token", out string token)) { Console.WriteLine($"获取到的access_token:{token}"); // 这里添加保存token的逻辑 } } // 返回成功响应 response.StatusCode = 200; byte[] successBytes = Encoding.UTF8.GetBytes("OK"); response.ContentLength64 = successBytes.Length; Stream successOutput = response.OutputStream; successOutput.Write(successBytes, 0, successBytes.Length); successOutput.Close(); return; // 跳过后续的调试输出逻辑 }
这样就能正常获取到StackOverflow OAuth隐式流返回的access_token了。
内容的提问来源于stack exchange,提问作者emmagras
相关产品推荐
相关产品推荐

