HttpClient不会从CookieContainer发送Cookie

时间:2013-07-04 16:05:03

标签: asp.net cookies asp.net-web-api

我正在使用Visual Studio 2012开发带有WPF(.NET 4.0)客户端的ASP WebAPI(ASP MVC 4)应用程序。客户端需要登录到服务器。我使用FormsAuthentication和身份验证cookie登录。登录在ASP MVC中已经正常工作。

问题在于,尽管登录在服务器上成功执行并且cookie被发送回客户端,但是在后续调用服务器时不会发送cookie,即使CookieContainer被重用auth cookie set。

以下是代码的简化版本:

客户端

public async Task<UserProfile> Login(string userName, string password, bool rememberMe)
{           
    using (var handler = new HttpClientHandler() { CookieContainer = this.cookieContainer })
    using (var httpClient = new HttpClient(handler))
    {
        httpClient.BaseAddress = new Uri("http://localhost:50000/");

        httpClient.DefaultRequestHeaders.Accept.Add(
            new MediaTypeWithQualityHeaderValue("application/json"));

        var result = await httpClient.PostAsJsonAsync("api/auth/login", new
        {
            username = userName,
            password = password,
            rememberMe = rememberMe
        });

        result.EnsureSuccessStatusCode();

        var userProfile = await result.Content.ReadAsAsync<UserProfile>();

        if (userProfile == null)
            throw new UnauthorizedAccessException();

        return userProfile;
    }
}

public async Task<ExamSubmissionResponse> PostItem(Item item)
{
    using (var handler = new HttpClientHandler() { CookieContainer = this.cookieContainer })
    using (var httpClient = new HttpClient(handler))
    {
        httpClient.BaseAddress = new Uri("http://localhost:50000/");

        var result = await httpClient.PostAsJsonAsync("api/Items/", item);
    }
}

服务器

[HttpPost]
public HttpResponseMessage Login(LoginModel model)
{
    if (this.ValidateUser(model.UserName, model.Password))
    {
        // Get user data from database

        string userData = JsonConvert.SerializeObject(userModel);

        var authTicket = new FormsAuthenticationTicket(
            1,
            model.UserName,
            DateTime.Now,
            DateTime.Now.AddMinutes(10 * 15),
            model.RememberMe,
            userData
        );              

        string ticket = FormsAuthentication.Encrypt(authTicket);
        var cookie = new CookieHeaderValue(FormsAuthentication.FormsCookieName, ticket);
        var response = Request.CreateResponse(HttpStatusCode.Created, userModel);
        response.Headers.AddCookies(new CookieHeaderValue[] { cookie });

        return response;
    }
    return null;
}

首先我使用Fiddler2调试了问题(我使用基地址作为“http://localhost.fiddler:50000/”来查看本地流量)。然后我怀疑小提琴手可能会干扰,所以我只是用Visual Studio 2012调试。

我尝试并验证过:

  • 登录方法

  • 到达服务器
  • 使用客户端发送的数据

  • 对用户进行了成功验证
  • Cookie已在服务器上设置

  • Cookie在响应中(通过fiddler验证)

  • 操作后cookie位于CookieContainer中。这里有一个奇怪的事情:容器中cookie的域名设置为“localhost”(使用VS2012调试器验证)。 不应该是“http://localhost:50000吗?当我尝试使用cookieContainer.GetCookies(new Uri("http://localhost:50000"))获取容器的cookie时,它什么都不返回。当我使用cookieContainer.GetCookies(new Uri("localhost"))尝试它时,它会给我一个无效的Uri错误。不确定这里发生了什么。

  • PostItem请求发出之前,cookie位于容器中。当达到语句httpClient.PostAsJsonAsync时,容器在HttpClient中正确设置。

  • cookie未发送到服务器(我使用fiddler和Global.asax.cs中的Application_PostAuthenticateRequest方法检查它,验证this.Request.Cookies

我怀疑由于CookieContainer中的域名不匹配而未发送Cookie,但是为什么域名未在CookieContainer中首先设置?

1 个答案:

答案 0 :(得分:5)

您的问题是您没有在从Web Api控制器发回的cookie上设置任何路径。

有两件事可以控制发送cookie的位置:

  1. Cookie的域名
  2. Cookie的路径
  3. 关于域名,共识似乎是端口号不再(但仍可能)是评估cookie域的一个因素。有关端口号如何影响域的详细信息,请参阅this question

    关于路径:Cookie与其域中的特定路径相关联。在您的情况下,Web Api发送cookie而不指定其路径。默认情况下,cookie将与创建cookie的请求/响应的路径相关联。

    在您的情况下,Cookie将具有路径api/auth/login。这意味着cookie将被发送到此路径的子路径(缺少更好的术语),但不会发送到兄弟路径

    要测试此功能,请尝试:

    cookieContainer.GetCookies(new Uri("http://localhost/api/auth/login")
    

    这应该给你cookie。所以应该这样:

    cookieContainer.GetCookies(new Uri("http://localhost/api/auth/login/foo/bar")
    

    另一方面,这些将找不到cookie:

    cookieContainer.GetCookies(new Uri("http://localhost/")
    cookieContainer.GetCookies(new Uri("http://localhost/api/")
    cookieContainer.GetCookies(new Uri("http://localhost/api/auth/")
    cookieContainer.GetCookies(new Uri("http://localhost/api/auth/foo")
    cookieContainer.GetCookies(new Uri("http://localhost/api/Items/")
    

    要解决此问题,只需在发送声明之前将路径“/”(或可能是“/ api”)添加到cookie中:

    ...
    string ticket = FormsAuthentication.Encrypt(authTicket);
    var cookie = new CookieHeaderValue(FormsAuthentication.FormsCookieName, ticket);
    cookie.Path = "/";
    var response = Request.CreateResponse(HttpStatusCode.Created, userModel);
    response.Headers.AddCookies(new CookieHeaderValue[] { cookie });
    ...