一. 关于Cookie
一句话概括就是:cookie就是一系列的键值对组成的字符串
二. Cookie的最常用场景
登录状态的判定等
前言
以前看过不少关于cookie的文章,至于写的好坏不做评价,反正我是没看多明白如何操作cookie,也有可能是个人水平不行,无法理解别人的讲解。最近一直在学习后台开发,加上个人一直在做移动端开发,之前一直不明白的地方现在都豁然开朗了,于是打算写这篇文章了。
如果是在浏览器中,浏览器会管理cookie,不用人为干涉,但是在移动端开发时就不同了,需要手动传递cookie,和服务端交流。如果服务器按照传统的页面开发实现实现接口,那么移动客户端就要实现像浏览器一样操作cookie。
1.想明白如何操作cookie,就必须明白cookie是什么,以及cookie是如何传递的
请求参数:
返回参数:
a)先看响应,返回数据的头中有一系列的key为Set-Cookie的键值对
先说第一个Set-Cookie:JSESSIONID
JSESSIONID是什么:事实上当用户访问服务器的时候会为每一个用户开启一个session,浏览器是怎么判断这个session到底是属于哪个用户呢?jsessionid的作用就体现出来了:jsessionid就是用来判断当前用户对应于哪个session。换句话说服务器识别session的方法是通过jsessionid来告诉服务器该客户端的session在内存的什么地方。
这时用来实现会话的,如果是第一次访问,请求的参数中没有这个参数,服务器会生成一个JSESSIONID放在响应头中,下次在访问的时候,浏览器就会把这个参数带上
第一幅图中的Cookie中就有JSESSIONID,这说明已经不是第一次访问了
而第二幅图中返回的头中有JSESSIONID,说明这次的请求是第一次请求
这两幅图并不是同一次的请求和响应
第二幅图中的其它Set-Cookie
都是服务器返回的其它键值对,浏览器不用关心它们是什么意思,下次访问的时候直接在返回给服务端就是。
其中第二个Set-Cookie长得与其它不太一样,多了一个Expires
,这个是表示这个键值对过期时间,如果过期了,下次请求浏览器就不会把这个键值对带过去
b)再看请求:
请求的头中Cookie就是把所有的服务器之前返回的Cookie以分号;
进行分割,拼成了一个字符串传给了服务器。
例如之前服务器响应了JSESSIONID、akey、bkey、ckey四个键值对,再次访问的时候,浏览器就会把没有过期的cookie再次发送给服务器。
上面提到的akey设置了过期时间,如果akey过期了,那么请求中就不会把akey加到Cookie中。只会发送JSESSIONID、bkey、ckey
c)Cookie的识别
当我们访问的路径地址中包含此cookie的path,则携带。例如:http://localhost:8080/day11/hello,就会把下图中的ckey带着
path会有一个默认值,过期时间如果不设置,默认就是关闭浏览器时过期
2.利用HttpURLConnection操作Cookie,模拟浏览器
a)获取Cookie
getHeaderFields()
方法,返回结果是Map<String,List<String>>
类型
HttpURLConnection httpConn = null;
...
Map<String,List<String>> cookies = httpConn.getHeaderFields();
List<String> setCookies = cookies.get("Set-Cookie");
这样就可以吧所有的Set-Cookie
字段都拿到。存起来,至于怎么存,无所谓,自己决定
b)携带Cookie
// 把你存的Cookie键值对用分号分隔,拼成一个字符串,是不是需要把所有Cookie都拼进来,看实际情况
// 例如你有10个键值对,有3个过期的,肯定不要了,至于过期判定,前面有说到,这个就需要你按照规定的格式判断出来。还有两个可能是这次请求不需要的,也可以不带,为了简单可以直接把所有没过期的都带上,省事,服务器爱用不用。
String cookie = "JSESSIONID=xxx;key1=value1;key2=value2"
conn.setRequestProperty("Cookie", cookie);
这样就可以传给服务器,就是在和么简单。
c)会话保持
拿到的Cookie中如果有JSESSIONID就存起来,每次请求都带着,这样就能实现保持会话,如果某次请求服务器告诉你需要重新新登录了,那么你就把保存的JSESSIONID删了,并且请求中不携带JSESSIONID,服务器会再给你一个新的,保存下来以后继续用即可。
三、Cookie在Android中的持久化
持久化保存cookie有很多方式,可以用数据库,可以用文件,SharedPreferences,还可以保存到系统Webview的CookieManager里(其实也是个数据库)。
如果我们自己本地保存cookie,要做好本地Cookie和Webview的cookie同步,所以最好的办法是把本地请求获得的Cookie也保存到系统Webview的CookieManager里,取的时候从Webview的CookieManager里取,让CookieManager统一管理岂不美滋滋,哈哈。
PersistentCookieJar是一个持久化在SharedPreferences中的例子,代码也不复杂,大家可以看看:
https://github.com/franmontiel/PersistentCookieJar A persistent CookieJar implementation for OkHttp 3 based on SharedPreferences.
OkHttp3.0 中实现 Cookie 持久化管理
3.0之后OKHttp是加了CookieJar和Cookie两个类的,通过实现CookieJar即可管理cookie。
private class CookiesManager implements CookieJar {
private final PersistentCookieStore cookieStore = new PersistentCookieStore(getApplicationContext());
@Override
public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
if (cookies != null && cookies.size() > 0) {
for (Cookie item : cookies) {
cookieStore.add(url, item);
}
}
}
@Override
public List<Cookie> loadForRequest(HttpUrl url) {
List<Cookie> cookies = cookieStore.get(url);
return cookies;
}
}
OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.cookieJar(new CookiesManager());
WebView中的Cookie机制
WebView是基于webkit内核的UI控件,相当于一个浏览器客户端。它会在本地维护每次会话的cookie(保存在data/data/package_name/app_WebView/Cookies),如下图:
数据就保存在Cookies那个文件里,其实是个数据库,把后缀改成.db用数据库打开可以看到里面的表结构,主要有host_key, name, value, path等,host_key其实就是前面说的domain,这些字段其实也都是前面说的属性:
当WebView加载URL的时候,WebView会从本地读取该URL对应的cookie,并携带该cookie与服务器进行通信。WebView通过android.webkit.CookieManager类来维护cookie。CookieManager是 WebView的cookie管理类。
之前同步cookie需要用到CookieSyncManager类,现在这个类已经被deprecated。如今WebView已经可以在需要的时候自动同步cookie了。
CookieSyncManager
在安卓5.0以下,主要使用CookieSyncManager在内存和存储器之间同步浏览器的cookie,另外CookieSyncManager同步策略是在一个独立的线程里定时进行同步。
cookie开始同步:注意每次同步的时间间隔是5分钟
CookieSyncManager.createInstance(context);
CookieSyncManager.getInstance().startSync();
cookie停止同步:
CookieSyncManager.getInstance().stopSync()
cookie立即同步:调用了该方法会立即进行cookie的同步,代码如下:
//一般是在webview中的onPageFinished(WebView, String)方法进行强制同步
CookieSyncManager.getInstance().sync()
删除cookie操作:
CookieSyncManager.createInstance(this);
CookieManager.getInstance().removeAllCookie();
CookieManager.getInstance().removeSessionCookie();
CookieSyncManager.getInstance().sync();
CookieManager
从5.0之后,webview已经内置了cookie的同步操作了。
删除所有Cookie
CookieManager.getInstance().removeAllCookies(null);
CookieManager.getInstance().flush();
保存Cookie
CookieManager.getInstance().setCookie(String url, String value)
获取Cookie
CookieManager.getInstance().getCookie(url)
我们综合两个Manager, 最后写法:
同步Cookie
CookieSyncManager.createInstance(this);
if (Build.VERSION.SDK_INT < 21) {
CookieSyncManager.getInstance().sync();
} else {
CookieManager.getInstance().flush();
}
删除所有Cookie
CookieSyncManager.createInstance(this);
CookieManager.getInstance().removeAllCookie();
CookieManager.getInstance().removeSessionCookie();
if (Build.VERSION.SDK_INT < 21) {
CookieSyncManager.getInstance().sync();
} else {
CookieManager.getInstance().flush();
}
总结,Cookie本身就是键值对,key是Cookie,value是各种cookie中间用分号分隔的字符串,每个value分号分隔的里面又是键值对,name是某个cookie的名称,value是具体的cookie的值。在浏览器的请求头中会自动传给服务端,其实传的是多个Cookies,后端从中根据name取出你需要的即可。不过我不太建议使用Cookie来保存用户的登录状态,在移动端ios和安卓是可以的,但是如果在web端,用户的浏览器是禁用的,而我们的鉴权可能又是使用的一套,就会有些麻烦了。