【问题标题】:TestServer and HttpClient get BadRequest for antiforgery token in .net core web apiTestServer 和 HttpClient 在 .net 核心 web api 中获取防伪令牌的 BadRequest
【发布时间】:2023-03-07 17:45:01
【问题描述】:

我在从 xUnit 测试项目调用默认端点“/api/values”时遇到问题。 Web api 是默认的 .net 核心项目。我总是收到错误的请求 - 400,即使我在每个请求上添加带有 AF cookie 值的标头。

首先我在 Startup 类中设置防伪。

public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc(
             opt =>
             {
                 opt.Filters.Add(new ValidateAntiForgeryTokenAttribute());
             }
            ).SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

        services.AddAntiforgery(options =>
        {
            options.HeaderName = "X-XSRF-TOKEN";
        });
    }

然后添加单独的控制器和动作来创建 AF cookie

    [IgnoreAntiforgeryToken]
    [AllowAnonymous]
    [HttpGet("antiforgery")]  
    public IActionResult GenerateAntiForgeryTokens()
    {
        //generate the tokens/cookie values
        //it modifies the response so that the Set-Cookie statement is added to it (that’s why it needs HttpContext as an argument).
        var tokens = _antiForgery.GetAndStoreTokens(HttpContext);
        Response.Cookies.Append("XSRF-REQUEST-TOKEN", tokens.RequestToken, new CookieOptions
        {
            HttpOnly = false,

        });

        return NoContent();
    }

然后我设置测试类

 public UnitTest1()
    {
        _server = new TestServer(
          WebHost.CreateDefaultBuilder()
           .ConfigureAppConfiguration((builderContext, config) =>
           {
               config.AddJsonFile("appsettings.test.json", optional: false, reloadOnChange: true);
           })
          .UseStartup<Startup>()
          .UseEnvironment("Development")
          );
        _client = _server.CreateClient();
        _client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
    }

并在测试类中添加方法以从 AF cookie 中获取 AF 标头的值

  protected async Task<string> EnsureAntiforgeryToken()
    {
        string _antiforgeryToken = string.Empty;

            var response = await _client.GetAsync("/api/AntiForgery/antiforgery");
            response.EnsureSuccessStatusCode();
            if (response.Headers.TryGetValues("Set-Cookie", out IEnumerable<string> values))
            {
                var _antiforgeryCookie = Microsoft.Net.Http.Headers.SetCookieHeaderValue.ParseList(values.ToList()).SingleOrDefault(c => c.Name.StartsWith(XSRF_TOKEN, StringComparison.InvariantCultureIgnoreCase));
                _antiforgeryToken = _antiforgeryCookie.Value.ToString();
            }

        return await Task.FromResult<string>(_antiforgeryToken);
    }

在我的测试方法中我尝试调用端点

[Fact]
    public async Task Test1Async()
    {
        _antiforgeryCookie = await EnsureAntiforgeryToken();
        _client.DefaultRequestHeaders.Add("X-XSRF-TOKEN", _antiforgeryCookie);
        var result = await _client.GetAsync("/api/values"); //always get error 400
        Assert.True(true, "");
    }

【问题讨论】:

    标签: httpclient antiforgerytoken bad-request


    【解决方案1】:

    发生的情况是,浏览器或调试工具(如邮递员)中的 cookie 在收到时会自动存储,然后与每个后续请求一起发送到具有相同域的 URL。当您尝试在代码中编写和发出请求时,情况并非如此。

    因此,您需要将 cookie作为 cookie 添加到通过防伪验证到达端点的请求中。

    当您获得带有 XSRF 令牌的响应时,您将拥有从令牌生成响应中检索的 cookies 数组:

    response.Headers.TryGetValues("Set-Cookie", out IEnumerable<string> values)
    

    您还解析XSRF-TOKEN cookie 以获取其值,这很棒。但是您还需要两个未解析的 cookie。

    所以,你可以介绍一下:

    public class AntiForgeryToken
    {
        public string XsrfToken { get; set; }
        public string[] Cookies { get; set; }
    }
    

    并修改EnsureAntiforgeryToken 以填充它:

    protected async Task<AntiForgeryToken> EnsureAntiforgeryToken()
    {
        var antiForgerytoken = new AntiForgeryToken();
    
        var response = await _client.GetAsync("/api/AntiForgery/antiforgery");
        response.EnsureSuccessStatusCode();
    
        if (response.Headers.TryGetValues("Set-Cookie", out IEnumerable<string> values))
        {
            var cookies = SetCookieHeaderValue.ParseList(values.ToList());
    
            var _antiforgeryCookie = cookies.SingleOrDefault(c =>
                c.Name.StartsWith(XSRF_TOKEN, StringComparison.OrdinalIgnoreCase));
    
            // Value of XSRF token cookie
            antiForgerytoken.XsrfToken = _antiforgeryCookie.Value.ToString();
    
            // and the cookies unparsed (both XSRF-TOKEN and .AspNetCore.Antiforgery.{someId})
            antiForgerytoken.Cookies = values.ToArray();
        }
    
        return antiForgerytoken;
    }
    

    除了随响应返回的 cookies 数组外,我们还会返回您已经在解析的 XSRF 令牌。 cookie 是带有它们的值的字符串以及使它们成为 cookie 的所有元数据。

    然后,您将X-XSRF-TOKEN 标头两个 cookie 添加到您的 HttpClient,如下所示:

    public async Task Test1Async()
    {
        _antiForgeryToken = await EnsureAntiforgeryToken();
    
        // the bit you have and will still need
        _client.DefaultRequestHeaders.Add("X-XSRF-TOKEN", _antiForgeryToken.XsrfToken);
    
        // the bit you're missing
        _client.DefaultRequestHeaders.Add("Cookie", _antiForgeryToken.Cookies[0]);
        _client.DefaultRequestHeaders.Add("Cookie", _antiForgeryToken.Cookies[1]);
    
        var result = await _client.GetAsync("/api/values"); // no more 400
    
        Assert.True(result.IsSuccessStatusCode);
    }
    

    这最终模仿了浏览器或调试工具的行为,其中接收到的 cookie 被存储并随着每个请求自动发送回与接收到它的域相同的 URL。

    【讨论】:

      猜你喜欢
      • 2020-06-22
      • 1970-01-01
      • 2014-10-03
      • 1970-01-01
      • 1970-01-01
      • 2019-04-26
      • 1970-01-01
      • 2017-12-29
      • 2012-01-22
      相关资源
      最近更新 更多