android cookie 格式,Android OkHttp Cookie持久化问题总结

说明

最近封装一个SDK时,遇到一个需求就是登录成功之后,APP需要持久保存Cookie,当APP退出再进入时需要从本地读取Cookie值,类似于浏览器,一个网站登录成功之后,关闭浏览器再打开,还能继续访问这个网站网页。

3c3da18e369e

Cookie

图片来源:https://www.cnblogs.com/zhuanzhuanfe/p/8010854.html

分析

首先我们清除谷歌浏览器里面缓存的Cookie,当首次访问百度https://www.baidu.com/,请求体中还没有携带Cookie,响应体中会出现Set-Cookie字段,要求浏览器保存Cookie,当第二次请求时会携带这个Cookie信息。

请求头(第一次请求):

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8

Accept-Encoding: gzip, deflate, br

Accept-Language: zh-CN,zh;q=0.9

Connection: keep-alive

Host: www.baidu.com

Upgrade-Insecure-Requests: 1

User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3472.3 Safari/537.36

响应头:

Bdpagetype: 1

Bdqid: 0xe1a8fd3600011fd8

Cache-Control: private

Connection: Keep-Alive

Content-Encoding: gzip

Content-Type: text/html

Cxy_all: baidu+c1a146ec227bccffbb8afe4da97bdf3e

Date: Sat, 06 Apr 2019 09:48:35 GMT

Expires: Sat, 06 Apr 2019 09:47:45 GMT

P3p: CP=" OTI DSP COR IVA OUR IND COM "

Server: BWS/1.1

Set-Cookie: PSTM=1554544115; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com

Set-Cookie: BAIDUID=F7EBDE8F1230A7DDF1DD141A458BD04B:FG=1; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com

Set-Cookie: BIDUPSID=F7EBDE8F1230A7DDF1DD141A458BD04B; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com

Set-Cookie: delPer=0; path=/; domain=.baidu.com

Set-Cookie: BDSVRTM=0; path=/

Set-Cookie: BD_HOME=0; path=/

Set-Cookie: H_PS_PSSID=1439_28794_21081_28774_28721_28558_28585_26350_28604_28625_22159; path=/; domain=.baidu.com

Strict-Transport-Security: max-age=172800

Transfer-Encoding: chunked

Vary: Accept-Encoding

X-Ua-Compatible: IE=Edge,chrome=1

请求头(第二次请求):

里面携带Cookie信息

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8

Accept-Encoding: gzip, deflate, br

Accept-Language: zh-CN,zh;q=0.9

Cache-Control: max-age=0

Connection: keep-alive

Cookie: BAIDUID=F7EBDE8F1230A7DDF1DD141A458BD04B:FG=1; BIDUPSID=F7EBDE8F1230A7DDF1DD141A458BD04B; PSTM=1554544115; delPer=0; BD_HOME=0; H_PS_PSSID=1439_28794_21081_28774_28721_28558_28585_26350_28604_28625_22159; BD_UPN=12314353

Host: www.baidu.com

Upgrade-Insecure-Requests: 1

User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3472.3 Safari/537.36

现象

OkHttpClient okHttpClient = new OkHttpClient.Builder()

.connectTimeout(10000L, TimeUnit.MILLISECONDS)

.readTimeout(10000L, TimeUnit.MILLISECONDS)

.cookieJar(new CookieJarImpl(new PersistentCookieStore(this)))

// .cookieJar(new CookieJarImpl(new MemoryCookieStore()))

.addInterceptor(new LoggerInterceptor("TAG"))

.build();

OkHttpUtils.initClient(okHttpClient);

String top250 = "http://api.douban.com/v2/movie/top250";

// 配置基本网络请求

OkHttpUtils.get().url(top250)

.build()

.execute(new StringCallback() {

@Override

public void onError(Call call, Exception e, int id) {

Log.d(TAG, " 失败:" + e.toString());

}

@Override

public void onResponse(String response, int id) {

Log.d(TAG, " 成功:" + response);

}

});

当设置内存保存Cookie时(MemoryCookieStore),第二次访问携带上Cookie,但是退出APP之后就丢失了。

3c3da18e369e

当设置永久保存Cookie时(PersistentCookieStore),第二次访问还是没有携带上Cookie,

3c3da18e369e

image.png

3c3da18e369e

PersistentCookieStore代码实现

3c3da18e369e

persistent值

从源码上可以看出,当请求头中存在expires和max-age时,返回为True,这个时候PersistentCookieStore是不对Cookie进行磁盘、内存存储的,这里只是设置一个Cookie的有效期,此时Cookie值并没有过期。

维持持久化Cookie,推荐使用持久化cookie框架,PersistentCookieJar,

ClearableCookieJar cookieJar =

new PersistentCookieJar(new SetCookieCache(), new SharedPrefsCookiePersistor(this));

OkHttpClient okHttpClient = new OkHttpClient.Builder()

.connectTimeout(10000L, TimeUnit.MILLISECONDS)

.readTimeout(10000L, TimeUnit.MILLISECONDS)

.cookieJar(cookieJar)

// .cookieJar(new CookieJarImpl(new PersistentCookieStore(this)))

// .cookieJar(new CookieJarImpl(new MemoryCookieStore()))

.addInterceptor(new LoggerInterceptor("TAG"))

.build();

OkHttpUtils.initClient(okHttpClient);

3c3da18e369e

Cookie未保存

3c3da18e369e

Cookie过滤条件

3c3da18e369e

persistent

3c3da18e369e

Cookie判断

从源码上可以看出,当请求头中不存在expires和max-age时,返回为False,这个时候PersistentCookieJar是不对Cookie进行磁盘存储的。

另外一种情况

okttp3访问IP地址Cookie丢失的现象,这里使用百度的IP地址:http://220.181.112.244:80/,

//这里使用百度IP地址

String baidu = "http://220.181.112.244:80/";

// 配置基本网络请求

OkHttpUtils.get().url(baidu)

.build()

.execute(new StringCallback() {

@Override

public void onError(Call call, Exception e, int id) {

Log.d(TAG, " 失败:" + e.toString());

}

@Override

public void onResponse(String response, int id) {

Log.d(TAG, " 成功:" + response);

}

});

3c3da18e369e

丢失Cookie情况

查看OkHttp-3.3.1底层Cookie实现,可以看到这一部分代码:

...

} else if (attributeName.equalsIgnoreCase("domain")) {

try {

domain = parseDomain(attributeValue);

hostOnly = false;

} catch (IllegalArgumentException e) {

// Ignore this attribute, it isn't recognizable as a domain.

}

}

...

// If the domain is present, it must domain match. Otherwise we have a host-only cookie.

if (domain == null) {

domain = url.host();

} else if (!domainMatch(url, domain)) {

return null; // No domain match? This is either incompetence or malice!

}

...

for (int i = 0, size = cookieStrings.size(); i < size; i++) {

Cookie cookie = Cookie.parse(url, cookieStrings.get(i));

if (cookie == null) continue;

if (cookies == null) cookies = new ArrayList<>();

cookies.add(cookie);

}

当请求头中存在domain时,这个时候主地址为ip与domian不等,Cookie解析失败为null,导致保存Cookie失败,这个浏览器也是存在问题的,这个得后台注意格式。

3c3da18e369e

浏览器情况

代码实现

第一种实现方式(拦截器实现)

mSharedPreferences = getSharedPreferences("Cookie_Pre", Context.MODE_PRIVATE);

cookies = new HashMap<>();

OkHttpClient okHttpClient = new OkHttpClient.Builder()

.connectTimeout(10000L, TimeUnit.MILLISECONDS)

.readTimeout(10000L, TimeUnit.MILLISECONDS)

//网络拦截器

.addInterceptor(new Interceptor() {

@Override

public Response intercept(Chain chain) throws IOException {

//获取请求链接

Request originalRequest = chain.request();

//获取url的主机地址

String hostString = originalRequest.url().host();

if (!cookies.containsKey(hostString)) {

//获取磁盘里面的spCookie字符串

String spCookie = mSharedPreferences.getString(hostString, "");

if (!TextUtils.isEmpty(spCookie)) {

//获取spCookie解密放到内存中

cookies.put(hostString, spCookie);

}

}

//获取内存中的Cookie

String memoryCookie = cookies.get(hostString);

//拦截网络请求数据

Request request = originalRequest.newBuilder()

//设置请求头Cookie值

.addHeader("Cookie", memoryCookie == null ? "" : memoryCookie)

.build();

//拦截返回数据

Response originalResponse = chain.proceed(request);

//判断请求头里面是否有Set-Cookie值,更新Cookie

if (!originalResponse.headers("Set-Cookie").isEmpty()) {

//字符串集

StringBuilder stringBuilder = new StringBuilder();

for (String header : originalResponse.headers("Set-Cookie")) {

stringBuilder.append(header);

stringBuilder.append(";");

}

//拼接Cookie成字符串

String cookie = stringBuilder.toString();

//更新内存中Cookies值

cookies.put(hostString, cookie);

//存储到本地磁盘中

SharedPreferences.Editor editor = mSharedPreferences.edit();

//存储cookie(为了安全这里可以加密存储)

editor.putString(hostString, cookie);

editor.apply();

Log.e("Set-Cookie", "cookies: " + cookie + " host: " + hostString);

}

return originalResponse;

}

})

.addInterceptor(new LoggerInterceptor("TAG"))

.build();

OkHttpUtils.initClient(okHttpClient);

第二种实现方式(继承CookieJar实现)

这里可以参考OKGO里面实现的库,Cookie,实现

CookieJarImpl继承CookieJar和SPCookieStore。

public class SPCookieStore implements CookieStore {

private static final String COOKIE_PREFS = "okhttp_cookie"; //cookie使用prefs保存

private static final String COOKIE_NAME_PREFIX = "cookie_"; //cookie持久化的统一前缀

private final Map> cookies;

private final SharedPreferences cookiePrefs;

public SPCookieStore(Context context) {

cookiePrefs = context.getSharedPreferences(COOKIE_PREFS, Context.MODE_PRIVATE);

cookies = new HashMap<>();

//将持久化的cookies缓存到内存中,数据结构为 Map>

Map prefsMap = cookiePrefs.getAll();

for (Map.Entry entry : prefsMap.entrySet()) {

if ((entry.getValue()) != null && !entry.getKey().startsWith(COOKIE_NAME_PREFIX)) {

//获取url对应的所有cookie的key,用","分割

String[] cookieNames = TextUtils.split((String) entry.getValue(), ",");

for (String name : cookieNames) {

//根据对应cookie的Key,从xml中获取cookie的真实值

String encodedCookie = cookiePrefs.getString(COOKIE_NAME_PREFIX + name, null);

if (encodedCookie != null) {

Cookie decodedCookie = SerializableCookie.decodeCookie(encodedCookie);

if (decodedCookie != null) {

if (!cookies.containsKey(entry.getKey())) {

cookies.put(entry.getKey(), new ConcurrentHashMap());

}

cookies.get(entry.getKey()).put(name, decodedCookie);

}

}

}

}

}

}

private String getCookieToken(Cookie cookie) {

return cookie.name() + "@" + cookie.domain();

}

/** 当前cookie是否过期 */

private static boolean isCookieExpired(Cookie cookie) {

return cookie.expiresAt() < System.currentTimeMillis();

}

/** 将url的所有Cookie保存在本地 */

@Override

public synchronized void saveCookie(HttpUrl url, List urlCookies) {

for (Cookie cookie : urlCookies) {

saveCookie(url, cookie);

}

}

@Override

public synchronized void saveCookie(HttpUrl url, Cookie cookie) {

if (!cookies.containsKey(url.host())) {

cookies.put(url.host(), new ConcurrentHashMap());

}

//当前cookie是否过期

if (isCookieExpired(cookie)) {

removeCookie(url, cookie);

} else {

saveCookie(url, cookie, getCookieToken(cookie));

}

}

/** 保存cookie,并将cookies持久化到本地 */

private void saveCookie(HttpUrl url, Cookie cookie, String cookieToken) {

//内存缓存

cookies.get(url.host()).put(cookieToken, cookie);

//文件缓存

SharedPreferences.Editor prefsWriter = cookiePrefs.edit();

prefsWriter.putString(url.host(), TextUtils.join(",", cookies.get(url.host()).keySet()));

prefsWriter.putString(COOKIE_NAME_PREFIX + cookieToken, SerializableCookie.encodeCookie(url.host(), cookie));

prefsWriter.apply();

}

/** 根据当前url获取所有需要的cookie,只返回没有过期的cookie */

@Override

public synchronized List loadCookie(HttpUrl url) {

List ret = new ArrayList<>();

if (!cookies.containsKey(url.host())) return ret;

Collection urlCookies = cookies.get(url.host()).values();

for (Cookie cookie : urlCookies) {

if (isCookieExpired(cookie)) {

removeCookie(url, cookie);

} else {

ret.add(cookie);

}

}

return ret;

}

/** 根据url移除当前的cookie */

@Override

public synchronized boolean removeCookie(HttpUrl url, Cookie cookie) {

if (!cookies.containsKey(url.host())) return false;

String cookieToken = getCookieToken(cookie);

if (!cookies.get(url.host()).containsKey(cookieToken)) return false;

//内存移除

cookies.get(url.host()).remove(cookieToken);

//文件移除

SharedPreferences.Editor prefsWriter = cookiePrefs.edit();

if (cookiePrefs.contains(COOKIE_NAME_PREFIX + cookieToken)) {

prefsWriter.remove(COOKIE_NAME_PREFIX + cookieToken);

}

prefsWriter.putString(url.host(), TextUtils.join(",", cookies.get(url.host()).keySet()));

prefsWriter.apply();

return true;

}

@Override

public synchronized boolean removeCookie(HttpUrl url) {

if (!cookies.containsKey(url.host())) return false;

//内存移除

ConcurrentHashMap urlCookie = cookies.remove(url.host());

//文件移除

Set cookieTokens = urlCookie.keySet();

SharedPreferences.Editor prefsWriter = cookiePrefs.edit();

for (String cookieToken : cookieTokens) {

if (cookiePrefs.contains(COOKIE_NAME_PREFIX + cookieToken)) {

prefsWriter.remove(COOKIE_NAME_PREFIX + cookieToken);

}

}

prefsWriter.remove(url.host());

prefsWriter.apply();

return true;

}

@Override

public synchronized boolean removeAllCookie() {

//内存移除

cookies.clear();

//文件移除

SharedPreferences.Editor prefsWriter = cookiePrefs.edit();

prefsWriter.clear();

prefsWriter.apply();

return true;

}

/** 获取所有的cookie */

@Override

public synchronized List getAllCookie() {

List ret = new ArrayList<>();

for (String key : cookies.keySet()) {

ret.addAll(cookies.get(key).values());

}

return ret;

}

@Override

public synchronized List getCookie(HttpUrl url) {

List ret = new ArrayList<>();

Map mapCookie = cookies.get(url.host());

if (mapCookie != null) ret.addAll(mapCookie.values());

return ret;

}

}

//当前cookie是否过期

if (isCookieExpired(cookie)) {

removeCookie(url, cookie);

} else {

saveCookie(url, cookie, getCookieToken(cookie));

}

/** 当前cookie是否过期 */

private static boolean isCookieExpired(Cookie cookie) {

return cookie.expiresAt() < System.currentTimeMillis();

}

【总结】这里保存持久化Cookie的关键看expiresAt与当前时间戳相比是否为过期,而不是看响应头里是否存在expires和max-age字段。

使用与之前类似:

OkHttpClient okHttpClient = new OkHttpClient.Builder()

.connectTimeout(10000L, TimeUnit.MILLISECONDS)

.readTimeout(10000L, TimeUnit.MILLISECONDS)

.cookieJar(new CookieJarImpl(new SPCookieStore()))

.addInterceptor(new LoggerInterceptor("TAG"))

.build();

OkHttpUtils.initClient(okHttpClient);

总结

后台对Cookie返回格式还是要规范一点,否则Cookie持久化保存会出现莫名其妙的错误。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值