本主题介绍如何在 Web API 中发送和接收 HTTP Cookie。
HTTP Cookie 的背景
本部分简要概述了如何在 HTTP 级别实现 Cookie。 有关详细信息,请参阅 RFC 6265。
Cookie 是服务器在 HTTP 响应中发送的一段数据。 客户端(可选)存储 Cookie,并在后续请求中返回它。 这允许客户端和服务器共享状态。 若要设置 Cookie,服务器在响应中包含 Set-Cookie 标头。 Cookie 的格式是名称值对,具有可选属性。 例如:
Set-Cookie: session-id=1234567
下面是具有属性的示例:
Set-Cookie: session-id=1234567; max-age=86400; domain=example.com; path=/;
若要将 Cookie 返回到服务器,客户端在后续请求中包含 Cookie 标头。
Cookie: session-id=1234567
HTTP 响应可以包含多个 Set-Cookie 标头。
Set-Cookie: session-token=abcdef;
Set-Cookie: session-id=1234567;
客户端使用单个 Cookie 标头返回多个 Cookie。
Cookie: session-id=1234567; session-token=abcdef;
Cookie 的范围和持续时间由 Set-Cookie 标头中的以下属性控制:
- 域:告知客户端应接收 Cookie 的域。 例如,如果域为“example.com”,客户端会将 cookie 返回到 example.com 的每个子域。 如果未指定,则域为源服务器。
- 路径:将 Cookie 限制为域中的指定路径。 如果未指定,则使用请求 URI 的路径。
- 过期:设置 Cookie 的到期日期。 客户端在 Cookie 过期时将其删除。
- 最大期限:设置 Cookie 的最大期限。 客户端在达到最大期限时删除 Cookie。
如果同时设置 Expires 和 Max-Age,则 Max-Age 优先。 如果两者均未设置,则客户端在当前会话结束时删除 Cookie。 (“会话”的确切含义由用户代理确定。
但是,请注意,客户端可能会忽略 Cookie。 例如,用户可能会出于隐私原因禁用 Cookie。 客户端可以在 Cookie 过期之前删除 Cookie,或限制存储的 Cookie 数。 出于隐私原因,客户端通常会拒绝“第三方”Cookie,其中域与源服务器不匹配。 简言之,服务器不应依赖于返回的它所设置的 Cookie。
Web API 中的 Cookie
若要将 Cookie 添加到 HTTP 响应,请创建表示 Cookie 的 CookieHeaderValue 实例。 然后调用 System.Net.Http 中定义的 AddCookies 扩展方法 。HttpResponseHeadersExtensions 类,用于添加 Cookie。
例如,以下代码在控制器操作中添加 Cookie:
public HttpResponseMessage Get()
{
var resp = new HttpResponseMessage();
var cookie = new CookieHeaderValue("session-id", "12345");
cookie.Expires = DateTimeOffset.Now.AddDays(1);
cookie.Domain = Request.RequestUri.Host;
cookie.Path = "/";
resp.Headers.AddCookies(new CookieHeaderValue[] { cookie });
return resp;
}
请注意, AddCookies 采用 CookieHeaderValue 实例数组。
若要从客户端请求中提取 Cookie,请调用 GetCookies 方法:
string sessionId = "";
CookieHeaderValue cookie = Request.Headers.GetCookies("session-id").FirstOrDefault();
if (cookie != null)
{
sessionId = cookie["session-id"].Value;
}
CookieHeaderValue 包含 CookieState 实例的集合。 每个 CookieState 表示一个 Cookie。 使用索引器方法按名称获取 CookieState ,如下所示。
结构化 Cookie 数据
许多浏览器会限制它们存储的 Cookie 数,包括总数和每个域的数量。 因此,可以将结构化数据放入单个 Cookie 中,而不是设置多个 Cookie。
注释
RFC 6265 不定义 Cookie 数据的结构。
使用 CookieHeaderValue 类,可以传递 Cookie 数据的名称值对列表。 这些名称/值对在 Set-Cookie 标头中编码为 URL 编码的表单数据:
var resp = new HttpResponseMessage();
var nv = new NameValueCollection();
nv["sid"] = "12345";
nv["token"] = "abcdef";
nv["theme"] = "dark blue";
var cookie = new CookieHeaderValue("session", nv);
resp.Headers.AddCookies(new CookieHeaderValue[] { cookie });
前面的代码生成以下 Set-Cookie 标头:
Set-Cookie: session=sid=12345&token=abcdef&theme=dark+blue;
CookieState 类提供索引器方法,用于从请求消息中的 Cookie 读取子值:
string sessionId = "";
string sessionToken = "";
string theme = "";
CookieHeaderValue cookie = Request.Headers.GetCookies("session").FirstOrDefault();
if (cookie != null)
{
CookieState cookieState = cookie["session"];
sessionId = cookieState["sid"];
sessionToken = cookieState["token"];
theme = cookieState["theme"];
}
示例:在消息处理程序中设置和检索 Cookie
前面的示例演示了如何在 Web API 控制器中使用 Cookie。 另一个选项是使用 消息处理程序。 消息处理程序在管道中比控制器更早被调用。 消息处理程序可以在请求到达控制器之前从请求中读取 Cookie,或者在控制器生成响应后向响应添加 Cookie。
以下代码显示了用于创建会话 ID 的消息处理程序。 会话 ID 存储在 Cookie 中。 处理程序检查请求中是否包含会话 Cookie。 如果请求不包含 Cookie,处理程序将生成新的会话 ID。 在任一情况下,处理程序将会话 ID 存储在 HttpRequestMessage.Properties 属性包中。 它还将会话 Cookie 添加到 HTTP 响应中。
此实现不验证来自客户端的会话 ID 是否实际由服务器颁发。 不要将其用作身份验证形式! 该示例的要点是显示 HTTP Cookie 管理。
using System;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http;
public class SessionIdHandler : DelegatingHandler
{
public static string SessionIdToken = "session-id";
async protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
string sessionId;
// Try to get the session ID from the request; otherwise create a new ID.
var cookie = request.Headers.GetCookies(SessionIdToken).FirstOrDefault();
if (cookie == null)
{
sessionId = Guid.NewGuid().ToString();
}
else
{
sessionId = cookie[SessionIdToken].Value;
try
{
Guid guid = Guid.Parse(sessionId);
}
catch (FormatException)
{
// Bad session ID. Create a new one.
sessionId = Guid.NewGuid().ToString();
}
}
// Store the session ID in the request property bag.
request.Properties[SessionIdToken] = sessionId;
// Continue processing the HTTP request.
HttpResponseMessage response = await base.SendAsync(request, cancellationToken);
// Set the session ID as a cookie in the response message.
response.Headers.AddCookies(new CookieHeaderValue[] {
new CookieHeaderValue(SessionIdToken, sessionId)
});
return response;
}
}
控制器可以从 HttpRequestMessage.Properties 属性包获取会话 ID。
public HttpResponseMessage Get()
{
string sessionId = Request.Properties[SessionIdHandler.SessionIdToken] as string;
return new HttpResponseMessage()
{
Content = new StringContent("Your session ID = " + sessionId)
};
}