HTTP协议是无状态的的协议。一旦数据交换完毕,客户端与服务器端的连接就会关闭,再次交换数据需要建立新的连接。Cookie和Seesion,就是这样的机制,可以弥补HTTP协议无状态的不足
Cookie是客户端技术,程序把每个客户的数据以Cookie的形式写给用户各自的浏览器。当用户浏览器再去访问服务器中的web资源时,就会带着各自的数据去。这样web资源处理的就是用户各自的数据了
Session是服务器端技术,服务器在运行时可以为每个用户的浏览器创建一个其独享的Session对象,由于Session为用户浏览器独享,所以用户在访问服务器的web资源时,可以把各自的资源放在各自的Session中,当用户再去访问服务器中的其他web资源时,其他web资源再从用户各自的Session中取出数据为用户服务
Cookie基础
工作原理
HTTP是一种无状态的协议,服务器单从网络连接上无从知道客户的身份。Cookie就像是给客户端颁发的一个通行证。每个客户浏览器一个,无论谁访问必须携带自己的通行证。这样服务器就能从通行证上确认客户身份了
基本知识
Cookie 是保存在客户端的内容,每个浏览器有各自的编号,区分浏览器,信息不共享 写入的信息只能是文本文档。不支持中文,如要支持,必须重新转码 客户端可以阻止服务器写入 Cookie Cookie 不能跨域名。服务器只能拿自己的web应用写入的东西 浏览器的Cookie数量是有限的,大概是30-50个,每个为4K左右,每个浏览器各有不同
实现
客户端请求服务器,可以使用 response 对象 向客户端浏览器颁发一个Cookie。客户端会把Cookie保存起来。当浏览器再去访问该网站时,浏览器把请求的网址连同该Cookie一同提交给服务器。服务器检查Cookie,以此来辨认用户状态。服务器还可以根据需要修改 Cookie 的内容 可以在浏览器地址栏输入 javascript:alert(document.cookie)
获取所有的 Cookie 信息
Cookie操作
基本知识
java中把Cookie封装成 javax.servlet.http.Cookie
类。服务器通过操作Cookie类对象对客户端Cookie进行操作 Cookie对象使用 key-value 属性对的形式保存用户状态。一个 request 或者 response 同时使用多个Cookie 创建Cookie, 如果key和value为中文(Unicode字符),需要使用 URLEncoder 进行编码。如果为二进制,则需要使用Base64编码
1
2
3
4
5
6
7
8
9
// 1-创建Cookie
String key = "key";
String value = "cookie";
Cookie cookie = new Cookie(key, value);
System.out.println(cookie.getName());
// 1-2 创建中文, 使用URLEncoder编码 和 URLDecoder解码
Cookie cookieChina = new Cookie(URLEncoder.encode("中文", "UTF-8"), URLEncoder.encode("中文", "UTF-8"));
System.out.println(cookieChina);
向客户端设置Cookie : response.addCookie(Cookie cookie)
获取客户端的Cookie : Cookie[] cookies = request.getCookies()
(以Cookie[]数组形式返回) 获取Cookie 的name和value: String tName = Cookie.getName(); String tValue = Cookie.getValue()
1
2
3
4
5
6
7
8
9
10
// 3-从客户端获取
Cookie[] cookies = req.getCookies();
if (cookies != null && cookies.length < 0) {
for (Cookie tCookie : cookies) {
String tName = tCookie.getName();
String tValue = tCookie.getValue();
int maxAge = tCookie.getMaxAge();
System.out.println("Cookie的name : " + tName + " | Cookie的value : " + tValue + " | Cookie的有效期: " + maxAge);
}
}
设置Cookie的时间 : cookie.setMaxAge(maxAge); 值的类型
正数:表示Cookie在maxAge之后失效,cookieChina.setMaxAge(Integer.MAX_VALUE)-永久有效 负数(-1):临时Cookie,浏览器关闭即失效,不保存。-1是默认值 0-删除该Cookie。Cookie没有删除的方法,可以设置maxage为0;修改Cookie: 可以添加一个同名的Cookie用于覆盖前一个来修改Cookie
1
2
3
4
5
6
7
// 4-Cookie的删除与修改
Cookie cookie2 = new Cookie("time", "201401008");
// 删除Cookie,设置 setMaxAge =0 即可删除
cookie2.setMaxAge(0);
resp.addCookie(cookie2);
// 使用同名Cookie, 修改前一个 cookie 的作用
resp.addCookie(new Cookie("time", "20150909"));
Cookie的域名: Domain属性(IE是禁止的,会有安全问题)
1
2
3
4
5
6
7
8
9
// 5-Cookie的域名
Cookie cookie3 = new Cookie("time", "201401008");
//设置域名,意思是所有 `baidu.com`下的二级域名都可以访问。必须以“.”开始
cookie3.setDomain(".baidu.com");
//设置所有路径都可以使用
cookie3.setPath("/");
//设置有效期
cookie3.setMaxAge(Integer.MAX_VALUE);
resp.addCookie(cookie3);
Cookie的路径: path属性
path属性决定了访问Cookie的路径,即contextPath路径。例如,如果只允许/JavaWeb/下的程序使用Cookie,可以这么写/Javaweb/
path属性必须以“/XXX/”这种形式,当只有 /
时,表示所有路径都可以访问该Cookie 一个Servlet/JSP 设置的cookie 只能被与这个Servlet/JSP在同一路径下或子路径下的Servlet/JSP访问。父路径和其他路径无法获取。如sesson/test/b.jsp可以获取到路径session/的 Cookie,而不能获取/session/b/的Cookie 这边的路径不是指文件的存放真实路径,而是指url路径,是指<url-pattern>
设置的路径
1
2
3
4
// 6-Cookie的路径
Cookie cookie4 = new Cookie("time","201401008");
cookie4.setPath("/Javaweb/");//只有Javaweb/下的可以访问
response.addCookie(cookie4);
Cookie的安全属性:secure属性 如果不希望Cookie在HTTP等非安全协议中传输,可以设置cookie的secure属性属性为true。浏览器只会在HTTPS和SSL等安全协议中传输此类Cookie
1
2
3
// 7-Cookie的安全属性
Cookie cookie5 = new Cookie("time","201401008");
cookie5.setSecure(true); //true,在HTTP中无法获取本Cookie
JS操作Cookie
原生JS
格式:在js中Cookie的格式:account=111; ssid=222; JSESSIONID=7E99EBDC3CA63AB849D1493FBCCC5159,类似于map类型 获取:document.cookie获取所有的Cookie 获取单个固定的Cookie,可以自己封装方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 获取Cookie
*/
var getCookie = function(name) {
var str = document.cookie;
if (!str || str.indexOf(name + "=") < 0)
return;
var cookies = str.split("; ");
for (var i = 0; i < cookies.length; i++) {
var cookie = cookies[i];
if (cookie.indexOf(name + "=") == 0) {
var value = cookie.substring(name.length + 1);
return decodeURI(value);
}
}
};
创建Cookie的方法,我们也可以封装成一个方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 设置Cookie
*/
var setCookie = function(name, value) {
// document.cookie = name + "=" + encodeURI(value);
var expires = (arguments.length > 2) ? arguments[2] : null;
var path = (arguments.length > 3) ? arguments[3] : null;
var domain = (arguments.length > 4) ? arguments[4] : null;
var secure = (arguments.length > 5) ? arguments[5] : false;
document.cookie = name
+ "="
+ encodeURI(value)
+ ((expires == null) ? "" : ("; expires=" + expires
.toGMTString()))
+ ((path == null) ? "" : ("; path=" + path))
+ ((domain == null) ? "" : ("; domain=" + domain))
+ ((secure == true) ? "; secure" : "");
};
Jquery 操作Cookie
需要下载 jquery.cookie.js
简单设置Cookie : 设置一个name-Cookie的名字,value-Cookie的值
1
$.cookie("name", "value");
设置其他参数
1
2
3
4
5
6
7
//设置完整的cookie
$.cookie("name", "sam-sho",{
expires : 10,//有效期,单位为天
path : "/",
domain : "jquery.com"
secrue : true
});
读取Cookie :读取名字为sam的Cookie
删除Cookie :删除名称为 sam 的Cookie,只要把value设置成null。注意:必须使用与之前设置的相同的路径(path)和域名(domain),才能正确删除
Cookie 实际案例
永久登录
LoginCookieServlet: 负责记录Cookie的servlet。Post提交表单后,servlet把写两个cookie写到客户端。一是账户的cookie,另一个是账户加密后的cookie。然后重定向到CheckCookieServlet,校验。一定要用重定向,确保客户端使用两次请求。由于加密的key是服务器独有的,是安全的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
public class LoginCookieServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* 密钥, 可以放配置文件
*/
private static final String KEY = ":https://sam-blog.gitee.io/";
@Override
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
/**
* post方法访问,处理永久登录
*/
@Override
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
String action = request.getParameter("action");
if ("login".equals(action)) {
String account = request.getParameter("account");
int timeout = new Integer(request.getParameter("timeout"));
// 把帐号连同密钥使用MD5后加密后保存, 这边加密略
String ssid = account + KEY;
// 把帐号保存到Cookie中 并控制有效期
// 中文需要编码
Cookie accountCookie = new Cookie("account", account);
accountCookie.setMaxAge(timeout);
// 把加密结果保存到Cookie中 并控制有效期
Cookie ssidCookie = new Cookie("ssid", ssid);
ssidCookie.setMaxAge(timeout);
// 账户Cookie
response.addCookie(accountCookie);
// 加密后账户的Cookie
response.addCookie(ssidCookie);
System.out.println(accountCookie.getName() + "++++++++" + accountCookie.getValue());
System.out.println(ssidCookie.getName() + "+++++++" + ssidCookie.getValue());
// 重定向到校验servlet
// 使用两个request,这样访问到校验的request就带有了本次response写入的cookie。
response.sendRedirect(this.getServletContext().getContextPath() + "/checkCookieServlet");
return;
}
}
}
LoginOutCookieServlet:注销的servlet,负责删除cookie。然后校验,其实可以和LoginCookieServlet进行合并
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
if ("logout".equals(action)) {
// 删除Cookie中的帐号
Cookie accountCookie = new Cookie("account", "");
accountCookie.setMaxAge(0);
// 删除Cookie中的加密结果
Cookie ssidCookie = new Cookie("ssid", "");
ssidCookie.setMaxAge(0);
response.addCookie(accountCookie);
response.addCookie(ssidCookie);
// 重定向到校验servlet
response.sendRedirect(this.getServletContext().getContextPath() + "/checkCookieServlet");
return;
}
CheckCookieServlet:负责校验 Cookie
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
public class CheckCookieServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* 密钥, 可以放配置文件
*/
private static final String KEY = ":https://sam-blog.gitee.io/";
@Override
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
/**
* 校验
*/
@Override
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
boolean loggin = false;
String account = null;
String ssid = null;
// 获取Cookie中的account与ssid
Cookie[] cookies = request.getCookies();
if (cookies != null) {
System.out.println("所有的Cookies : " + Arrays.asList(cookies));
for (Cookie cookie : cookies) {
if ((cookie.getName()).equals("account")) {
account = cookie.getValue();
}
if ((cookie.getName()).equals("ssid")) {
ssid = cookie.getValue();
}
}
}
if (account != null && ssid != null) {
// 如果加密规则正确, 则视为已经登录.加密略
loggin = ssid.equals(account + KEY);
}
System.out.println("是否匹配 : " + loggin);
request.getSession(true).setAttribute("loggin", loggin);
// 跳转到登录页面
request.getRequestDispatcher("/cookie/loginCookie.jsp").forward(request, response);
return;
}
}
loginCookie.jsp:显示页面
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
<%@ page language="java" pageEncoding="UTF-8" isErrorPage="false" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<c:set var="base" value="${pageContext.request.contextPath}"/>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>
<c:choose>
<c:when test="${loggin==true}">欢迎您回来</c:when>
<c:otherwise>请先登录</c:otherwise>
</c:choose>
</title>
</head>
<body>
<div align="center" style="margin: 10px;">
<fieldset>
<legend>
当前有效的 Cookie
</legend>
<script>
document.write(document.cookie);
</script>
</fieldset>
<fieldset>
<legend>
<c:choose>
<c:when test="${loggin ==true}">
欢迎您回来
</c:when>
<c:otherwise>
请先登录
</c:otherwise>
</c:choose>
</legend>
<c:choose>
<c:when test="${loggin==true}">
欢迎您, ${cookie.account.value }
<a href="${base}/loginOutCookieServlet?action=logout">注 销</a>
</c:when>
<c:otherwise>
<form action="${base}/loginCookieServlet?action=login" method="post">
<table>
<tr>
<td>
帐号:
</td>
<td>
<input type="text" name="account" style="width: 200px;">
</td>
</tr>
<tr>
<td>
密码:
</td>
<td>
<input type="password" name="password" style="width: 200px;">
</td>
</tr>
<tr>
<td>
有效期:
</td>
<td>
<input type="radio" name="timeout" value="-1" checked>
关闭浏览器即失效
<br/>
<input type="radio" name="timeout"
value="<%=30 * 24 * 60 * 60%>">
30天内有效
<br/>
<input type="radio" name="timeout"
value="<%=Integer.MAX_VALUE%>">
永久有效
<br/>
</td>
</tr>
<tr>
<td>
</td>
<td>
<input type="submit" value=" 登 录 " class="button">
</td>
</tr>
</table>
</form>
</c:otherwise>
</c:choose>
</fieldset>
</div>
</body>
</html>
使用Cookie统计PV、UV量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// 客户标识
private final String CUSTOMER_IDENTIFICATION = "CusID";
private String uvFlag;
/**
* cookie中获取用户唯一标识 采用uuid记录 存在获取不存在重新设置
*/
private void autoSetCookie() {
Cookie[] cookies = getRequest().getCookies();
int count = 0;
if (cookies != null) {
for (Cookie cookie : cookies) {
if (org.apache.commons.lang3.StringUtils.equals(
cookie.getName(), CUSTOMER_IDENTIFICATION)) {
uvFlag = cookie.getValue();//存入数据库,以便统计
break;
}
count++;
// 该cookie在cookie列表中不存在
if (count == cookies.length) {
autoSetCid();
}
}
} else {
autoSetCid();
}
}
/**
* 自动设置客户端唯一标识
*/
private void autoSetCid() {
uvFlag = UUID.randomUUID().toString().replaceAll("[-]", "");
Cookie cidCookie = new Cookie(CUSTOMER_IDENTIFICATION, uvFlag);
cidCookie.setDomain(PropertyFileUtil.get("domain"));//写在配置文件
cidCookie.setMaxAge(365 * 24 * 60 * 60);
cidCookie.setPath("/");
getResponse().addCookie(cidCookie);//向客户端写Cookie
}
参考
源码地址 Servlet学习笔记(六):Cookie