一文搞明白Cookie、Session与Token
前言
本文旨在系统梳理总结下Cookie、Session和Token
-
Cookie:存在客户端,用来解决客户端如何保存信息的问题
-
Session:存在服务端,来解决多用户问题,即每个客户端会对应一个session
-
Token:无状态且支持跨域,有效防御CSRF,解决了session依赖于单个Web服务器的问题
一、COOKIE
1、Cookie 简介
HTTP 协议是一种无状态协议,即每次服务端接收到客户端的请求时,都是一个全新的请求,服务器并不知道客户端的历史请求记录;Session 和 Cookie 的主要目的就是为了弥补 HTTP 的无状态特性
-
当用户第一次访问服务器时,服务器可以在响应信息(response)中增加Set-Cookie响应头,将信息以Cookie为载体发送给浏览器
-
浏览器接收到服务器发送来的Cookie信息,就会将他保存在浏览器的缓冲区内
-
这样,当浏览器再次访问服务器时,就会将Cookie放在请求消息中,Web服务器就可以通过request中的用户信息来分辨此次请求是由哪个用户发起的
Cookie定义
网站向访问电脑写入的小文本,大多数是4KB,记录用户ID、密码、停留时间等信息
Cookie作用
-
会话管理:登陆、购物车、游戏得分或者服务器应该记住的其他内容
-
个性化:用户偏好、主题或者其他设置
-
追踪:记录和分析用户行为
Cookie分类
-
内存Cookie
由浏览器维护,保存在内存中,浏览器关闭就消失 -
硬盘Cookie
保存在硬盘里,有个过期时间
2、Cookie 格式
Set-Cookie: "<name>=<value>[;domain=<domain_name>][;path=<some_path>][;expires=<date>][;<Max-Age>=<age>][;HttpOnly][;secure]"
其中name=value
是必选项,其它都是可选项
Cookie的主要构成如下:
-
Set-Cookie
:HTTP响应头,服务端通过此HTTP头向客户端发送Cookie -
name
:一个唯一确定的cookie名称。通常来讲cookie的名称不含分号、逗号和空格等字符,且不区分大小写 -
domain
:cookie对于哪个域是有效的。所有向该域发送的请求中都会包含这个cookie信息。这个值可以包含子域(如:yq.aliyun.com),也可以不包含它(如:.aliyun.com,则对于aliyun.com的所有子域都有效)。如果缺省,值为Web服务器的域名 -
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。注意这个值虽然在脚本中不可获取,但仍然在浏览器安装目录中以文件形式存在。这项设置通常在服务器端设置,用来防御XSS -
secure
:安全标志,指定后,只有在使用SSL链接时候才能发送到服务器,如果是HTTP链接则不会传递该信息。就算设置了secure 属性也并不代表他人不能看到你机器本地保存的 cookie 信息,所以不要把重要信息放cookie就对了
3、Cookie 读写
Java、JavaScript、PHP、ASP.NET都可以读写Cookie
常用API
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的name -
setValue(String value)
与getValue
方法:设置和获取cookie的value
一个模板
public 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", "[email protected]");
// 发送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;
}
4、Cookie 编码
cookie中存储中文会出现中文乱码,需要对value进行额外的编码
base64编码
存储:Base64.getEncoder().encodeToString(content.getBytes("utf-8"));
读取:new String(Base64.getDecoder().decode(cookie.getValue()),"utf-8")
URLEncoder类
存储:Cookie cookie = new Cookie("userName", URLEncoder.encode("你好世界", "UTF-8"));
读取:URLDecoder.decode(cookie.getValue(), "UTF-8")
二、SESSION
1、Session 简介
客户端请求服务端,服务端会为这次请求开辟一块内存空间,这个对象便是 Session 对象,存储结构为 ConcurrentHashMap
Session 弥补了 HTTP 无状态特性,服务器可以利用 Session 存储客户端在同一个会话期间的一些操作记录
为了防止服务器端的session过多导致内存溢出,web服务器默认会给每个session设置一个有效期, (30分钟)若有效期内客户端没有访问过该session,服务器就认为该客户端已离线并删除该session
Session 原理
-
服务器第一次接收到请求时,开辟了一块 Session 空间(创建了Session对象),同时生成一个 sessionId ,并通过响应头的
Set-Cookie:JSESSIONID=XXXXXXX
命令,向客户端发送要求设置 Cookie 的响应 -
客户端收到响应后,在本机客户端设置了一个
JSESSIONID=XXXXXXX
的 Cookie 信息,该 Cookie 的过期时间为浏览器会话结束 -
接下来客户端每次向同一个网站发送请求时,请求头都会带上该 Cookie信息(包含 sessionId ), 然后,服务器通过读取请求头中的 Cookie 信息,获取名称为
JSESSIONID
的值,得到此次请求的sessionId
保存sessionID的方式
-
cookie中
通过一个特殊的cookie,name为JSESSIONID
,value为服务器端的sessionId
但是当浏览器禁用cookie后,session就会失效 -
url重写
当浏览器Cookie被禁时用
把sessionId
附加在URL路径的后面:一种是作为URL路径的附加信息,另一种是作为查询字符串附加在URL后面。 -
response.encodeURL(String url)
用于对表单action和超链接的url地址进行重写 -
response.encodeRedirectURL(String url)
用于对sendRedirect方法后的url地址进行重写
2、Session 读写
常用API
-
getId()
方法:得到sessionid -
invalidate()
方法:让session立刻失效 -
getAttribute(String key)
:根据key获取该session中的value -
setAttribute(String key,Object value)
:往session中存放key-value -
removeAttribute(Stringkey)
:根据key删除session中的key-value -
getServletContext()
:得到ServletContext -
setMaxInactiveInterval(long timeout)
/getMaxInactiveInterval
:设置/获取session的最大有效时间 -
getCreationTime
方法:获取session的创建的时间 -
getLastAccessedTime
方法:获取session最后一次访问的时间 -
getSession()
:从HttpServletRequest中获取session
一个模板
session.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
1、Token 简介
Token定义
Token,可以翻译成"令牌",本质上它是一个全局唯一的字符串,用来唯一识别一个客户端
但它不像cookie和session一样是一种web规范
可以认为他是借鉴了cookie和session工作的原理,进而延伸出来的一种维持用户会话状态的机制
Token原理
-
客户端使用用户名跟密码请求登录
-
服务端收到请求,去验证用户名与密码
-
验证成功后,服务端会签发一个 Token,再把这个 Token 发送给客户端
-
客户端收到 Token 以后可以把它存储起来,比如放在 Cookie 里或者 Local Storage 里
-
客户端每次向服务端请求资源的时候需要带着服务端签发的 Token
-
服务端收到请求,然后去验证客户端请求里面带着的 Token,如果验证成功,就向客户端返回请求的数据
-
因为token是被签名的,所以我们可以认为一个可以解码认证通过的token是由我们系统发放的,其中带的信息是合法有效的
Token特点
Token的特点,是创造出这东西的原因,也是它跟 Session & Cookie 这套机制的区别:
-
支持跨域访问:
Cookie是不允许垮域访问的,这一点对Token机制是不存在的,前提是传输的用户认证信息通过HTTP头传输 -
无状态(也称:服务端可扩展行):
Token机制在服务端不需要存储session信息,因为Token 自身包含了所有登录用户的信息,只需要在客户端的cookie或本地介质存储状态信息 -
更适用CDN:
可以通过内容分发网络请求你服务端的所有资料(如:javascript,HTML,图片等),而你的服务端只要提供API即可 -
去耦:
不需要绑定到一个特定的身份验证方案。Token可以在任何地方生成,只要在你的API被调用的时候,你可以进行Token生成调用即可 -
更适用于移动应用:
当你的客户端是一个原生平台(iOS, Android)时,Cookie是不被支持的(你需要通过Cookie容器进行处理),这时采用Token认证机制就会简单得多 -
CSRF:
因为不再依赖于Cookie,所以你就不需要考虑对CSRF(跨站请求伪造)的防范
2、JSON Web Token (JWT)
JSON Web Token(JWT),通常可以称为 Json 令牌,是RFC 7519
中定义的用于安全的将信息作为 Json 对象进行传输的一种规范,这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息
JWT格式
一个JWT实际上就是一个字符串,它由三部分组成:头部、载荷与签名
(1)头部(Header)
-
用于描述关于该JWT的最基本的信息:令牌的类型(即 JWT)和使用的签名算法
-
这也可以被表示成一个JSON对象
-
然后将其进行base64编码,得到第一部分
{
"typ": "JWT",
"alg": "HS256"
}
(2)载荷(Payload)
-
一般添加用户的相关信息或其他业务需要的必要信息
-
不建议添加敏感信息,因为该部分在客户端可解密
-
进行base64编码,得到第二部分
{ "iss": "JWT Builder",
"iat": 1416797419,
"exp": 1448333419,
"aud": "www.example.com",
"sub": "[email protected]",
"Email": "[email protected]",
"Role": [ "admin", "user" ]
}
注:针对JWT的攻击,通常是这部分受控导致
(3)签名(Signature)
-
需要base64加密后的header和base64加密后的payload使用"."连接组成的字符串
-
然后通过header中声明的加密方式进行加盐secret组合加密(在加密的时候,我们还需要提供一个密钥(secret),加盐secret组合加密)
-
然后就构成了jwt的第三部分。
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
最后,将这一部分签名也拼接在被签名的字符串后面,我们就得到了完整的JWT
注意:secret就是你服务端的私钥,在任何场景都不应该流露出去,一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了
JWT作用
-
认证(Authorization):这是使用 JWT 最常见的一种情况,一旦用户登录,后面每个请求都会包含 JWT,从而允许用户访问该令牌所允许的路由、服务和资源。单点登录是当今广泛使用 JWT 的一项功能,因为它的开销很小
-
信息交换(Information Exchange):JWT 是能够安全传输信息的一种方式。通过使用公钥/私钥对 JWT 进行签名认证。此外,由于签名是使用 head 和 payload 计算的,因此你还可以验证内容是否遭到篡改
结语
对Cookie、Session与Token做了个归纳
原文始发于微信公众号(红客突击队):一文搞明白Cookie、Session与Token
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论