cookie 浅析
HTTP协议本身是无状态的。什么是无状态呢,即服务器无法判断用户身份。
cookie就像一个身份证一样,是服务器颁发给客户端的,后续客户端就可以拿着这个绿卡通行,在请求的时候就可以获取到服务器的数据。
-
客户端发送登录请求
-
服务器响应:在响应报文之中通过报文首部的 Set-Cookie首部字段颁发 cookie字段给客户端
-
客户端获取到set-cookie字段(代表登陆成功)
客户端解析set-cookie字段
-
客户端在下一次请求时候在请求报文之中通过 cookie 首部 设置cookie来获得服务端的数据
set-Cookie 字段
某api接口登录时返回的set-cookie 字段
ini
"MUSIC_A_T=1678353370003; Max-Age=2147483647; Expires=Wed, 28 Mar 2091 11:27:23 GMT; Path=/eapi/feedback; HTTPOnly;
MUSIC_R_T=1678353370019; Max-Age=2147483647; Expires=Wed, 28 Mar 2091 11:27:23 GMT; Path=/neapi/feedback; HTTPOnly;
MUSIC_A_T=1678353370003; Max-Age=2147483647; Expires=Wed, 28 Mar 2091 11:27:23 GMT; Path=/neapi/clientlog; HTTPOnly;
MUSIC_A_T=1678353370003; Max-Age=2147483647; Expires=Wed, 28 Mar 2091 11:27:23 GMT; Path=/weapi/clientlog; HTTPOnly;
MUSIC_SNS=; Max-Age=0; Expires=Fri, 10 Mar 2023 08:13:16 GMT; Path=/;
MUSIC_R_T=1678353370019; Max-Age=2147483647; Expires=Wed, 28 Mar 2091 11:27:23 GMT; Path=/eapi/clientlog; HTTPOnly;
MUSIC_A_T=1678353370003; Max-Age=2147483647; Expires=Wed, 28 Mar 2091 11:27:23 GMT; Path=/neapi/feedback; HTTPOnly;
MUSIC_R_T=1678353370019; Max-Age=2147483647; Expires=Wed, 28 Mar 2091 11:27:23 GMT; Path=/wapi/clientlog; HTTPOnly;
MUSIC_A_T=1678353370003; Max-Age=2147483647; Expires=Wed, 28 Mar 2091 11:27:23 GMT; Path=/api/clientlog; HTTPOnly;
MUSIC_R_T=1678353370019; Max-Age=2147483647; Expires=Wed, 28 Mar 2091 11:27:23 GMT; Path=/eapi/feedback; HTTPOnly;
MUSIC_R_T=1678353370019; Max-Age=2147483647; Expires=Wed, 28 Mar 2091 11:27:23 GMT; Path=/weapi/clientlog; HTTPOnly;
MUSIC_R_T=1678353370019; Max-Age=2147483647; Expires=Wed, 28 Mar 2091 11:27:23 GMT; Path=/api/feedback; HTTPOnly;
MUSIC_A_T=1678353370003; Max-Age=2147483647; Expires=Wed, 28 Mar 2091 11:27:23 GMT; Path=/wapi/clientlog; HTTPOnly;
MUSIC_R_T=1678353370019; Max-Age=2147483647; Expires=Wed, 28 Mar 2091 11:27:23 GMT; Path=/wapi/feedback; HTTPOnly;
MUSIC_A_T=1678353370003; Max-Age=2147483647; Expires=Wed, 28 Mar 2091 11:27:23 GMT; Path=/openapi/clientlog; HTTPOnly;
MUSIC_R_T=1678353370019; Max-Age=2147483647; Expires=Wed, 28 Mar 2091 11:27:23 GMT; Path=/openapi/clientlog; HTTPOnly;
MUSIC_A_T=1678353370003; Max-Age=2147483647; Expires=Wed, 28 Mar 2091 11:27:23 GMT; Path=/wapi/feedback; HTTPOnly;
MUSIC_R_T=1678353370019; Max-Age=2147483647; Expires=Wed, 28 Mar 2091 11:27:23 GMT; Path=/weapi/feedback; HTTPOnly;
MUSIC_A_T=1678353370003; Max-Age=2147483647; Expires=Wed, 28 Mar 2091 11:27:23 GMT; Path=/api/feedback; HTTPOnly;
MUSIC_A_T=1678353370003; Max-Age=2147483647; Expires=Wed, 28 Mar 2091 11:27:23 GMT; Path=/weapi/feedback; HTTPOnly;
MUSIC_A_T=1678353370003; Max-Age=2147483647; Expires=Wed, 28 Mar 2091 11:27:23 GMT; Path=/eapi/clientlog; HTTPOnly;
MUSIC_R_T=1678353370019; Max-Age=2147483647; Expires=Wed, 28 Mar 2091 11:27:23 GMT; Path=/neapi/clientlog; HTTPOnly;
__csrf=80afb31b44803b11a3a1b6dc353a0a98; Max-Age=1296010; Expires=Sat, 25 Mar 2023 08:13:26 GMT; Path=/;;
MUSIC_U=49e11c32722be03b70e7c306f8729b04ca985a91a360e98db8a41af6e30166d5993166e004087dd3a0cad15c758e52c84916e0979136541ccaa6936aa865f618ddb65572a1118868d4dbf082a8813684; Max-Age=15552000; Expires=Wed, 06 Sep 2023 08:13:16 GMT; Path=/; HTTPOnly;
MUSIC_R_T=1678353370019; Max-Age=2147483647; Expires=Wed, 28 Mar 2091 11:27:23 GMT; Path=/api/clientlog; HTTPOnly"
各个字段的属性
max-age 与Expires 字段同时存在时 ,max-age字段有效
Path=/api/clientlog 路径:如果api访问到了这个路径 就把这个路径所需要的cookie 带上,其他的cookie则不用带上
如何解析? okhttp 的cookie类已经做了
如何判断cookie是否过期
MUSIC_U
的过期时间是:Expires=Wed, 06 Sep 2023 08:13:16 GMT
__csrf
的过期时间是:Expires=Sat, 25 Mar 2023 08:13:26 GMT
要判断登录是否过期,您需要将这两个过期时间与当前时间进行比较。如果当前时间晚于这两个过期时间中的任何一个,那么登录就被认为是过期的。
cookiejar
cookiejar是okhttp的一个接口,并且没有默认实现
-
saveFromResponse 方法会解析setCookie字段,并做回调
- 在此方法中应该对回调的cookie实现保存和持久化,用于下一次的请求(整个
Cookie
对象序列化成一个字符串)
- 在此方法中应该对回调的cookie实现保存和持久化,用于下一次的请求(整个
-
loadForRequest方法返回一个List 并会解析到下一次请求报文的cookie首部
-
在此方法中应该读取持久化过的cookie(将给定的经过序列化的字符串还原成一个 OkHttp3
Cookie
对象),并对cookie做特殊的过滤处理-
过期:登录过期,考虑清除持久化过的cookie并重新登录
-
匹配path:若cookie与当前请求的路径相同,才在本次请求时带上,否则不带
-
-
第三方持久化与过滤方案
scss
PersistentCookieJar cookieJar =
new PersistentCookieJar(new SetCookieCache(), new SharedPrefsCookiePersistor(context));
return new OkHttpClient.Builder()
.cookieJar(cookieJar)
.build();
依赖
arduino
//cookie管理
implementation 'com.github.franmontiel:PersistentCookieJar:v1.0.1'
框架做了cookie的持久化管理,与path不匹配的过滤,以及cookie过期的处理,但是并不能判断是否需要重新登陆
解决方案:每次进入app之后进行一次个人账户的请求,若返回的id不为空,则代表当前cookie并未过期
若为空,则重新登陆
原码解析
- getResponseWithInterceptorChain方法
scss
val interceptors = mutableListOf<Interceptor>()
interceptors += client.interceptors
interceptors += RetryAndFollowUpInterceptor(client)
interceptors += BridgeInterceptor(client.cookieJar)
interceptors += CacheInterceptor(client.cache)
interceptors += ConnectInterceptor
2.在OkhttpClient类中
ini
val cookieJar: CookieJar = builder.cookieJar
3.在Builder类中
csharp
internal var cookieJar: CookieJar = CookieJar.NO_COOKIES
4.具体NO_COOKIES 的实现
kotlin
private class NoCookies : CookieJar {
override fun saveFromResponse(url: HttpUrl, cookies: List<Cookie>) {
}
override fun loadForRequest(url: HttpUrl): List<Cookie> {
return emptyList()
}
}
可以看到nocookies的实现是空实现,也就是没有具体作用的,这也是我们为什么要实现cookiejar的原因
5.具体到BridgeInterceptor(client.cookieJar)中
BridgeInterceptor 的作用
在请求之前:对请求报文首部字段的添加
请求之后:对获得的响应报文首部的去除,以及根据特殊特殊首部做处理,eg :Content-Encoding为gzip就需要对响应的body做解压缩处理
为什么我们要找到这个类:因为cookie也是首部字段
6.原码使用流程
scss
//1.请求之前
val cookies = cookieJar.loadForRequest(userRequest.url)//拿到我们自定义持久化并已经过滤之后的cookie
if (cookies.isNotEmpty()) {//表明是我们自定义的cookiejar
requestBuilder.header("Cookie", cookieHeader(cookies))//添加我们解持久化,并过滤的cookie为首部
}
......省略部分代码
//2.进行实际的网络请求
val networkResponse = chain.proceed(requestBuilder.build())
//3.请求之后
cookieJar.receiveHeaders(userRequest.url, networkResponse.headers)//从响应报文的set-cookie字段解析成cookie实例对象
//1
//3
解析后将url对象与cookie回调 供我们持久化