一文搞明白Cookie、Session与Token

admin 2022年8月31日14:19:32安全闲碎评论5 views8905字阅读29分41秒阅读模式



一文搞明白Cookie、Session与Token


前言

本文旨在系统梳理总结下Cookie、Session和Token

  • Cookie:存在客户端,用来解决客户端如何保存信息的问题

  • Session:存在服务端,来解决多用户问题,即每个客户端会对应一个session

  • Token:无状态且支持跨域,有效防御CSRF,解决了session依赖于单个Web服务器的问题

一、COOKIE

1、Cookie 简介

HTTP 协议是一种无状态协议,即每次服务端接收到客户端的请求时,都是一个全新的请求,服务器并不知道客户端的历史请求记录;Session 和 Cookie 的主要目的就是为了弥补 HTTP 的无状态特性

一文搞明白Cookie、Session与Token

  • 当用户第一次访问服务器时,服务器可以在响应信息(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

一文搞明白Cookie、Session与Token

javax.servlet.http.Cookie类来封装Cookie信息,它包含有生成Cookie信息和提取Cookie信息的各个属性的方法:

  • public Cookie(String name,String value)

  • setMaxAge(int longTime)getMaxAge方法:设置和获取cookie的最大有效时长,setMaxAge(0) 表示删除磁盘上的某个cookie
    注:cookie没有提供修改方法,当name一样时,覆盖原来的就算是更新了

  • setPathgetPath方法:设置或读取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,设置属性:过期时间,pathdocument.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

一文搞明白Cookie、Session与Token

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实际上就是一个字符串,它由三部分组成:头部、载荷与签名

一文搞明白Cookie、Session与Token
(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" ] }

一文搞明白Cookie、Session与Token
注:针对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做了个归纳




红客突击队于2019年由队长k龙牵头,联合国内多位顶尖高校研究生成立。其团队从成立至今多次参加国际网络安全竞赛并取得良好成绩,积累了丰富的竞赛经验。团队现有三十多位正式成员及若干预备人员,下属联合分队数支。红客突击队始终秉承先做人后技术的宗旨,旨在打造国际顶尖网络安全团队。


原文始发于微信公众号(红客突击队):一文搞明白Cookie、Session与Token

特别标注: 本站(CN-SEC.COM)所有文章仅供技术研究,若将其信息做其他用途,由用户承担全部法律及连带责任,本站不承担任何法律及连带责任,请遵守中华人民共和国安全法.
  • 我的微信
  • 微信扫一扫
  • weinxin
  • 我的微信公众号
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年8月31日14:19:32
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                  一文搞明白Cookie、Session与Token http://cn-sec.com/archives/1265903.html

发表评论

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: