本文旨在系统梳理总结下Cookie、Session和Token
HTTP 协议是一种无状态协议,即每次服务端接收到客户端的请求时,都是一个全新的请求,服务器并不知道客户端的历史请求记录;Session 和 Cookie 的主要目的就是为了弥补 HTTP 的无状态特性
网站向访问电脑写入的小文本,大多数是4KB,记录用户ID、密码、停留时间等信息
Set-Cookie: "<name>=<value>[;domain=<domain_name>][;path=<some_path>][;expires=<date>][;<Max-Age>=<age>][;HttpOnly][;secure]"
其中name=value
是必选项,其它都是可选项
Cookie的主要构成如下:
Set-Cookie
:HTTP响应头,服务端通过此HTTP头向客户端发送Cookiename
:一个唯一确定的cookie名称。通常来讲cookie的名称不含分号、逗号和空格等字符,且不区分大小写domain
:cookie对于哪个域是有效的。所有向该域发送的请求中都会包含这个cookie信息。path
:表示这个cookie影响到的路径,浏览器会根据这项配置,向指定域中匹配的路径发送cookie。如果值为/
,则Web服务器上所有WWW资源均可读取该Cookie。借助path和domain,可以有效控制Cookie被访问的范围expires
:失效时间,表示cookie何时应该被删除的时间戳(即何时应该停止向服务器发送这个cookie)。如果不设置这个时间戳,浏览器会在页面关闭时即将删除所有cookie;不过也可以自己设置删除时间。这个值是GMT时间格式,如果客户端和服务器端时间不一致,使用expires就会存在偏差。max-age
:与expires作用相同,用来告诉浏览器此cookie多久过期(单位是秒),而不是一个固定的时间点。正常情况下,max-age的优先级高于expires。HttpOnly
:告知浏览器不允许通过脚本document.cookie去更改这个值,同样这个值在document.cookie中也不可见。但在http请求张仍然会携带这个cookie。注意这个值虽然在脚本中不可获取,但仍然在浏览器安装目录中以文件形式存在。这项设置通常在服务器端设置,用来防御XSSsecure
:安全标志,指定后,只有在使用SSL链接时候才能发送到服务器,如果是HTTP链接则不会传递该信息。就算设置了secure 属性也并不代表他人不能看到你机器本地保存的 cookie 信息,所以不要把重要信息放cookie就对了Java、JavaScript、PHP、ASP.NET都可以读写Cookie
javax.servlet.http.Cookie
类来封装Cookie信息,它包含有生成Cookie信息和提取Cookie信息的各个属性的方法:
public Cookie(String name,String value)
setMaxAge(int longTime)
与getMaxAge
方法:设置和获取cookie的最大有效时长,setMaxAge(0)
表示删除磁盘上的某个cookie
注:cookie没有提供修改方法,当name一样时,覆盖原来的就算是更新了setPath
与getPath
方法:设置或读取Cookie的作用范围HttpServletResponse
接口:定义了一个addCookie(Cookie cookie)
方法,它用于在发送给浏览器的HTTP响应消息中增加一个Set-Cookie
响应头字段HttpServletRequest
接口:定义了一个getCookies
方法,它用于从HTTP请求消息的Cookie请求头字段中读取所有的Cookie项getName
方法:获取到cookie的namesetValue(String value)
与getValue
方法:设置和获取cookie的valuepublic class Cookiedemo1 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response) throws Exception {
// 创建Cookie对象,保存会话数据
// 如果发送中文,必须先使用URLEncoder进行加密
String name = URLEncoder.encode("张三", "utf-8");
Cookie c1 = new Cookie("name", name);
Cookie c2 = new Cookie("email", "chen@c.com");
// 发送cookie
response.addCookie(c1);
response.addCookie(c2);
// 浏览器下次访问获取已有的cookie
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
// cookie的名
String cname = cookie.getName();
// cookie的值
String cvalue = cookie.getValue();
// 解密
cvalue = URLDecoder.decode(cvalue, "utf-8");
System.out.println(cname + "=" + cvalue);
}
} else {
System.out.println("没有cookie信息!");
}
}
}
JavaScript 操作Cookie
操作方法
//创建一个Cookie,属性默认
document.cookie="password=123456";
//创建一个Cookie,设置属性:过期时间,path
document.cookie="attribute=pathDomain; expires=Thu, 14 Dec 2021 12:00:00 GMT; path=/";
//读取Cookie,返回name1=value1;...;namen=valuen 形式的字符串
document.cookie;
//修改Cookie,重新创建一遍,name相同会覆盖之前Cookie,修改了过期时间
document.cookie="attribute=pathDomain; expires=Thu, 14 Dec 2020 12:00:00 GMT; path=/";
//删除Cookie,可以指定过期时间为当前时间;注意:因为过期时间以浏览器的服务器时间为准,一般会有八小时时差
document.cookie="password=123; expires=" + new Date();
例子
//创建Cookie,并设置有效期(单位天)
function setCookie(cname,cvalue,exdays)
{
var d = new Date();
d.setTime(d.getTime()+(exdays*24*60*60*1000));
var expires = "expires=" + d.toGMTString();
document.cookie = cname + "=" + cvalue + "; " + expires;
}
//获取对应Cookie的值,通过字符串截取的方式
function getCookie(cname)
{
var name = cname + "=";
var ca = document.cookie.split(';');
for(var i=0; i<ca.length; i++)
{
var c = ca[i].trim();
if (c.indexOf(name)==0) return c.substring(name.length,c.length);
}
return "";
}
//删除Cookie,过期时间提前1天,解决时差问题
function delCookie(cname)
{
var d = new Date();
d.setTime(d.getTime()-(24*60*60*1000));
var expires = "expires=" + d.toGMTString();
document.cookie = cname + "=; " + expires;
}
使用cookie进行自动登录的服务器代码
//如果cookie中有customer信息,就放到session中
boolean checkCustomerCookie(HttpServletRequest request) throws UnsupportedEncodingException {
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
String cookieName = cookie.getName();
//如果有,解密后拿cookie中的值和数据库中的值进行比较
if (Constant.cookieCustomerKey.getName().equals(cookieName)){
String cookieValue = cookie.getValue();
String decry = EncrypUtils.Base64Util.decry(cookieValue);
Customer customer1 = JsonUtils.stringToObject(decry, Customer.class);
Customer customer2 = customerService.checkLogin(customer1.getPhoneNumber(), customer1.getPassword());
if (customer2 != null){
//放入到session中,放行
request.getSession().setAttribute("customer",customer2);
//自动登录时,更新用户的在线状态
Customer onlineCustomer = new Customer();
onlineCustomer.setId(customer2.getId());
onlineCustomer.setOnlineStatus(String.valueOf(Constant.ONLINESTATUS.getCode()));
customerService.updateById(onlineCustomer);
return true;
}
}
}
}
return false;
}
cookie中存储中文会出现中文乱码,需要对value进行额外的编码
存储:Base64.getEncoder().encodeToString(content.getBytes("utf-8"));
读取:new String(Base64.getDecoder().decode(cookie.getValue()),"utf-8")
存储:Cookie cookie = new Cookie("userName", URLEncoder.encode("你好世界", "UTF-8"));
读取:URLDecoder.decode(cookie.getValue(), "UTF-8")
客户端请求服务端,服务端会为这次请求开辟一块内存空间,这个对象便是 Session 对象,存储结构为 ConcurrentHashMap
Session 弥补了 HTTP 无状态特性,服务器可以利用 Session 存储客户端在同一个会话期间的一些操作记录
为了防止服务器端的session过多导致内存溢出,web服务器默认会给每个session设置一个有效期, (30分钟)若有效期内客户端没有访问过该session,服务器就认为该客户端已离线并删除该session
Set-Cookie:JSESSIONID=XXXXXXX
命令,向客户端发送要求设置 Cookie 的响应JSESSIONID=XXXXXXX
的 Cookie 信息,该 Cookie 的过期时间为浏览器会话结束JSESSIONID
的值,得到此次请求的 sessionId
JSESSIONID
,value为服务器端的sessionId
但是当浏览器禁用cookie后,session就会失效sessionId
附加在URL路径的后面:一种是作为URL路径的附加信息,另一种是作为查询字符串附加在URL后面。response.encodeURL(String url)
用于对表单action和超链接的url地址进行重写response.encodeRedirectURL(String url)
用于对sendRedirect方法后的url地址进行重写getId()
方法:得到sessionidinvalidate()
方法:让session立刻失效getAttribute(String key)
:根据key获取该session中的valuesetAttribute(String key,Object value)
:往session中存放key-valueremoveAttribute(Stringkey)
:根据key删除session中的key-valuegetServletContext()
:得到ServletContextsetMaxInactiveInterval(long timeout)
/getMaxInactiveInterval
:设置/获取session的最大有效时间getCreationTime
方法:获取session的创建的时间getLastAccessedTime
方法:获取session最后一次访问的时间getSession()
:从HttpServletRequest中获取sessionsession.setMaxInactiveInterval(2*3600);//session 保存俩小时
Cookie cookie=new Cookie("JSESSIONID",session.getId());//sessionid放到cookie中
cookie.setMaxAge(2*3600);//客户端的cookie也保存俩小时
cookie.setPath("/");//cookie作用范围设为整个项目
response.addCookie(cookie);//给浏览器返回该Cookie
Token,可以翻译成"令牌",本质上它是一个全局唯一的字符串,用来唯一识别一个客户端 但它不像cookie和session一样是一种web规范 可以认为他是借鉴了cookie和session工作的原理,进而延伸出来的一种维持用户会话状态的机制
Token的特点,是创造出这东西的原因,也是它跟 Session & Cookie 这套机制的区别:
JSON Web Token(JWT),通常可以称为 Json 令牌,是RFC 7519
中定义的用于安全的将信息作为 Json 对象进行传输的一种规范,这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息
一个JWT实际上就是一个字符串,它由三部分组成:头部、载荷与签名
(1)头部(Header)
{
"typ": "JWT",
"alg": "HS256"
}
(2)载荷(Payload)
{ "iss": "JWT Builder",
"iat": 1416797419,
"exp": 1448333419,
"aud": "www.example.com",
"sub": "aaa@example.com",
"Email": "aaa@example.com",
"Role": [ "admin", "user" ]
}
注:针对JWT的攻击,通常是这部分受控导致
(3)签名(Signature)
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
最后,将这一部分签名也拼接在被签名的字符串后面,我们就得到了完整的JWT
注意:secret就是你服务端的私钥,在任何场景都不应该流露出去,一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了
对Cookie、Session与Token做了个归纳
红客突击队于2019年由队长k龙牵头,联合国内多位顶尖高校研究生成立。其团队从成立至今多次参加国际网络安全竞赛并取得良好成绩,积累了丰富的竞赛经验。团队现有三十多位正式成员及若干预备人员,下属联合分队数支。红客突击队始终秉承先做人后技术的宗旨,旨在打造国际顶尖网络安全团队。