Cookie
What are Cookies? 简而言之,cookies 只不过是存储在客户端(即浏览器中)的一条信息。客户端将它们与每个请求一起发送到服务器,服务器可以告诉客户端要存储哪些 cookie。
它们通常用于跟踪网站的活动、自定义用户会话以及服务器在请求之间识别用户。另一种情况是将 JWT 令牌或用户 ID 存储在 cookie 中,以便服务器可以识别用户是否已通过每个请求进行身份验证。
How Do Cookies Work?
Cookie 由服务器在 HTTP 响应中发送到客户端,并存储在客户端(用户的浏览器)中。
服务器在名为 的 HTTP 响应标头中设置 cookie Set-Cookie
。cookie 由键/值对以及其他可选属性组成,我们稍后会介绍。
让我们想象一个用户登录的场景。客户端使用用户的凭据向服务器发送请求。服务器对用户进行身份验证,创建一个带有用户 ID 编码的 cookie,并将其设置在响应标头中。HTTP 响应中的标头Set-Cookie
如下所示:
|
|
一旦浏览器获得 cookie,它就可以将 cookie 发送回服务器。为此,浏览器通过设置名为的标头将 cookie 添加到 HTTP 请求中Cookie
:
|
|
服务器从请求中读取 cookie,根据用户 ID 是否有效这一事实来验证用户是否已通过身份验证。
如前所述,cookie 可以具有其他可选属性,因此让我们来探索它们。
Cookie Max-Age and Expiration Date
属性Max-Age
和/或Expires
用于使 cookie 持久化。默认情况下,浏览器会在会话关闭时删除 cookie,除非Max-Age
和/或Expires
已设置。这些属性设置如下:
|
|
此 cookie 将在创建后 86400 秒或超过 中指定的日期和时间后过期Expires
。
当 cookie 中同时存在这两个属性时,Max-Age
优先于Expires
.
Cookie Domain
Domain
是 Cookie 的另一个重要属性。当我们想为我们的 cookie 指定一个域时,我们会使用它:
|
|
通过这样做,我们告诉客户端它应该将 cookie 发送到哪个域。浏览器只会从该域向服务器发送 cookie。
将域设置为“example.com”不仅会将 cookie 发送到“example.com”域,还会将其子域“foo.example.com”和“bar.example.com”发送到。
如果我们不明确设置域,它将仅设置为创建 cookie 的域,而不是其子域。
Cookie Path
该Path
属性指定将在该域内传递 cookie 的位置。客户端会将 cookie 添加到对与给定路径匹配的 URL 的所有请求中。通过这种方式,我们缩小了 cookie 在域内有效的 URL。
让我们考虑在执行请求时后端为其客户端设置了一个 cookie http://example.com/login
:
|
|
请注意,该Path
属性设置为/user/
。现在让我们访问两个不同的 URL,看看我们在请求 cookie 中有什么。
当我们执行对 的请求时http://example.com/user/
,浏览器会在请求中添加以下标头:
|
|
正如预期的那样,浏览器将 cookie 发送回服务器。
当我们尝试向http://example.com/contacts/
浏览器发出另一个请求时,将不包含标头,因为它与属性Cookie
不匹配。Path
在创建 cookie 期间未设置路径时,默认为/
.
通过Path
显式设置,cookie 将被传递到指定的 URL 及其所有子目录。
Secure Cookie
如果我们将敏感信息存储在 cookie 中,并且我们希望它仅在安全 (HTTPS) 连接中发送,那么该Secure
属性就派上用场了:
|
|
通过设置Secure
,我们确保我们的 cookie 仅通过 HTTPS 传输,并且不会通过未加密的连接发送。
HttpOnly
Cookie
HttpOnly
是 cookie 的另一个重要属性。它确保客户端脚本不会访问 cookie。这是另一种保护 cookie 不被恶意代码或 XSS 攻击更改的形式。
|
|
并非所有浏览器都支持该HttpOnly
标志。好消息是它们中的大多数都这样做,但如果没有,HttpOnly
即使在创建 cookie 期间设置了该标志,它也会忽略该标志。HttpOnly
除非浏览器不支持 Cookie 或需要将其公开给客户端脚本,否则Cookie 应始终存在。
现在我们知道了 cookie 是什么以及它们是如何工作的,让我们来看看我们如何在 Spring Boot 中处理它们。
Handling Cookies with the Servlet API
Now, let’s take a look at how to set cookies on the server-side with the Servlet API.
Creating a Cookie
For creating a cookie with the Servlet API we use the Cookie
class which is defined inside the javax.servlet.http
package.
The following snippet of code creates a cookie with name user-id
and value c2FtLnNtaXRoQGV4YW1wbGUuY29t
and sets all the attributes we discussed:
|
|
Now that we created the cookie, we will need to send it to the client. To do so, we add the cookie to the response(HttpServletResponse
) and we are done. Yes, it is as simple as that:
|
|
Reading a Cookie
After adding the cookie to the response header, the server will need to read the cookies sent by the client in every request.
The method HttpServletRequest#getCookies()
returns an array of cookies that are sent with the request. We can identify our cookie by the cookie name.
In the following snippet of code, we are iterating through the array, searching by cookie name, and returning the value of the matched cookie:
|
|
Deleting a Cookie
To delete a cookie we will need to create another instance of the Cookie
with the same name and maxAge
0 and add it again to the response as below:
|
|
Going back to our use case where we save the JWT token inside the cookie, we would need to delete the cookie when the user logs out. Keeping the cookie alive after the user logs out can seriously compromise the security.
Handling Cookies with Spring
Now that we know how to handle a cookie using the Servlet API, let’s check how we can do the same using the Spring Framework.
Creating a Cookie
In this section, we will create a cookie with the same properties that we did using the Servlet API.
We will use the class ResponseCookie
for the cookie and ResponseEntity
for setting the cookie in the response. They are both defined inside org.springframework.http
package.
ResponseCookie
has a static method from(final String name, final String value)
which returns a ResponseCookieBuilder
initialized with the name and value of the cookie.
We can add all the properties that we need and use the method build()
of the builder to create the ResponseCookie
:
|
|
After creating the cookie, we add it to the header of the response like this:
|
|
Reading a Cookie with @CookieValue
Spring Framework provides the @CookieValue
annotation to read any cookie by specifying the name without needing to iterate over all the cookies fetched from the request.
@CookieValue
is used in a controller method and maps the value of a cookie to a method parameter:
|
|
In cases where the cookie with the name “user-id” does not exist, the controller will return the default value defined with defaultValue = "default-user-id"
. If we do not set the default value and Spring fails to find the cookie in the request then it will throw java.lang.IllegalStateException
exception.
Deleting a Cookie
To delete a cookie, we will need to create the cookie with the same name and maxAge
to 0 and set it to the response header:
|
|
Conclusion
在本文中,我们研究了 cookie 是什么以及它们是如何工作的。
总而言之,cookie 是简单的文本字符串,带有一些信息并用名称标识。
我们检查了一些可以添加到 cookie 中的可选属性,以使它们以某种方式运行。我们看到我们可以使用 and 使它们持久化,使用Max-Age
andExpires
缩小它们的范围Domain
,使用Path
让它们仅通过 HTTPS 传输Secure
,并使用 对客户端脚本隐藏它们HttpOnly
。
最后,我们研究了使用 Servlet API 和 Spring 处理 cookie 的两种方法。这两个 API 都提供了创建(使用属性)、读取和删除 cookie 所需的方法。
它们很容易实现,开发人员可以选择其中任何一个来实现 cookie。