C# Ajax Get Request 返回 401,Python 请求正常
结论
401 Unauthorized 说明服务器没有识别到有效的登录会话,并不是 Ajax 请求格式有误。
Python 代码调用的是 session.get(...)。如果这个 session 之前完成过登录、访问过相关页面,或者导入过浏览器 Cookie,发送请求时就会自动带上认证 Cookie。C# 示例只添加了 X-Requested-With,没有复用已经登录的会话。因此,即使复制了 User-Agent、Referer 等请求头,服务器仍会返回 401。
X-Requested-With: XMLHttpRequest 只能表示请求通常由前端脚本发起,不能用来证明用户身份。
正确处理方式
在 C# 中,应长期复用同一个 HttpClient 和 CookieContainer:
- 按照服务支持的登录流程完成认证。
- 使用
CookieContainer保存登录响应中的 Cookie。 - 使用同一个
HttpClient访问账户页面和订单历史接口。 - 如果接口要求 CSRF Token,需要从登录响应、Cookie 或页面中取得 Token,并按接口要求发送。
- 使用开发者工具对比 Python 请求和浏览器请求,重点检查
Cookie、重定向及认证相关请求头,不要只比较普通 Header。
C# 示例
下面的代码演示如何保存和复用 Cookie。登录部分需要根据服务当前支持的认证流程实现,仅补充请求头无法代替登录。
using System;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
public static class EpicOrderHistoryClient
{
public static async Task<string> GetOrderHistoryAsync()
{
var cookies = new CookieContainer();
using var handler = new HttpClientHandler
{
CookieContainer = cookies,
UseCookies = true,
AllowAutoRedirect = true,
AutomaticDecompression =
DecompressionMethods.GZip |
DecompressionMethods.Deflate |
DecompressionMethods.Brotli
};
using var client = new HttpClient(handler)
{
Timeout = TimeSpan.FromSeconds(10)
};
client.DefaultRequestHeaders.UserAgent.ParseAdd(
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) " +
"AppleWebKit/537.36 (KHTML, like Gecko) " +
"Chrome/123.0.0.0 Safari/537.36");
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
// 必须先通过服务支持的方式完成登录,并继续使用这个 client。
await AuthenticateAsync(client);
using var request = new HttpRequestMessage(
HttpMethod.Get,
"https://www.epicgames.com/payment/ajaxGetOrderHistory");
request.Headers.TryAddWithoutValidation(
"X-Requested-With",
"XMLHttpRequest");
request.Headers.Referrer = new Uri(
"https://www.epicgames.com/account/payment/history");
using HttpResponseMessage response =
await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
string body = await response.Content.ReadAsStringAsync();
if (response.StatusCode == HttpStatusCode.Unauthorized)
{
throw new InvalidOperationException(
"服务器返回 401:登录会话无效、已过期,或认证 Cookie/Token 未随请求发送。");
}
response.EnsureSuccessStatusCode();
return body;
}
private static Task AuthenticateAsync(HttpClient client)
{
// 按服务当前支持的认证流程实现。
// 登录请求和后续接口请求必须使用同一个 HttpClient/CookieContainer。
throw new NotImplementedException();
}
}
如果已经通过合规方式取得当前会话的 Cookie,也可以将其加入 CookieContainer。Cookie 的名称和值应以实际登录响应为准:
var cookies = new CookieContainer();
cookies.Add(
new Uri("https://www.epicgames.com"),
new Cookie("actual_cookie_name", "actual_cookie_value")
{
Secure = true,
HttpOnly = true,
Path = "/"
});
不要将账号 Cookie 写死在源码中,也不要把它记录到日志或提交到公开仓库。Cookie 通常会过期,还可能与设备、地区、CSRF Token 或其他会话状态绑定,所以只复制一个 Cookie 未必能完成认证。
为什么复制 Python Headers 没有效果
Python 请求中真正起作用的很可能不是这些显式设置的 Header,而是 session 内部保存的状态,例如:
Cookie: ...
Authorization: ...
X-CSRF-Token: ...
接口具体采用哪种认证机制,取决于当前实现。常见请求头的作用如下:
User-Agent:描述客户端,不能证明用户已经登录。Referer:表示请求来源,不能代替身份认证。X-Requested-With:常用于识别 Ajax 请求,不属于身份凭据。Cache-Control、Pragma:用于控制缓存,与登录权限无关。Host:HttpClient会根据 URL 自动生成,通常不需要手动设置。Connection、Keep-Alive:由 HTTP 客户端管理,不需要手动复制。
如何确认缺少了什么
可以在发送 Python 请求前检查 Session 中是否已经保存了 Cookie:
print(sesion.cookies.get_dict())
也可以打开浏览器开发者工具的 Network 面板,找到能够成功返回的请求,重点检查以下内容:
- 请求是否携带认证 Cookie;
- 发送请求前是否完成过登录或 Token 刷新;
- 是否包含 CSRF Token;
- 请求过程中是否发生了
302或303重定向; - Python 是否通过代理使用了不同的网络出口;
- C# 是否为每次请求重新创建客户端,导致 Cookie 丢失。
调试 C# 时,不要一开始就调用 EnsureSuccessStatusCode()。先记录状态码、响应内容、响应头和最终请求地址。服务器有时会在响应体或 WWW-Authenticate 中说明更具体的原因。
注意事项
该地址属于账户支付历史接口,通常需要用户身份认证。接口路径、认证方式和防自动化策略可能随时变化。如果服务提供正式 API,应优先使用正式 API 和受支持的授权流程。
不要伪造、窃取或绕过他人的会话凭据。即使操作自己的账户,也应遵守服务条款,并妥善保护登录 Cookie、访问令牌和支付历史数据。
备注:内容仅供参考。
