Cookie

  • HTTP协议是无状态的,但在WEB应用中,在多个请求之间共享会话是非常必要的,所以出现了Cookie
  • cookie是为了辩别用户身份,进行会话跟踪而存储在客户端上的数据

服务器设置cookie:客户端第一次访问服务器时,会通过响应头向客户端发送Cookie,属性之间用分号空格分隔

客户端接收并保存cookie:客户端再请求服务器时,会携带Cookie至服务器端,而cookie本身就是一个请求的header

重要属性

通过修改本地hosts文件,模拟两个不同的域名。

  1. # hosts
  2. 127.0.0.1 a.echoyya.com
  3. 127.0.0.1 b.echoyya.com
属性 说明
name=value 键值对,可以设置要保存的 Key/Value
Domain 针对某个域名生效 可以跨父域和子域,默认是当前域名
expires/max-age cookie存活时间 ,expires 绝对时间, max-age 相对时间 单位秒
secure 当 secure 值为 true 时,cookie 在 HTTP 中是无效,只在https下生效
Path 表示 cookie 影响到的路径,默认是/都能被访问到。若路径不匹配时,浏览器则不发送这个Cookie
httpOnly 表示浏览器无法通过代码来获取,防止XSS攻击,但是可以通过手动修改控制台方式进行更改。

实现原理

npm install koa koa-router

  1. const Koa = require('koa');
  2. const Router = require('koa-router');
  3. const querystring = require('querystring') // 用于解析和格式化网址查询字符串
  4. const app = new Koa();
  5. const router = new Router();
  6. // koa 操作cookie 实现原理
  7. app.use(async (ctx, next) => {
  8. // 扩展一个设置cookie的方法
  9. let cookieArr = [];
  10. ctx.req.getCookie = function (key) {
  11. let cookies = ctx.req.headers['cookie']; // name=xx; age=yy => name=xx&age=yy
  12. let cookieObj = querystring.parse(cookies,'; ')
  13. return cookieObj[key] || ''
  14. }
  15. ctx.res.setCookie = function (key, value, options={}) {
  16. let args = []; // 每个cookie 属性集合
  17. options.domain && args.push(`domain=${options.domain}`);
  18. options.maxAge && args.push(`max-age=${options.maxAge}`);
  19. options.httpOnly && args.push(`httpOnly=${options.httpOnly}`);
  20. options.path && args.push(`path=${options.path}`);
  21. cookieArr.push(`${key}=${value}; ${args.join('; ')}`);
  22. ctx.res.setHeader('Set-Cookie', cookieArr); // 字符串数组
  23. }
  24. await next();
  25. })
  26. router.get('/read', async (ctx, next) => {
  27. // 自己封装
  28. ctx.body = ctx.req.getCookie('name') || 'empty';
  29. // koa 实现
  30. // ctx.body = ctx.cookies.get('name') || 'empty';
  31. // 原生用法
  32. // ctx.body = ctx.req.headers['cookie'] || 'empty'; // 请求头
  33. })
  34. router.get('/write', async (ctx, next) => {
  35. // 自己封装
  36. ctx.res.setCookie('name', 'nn', {domain: '.echoyya.com'}); // 限制可访问的域名
  37. ctx.res.setCookie('age', '12', {httpOnly: true,path:'/write'}); // 限制可访问的路径
  38. // koa 实现
  39. // ctx.cookies.set('name', 'nn', {domain: '.echoyya.com'});
  40. // ctx.cookies.set('age', '12', {httpOnly: true,path:'/write'});
  41. // 原生用法
  42. // ctx.res.setHeader('Set-Cookie','name=yy');
  43. // ctx.res.setHeader('Set-Cookie','age=15'); // 设置一个cookie,再次set cookie 会将上一次的覆盖
  44. // ctx.res.setHeader('Set-Cookie', ['name=yy; domain=.echoyya.com', 'age=15; path=/; max-age=10; httpOnly=true']); // 设置多个cookie时,可接受一个字符串数组
  45. ctx.body = 'write ok';
  46. })
  47. app.use(router.routes())
  48. app.listen(4000);

cookie签名实现原理

cookie通常由服务器产生,存在客户端,随着每次请求发送至服务器端,而前端存储数据可以被用户手动篡改,

因此可以给cookie签名使其相对安全, 根据cookie的内容产生一个标识,保留原有内容,每次请求检验签名,添加一个配置{ signed: true }

  1. const Koa = require('koa');
  2. const Router = require('koa-router');
  3. const querystring = require('querystring');
  4. const crypto = require('crypto');
  5. const app = new Koa();
  6. const router = new Router();
  7. app.keys = ['ya'];
  8. // base64Url 需要特殊处理 + = /
  9. const sign = value => crypto.createHmac('sha1',app.keys.join('')).update(value).digest('base64').replace(/\+/g,'-').replace(/\=/g,'').replace(/\//g,'_');
  10. app.use(async (ctx, next) => {
  11. let cookieArr = [];
  12. ctx.req.getCookie = function (key, options = {}) {
  13. let cookies = ctx.req.headers['cookie'];
  14. let cookieObj = querystring.parse(cookies,'; ')
  15. if(options.signed){
  16. // 传递过来的签名,和最新计算获得的结果一直,则说明未被修改
  17. if(cookieObj[key + '.sig'] === sign(`${key}=${cookieObj[key]}`)){
  18. return cookieObj[key];
  19. }else {
  20. return ''
  21. }
  22. }
  23. return cookieObj[key] || ''
  24. }
  25. ctx.res.setCookie = function (key, value, options = {}) {
  26. let args = [];
  27. let keyValue = `${key}=${value}`
  28. options.domain && args.push(`domain=${options.domain}`);
  29. options.maxAge && args.push(`max-age=${options.maxAge}`);
  30. options.httpOnly && args.push(`httpOnly=${options.httpOnly}`);
  31. options.path && args.push(`path=${options.path}`);
  32. options.signed && cookieArr.push(`${key}.sig=${sign(keyValue)}`); // 是否开启cookie签名
  33. cookieArr.push(`${keyValue}; ${args.join('; ')}`);
  34. ctx.res.setHeader('Set-Cookie', cookieArr); // 字符串数组
  35. }
  36. await next();
  37. })
  38. // app.keys required for signed cookies
  39. router.get('/visit', async (ctx, next) => {
  40. // Koa 实现
  41. // let count = ctx.cookies.get('visit',{ signed: true }) || 0;
  42. // let visitCount = Number(count) + 1;
  43. // ctx.cookies.set('visit', visitCount, { signed: true });
  44. // ctx.body = `you visit ${visitCount}`
  45. // 自己封装
  46. let count = ctx.req.getCookie('visit', { signed: true }) || 0;
  47. let visitCount = Number(count) + 1;
  48. ctx.res.setCookie('visit', visitCount, { signed: true });
  49. ctx.body = `ya visit: ${visitCount}`
  50. })
  51. app.use(router.routes())
  52. app.listen(3000);

注意事项

  • 可能被客户端篡改,使用前验证签名的合法性
  • 不要存储敏感数据,比如用户密码,账户余额
  • 每次请求都会自动携带cookie,尽量减少cookie的体积
  • 设置正确的domain和path,减少数据传输

Session

是另一种记录客户状态的机制,不同的是Cookie保存在客户端浏览器中,而session保存在服务器

在服务器存储用户对应的信息,服务器可以存储敏感信息,而session本身是基于cookie的且比cookie安全

同时session 没有持久化功能,需要配合数据库或者redis使用

实现原理

npm install uuid

  1. const Koa = require('koa');
  2. const Router = require('koa-router');
  3. const uuid = require('uuid');
  4. const app = new Koa();
  5. const router = new Router();
  6. app.keys = ['ya']
  7. const session = {}; // 用来存储用户和信息的映射关系,对浏览器不可见
  8. const cardName = 'connect_sig';
  9. router.get('/cut', async (ctx, next) => {
  10. let id = ctx.cookies.get(cardName, {signed:true});
  11. if(id && session[id]){
  12. session[id].mny -= 20;
  13. ctx.body = `mny:` + session[id].mny;
  14. }else{
  15. let cardId = uuid.v4();
  16. session[cardId] = { mny: 500 };
  17. ctx.cookies.set(cardName, cardId,{httpOnly:true,signed:true}); // cookie中只存一个标识,并没有真实的数据
  18. ctx.body = `mny 500`;
  19. }
  20. })
  21. app.use(router.routes())
  22. app.listen(3000);

JWT

JSON Web Token(JWT)是目前最流行的跨域身份验证解决方案,JWT 默认是不加密的,任何人都可以读到,所以不要把重要信息放在这个部分。

解决问题:session不支持分布式架构,无法支持横向扩展,只能通过数据库来保存会话数据实现共享。如果持久层失败会出现认证失败。

优点:服务器不保存任何会话数据,即服务器变为无状态,使其更容易扩展。

使用方式

  1. HTTP 请求的头信息Authorization字段里面 Authorization: Bearer <token>
  2. 如果是post请求也可以放在请求体中,取决于后端采用哪种认证方式
  3. 通过url传输 http://www.xxx.com/pwa?token=xxxxx,但是一般不建议这样使用,因为会存在连接分享导致安全隐患

组成

JWT包含了使用.分隔的三部分 Header.Payload.Signature

1. Header 头部

  1. { "alg": "HS256", "typ": "JWT"}
  2. // algorithm => HMAC SHA256
  3. // type => JWT

2. Payload内容 JWT 规定了7个官方字段

  1. iss (issuer):签发人
  2. exp (expiration time):过期时间
  3. sub (subject):主题
  4. aud (audience):受众
  5. nbf (Not Before):生效时间
  6. iat (Issued At):签发时间
  7. jti (JWT ID):编号

3. Signature 签名

对前两部分的签名,防止数据篡改 HMACSHA256(base64UrlEncode(header) + "." +base64UrlEncode(payload),secret)

实际应用

npm install koa-bodyparser jwt-simple

  1. const Koa = require('koa');
  2. const Router = require('koa-router');
  3. const bodyParser = require('koa-bodyparser');
  4. const jwt = require('jwt-simple');
  5. const app = new Koa();
  6. const router = new Router();
  7. app.use(bodyParser())
  8. // 登陆
  9. router.post('/login', async (ctx, next) => {
  10. let { username, password } = ctx.request.body;
  11. if (username == 'admin' && password == 'admin') {
  12. // let token = jwt.encode(username,'ya'); // jwt-simple 实现
  13. let token = myJwt.encode(username,'ya'); // 自己实现
  14. ctx.body = {
  15. code: 200,
  16. data: {
  17. token,
  18. username
  19. }
  20. }
  21. }
  22. })
  23. // 验证是否有权限
  24. router.get('/validate', async (ctx, next) => {
  25. let authorization = ctx.get('authorization');
  26. if(authorization){
  27. let [,token] = authorization.split(' ');
  28. try{
  29. // let r = jwt.decode(token,'ya'); // jwt-simple 实现
  30. let r = myJwt.decode(token,'ya'); // 自己实现
  31. ctx.body = {
  32. code:200,
  33. data:{
  34. username:r
  35. }
  36. }
  37. }catch{
  38. ctx.body = {
  39. code:401,
  40. data:'token已失效'
  41. }
  42. }
  43. }
  44. })
  45. app.use(router.routes())
  46. app.listen(3000);

实现原理

  1. // token组成部分为为三段,1,固定格式表示类型 2,内容 3 签名
  2. // 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.ImFkbWluIg.xJ0xCP2SXSaJSC-Q1PXuByHdJlBUHCNjdGRU4XW0abU'
  3. const myJwt = {
  4. sign(str,secret){
  5. str = require('crypto').createHmac('sha256',secret).update(str).digest('base64');
  6. return this.toBase64Escape(str);
  7. },
  8. toBase64(content){ // 对象转base64 需要先转为buffer => base64
  9. let source = typeof content === 'string' ? content : JSON.stringify(content);
  10. return this.toBase64Escape(Buffer.from(source).toString('base64'));
  11. },
  12. toBase64Escape(base64){
  13. return base64.replace(/\+/g,'-').replace(/\//g,'_').replace(/\=/g,'');
  14. },
  15. encode(username, secret){ // 转为base64并不是为了安全,只是为了可以在网络中传输
  16. let header = this.toBase64({typ:'JWT',alg:'HS256'});
  17. let content = this.toBase64(username);
  18. let sign = this.sign([header, content].join('.'),secret)
  19. return header + '.' + content + '.' + sign
  20. },
  21. base64urlUnescape(str){
  22. str += new Array(5 - str.length % 4).join('=');
  23. return str.replace(/\-/g, '+').replace(/_/g, '/');
  24. },
  25. // 相同的内容生成的签名相同,可以添加一些过期时间等信息
  26. // 通过内容生成了一个签名,反之通过校验签名。即可得知内容是否发生改变
  27. decode(token,secret){
  28. let [header, content, sign] = token.split('.');
  29. let newSign = this.sign([header, content].join('.'),secret);
  30. if(sign === newSign){ // 此时内容line2中的数据一定是可靠的
  31. return Buffer.from(this.base64urlUnescape(content),'base64').toString();
  32. }else{
  33. throw new Error('token已失效')
  34. }
  35. }
  36. }

前端存储方式 cookie session localStorage sessionStorage token 区别

  • cookie特点可以每次请求的时候自动携带,可以实现用户登录功能. 使用cookie来识别用户,1.如果单纯的使用cookie,不建议存放敏感信息,如果被劫持到。(cookie是存在客户端,并不安全,用户可以自行篡改)2.每个浏览器一般对请求头都有大小限制 cookie 不能大于4k,如果cookie过大,会导致页面白屏。 每次访问服务器都会浪费流量(合理设置cookie);
  • session:在服务器存储用户对应的信息,服务器可以存储敏感信息,而session本身是基于cookie的且比cookie安全;
  • localStorage:关掉浏览器数据依然存在,除非手动清楚,有大小限制约5M,发送请求不会携带;
  • sessionStorage:页面不关闭就不会销毁 (用途:如单页应用访问时存储滚动条地址)
  • token -> jwt -> jsonwebtoken 不需要服务器存储,没有跨域限制,不建议存储敏感信息

Cookie、Session、JWT在koa中的应用及实现原理的更多相关文章

  1. Blazor和Vue对比学习(进阶2.2.4):状态管理之持久化保存(2),Cookie/Session/jwt

    注:本节涉及到前后端,这个系列的对比学习,还是专注在前端Vue和Blazor技术,所以就不撸码了,下面主要学习概念. 我们知道,Http是无状态协议,客户端请求服务端,认证一次后,如果再次请求,又要重 ...

  2. 存储机制 cookie session jwt token

    cookieCookie的诞生 由于HTTP协议是无状态的,而服务器端的业务必须是要有状态的.Cookie诞生的最初目的是为了存储web中的状态信息,以方便服务器端使用.比如判断用户是否是第一次访问网 ...

  3. koa 基础(十六)koa 中 session 的使用

    1.app.js /** * koa 中 session 的使用 * 1.npm install koa-session --save * 2.const session = require('koa ...

  4. 一文搞懂Cookie,Session,Token,JWT

    HTTP协议是无状态的,无状态意味着,服务器无法给不同的客户端响应不同的信息.这样一些交互业务就无法支撑了.Cookie应运而生. Cookie 通过F12开发者工具,先瞅瞅Cookie的颜值 从图中 ...

  5. session,cookie,jwt的简单使用

    cookie的使用 https://blog.csdn.net/qq_58168493/article/details/122492358 session的使用 https://blog.csdn.n ...

  6. 在IE浏览器中iframe跨域访问cookie/session丢失的解决办法

    单点登录需要在需要进入的子系统B中添加一个类,用于接收A系统传过来的参数: @Action(value = "outerLogin", results = { @Result(na ...

  7. cookie,session 的概念以及在django中的用法,以及cbv装饰器用法

    cookie的由来: 大家都知道HTTP协议是无状态的. 无状态的意思是每次请求都是独立的,它的执行情况和结果与前面的请求和之后的请求都无直接关系,它不会受前面的请求响应情况直接影响,也不会直接影响后 ...

  8. Cookie和Session在Node.JS中的实践(三)

    Cookie和Session在Node.JS中的实践(三) 前面作者写的COOKIE篇.SESSION篇,算是已经比较详细的说明了两者间的区别.机制.联系了.阅读时间可能稍长,因为作者本身作图也做了不 ...

  9. Cookie和Session在Node.JS中的实践(二)

    Cookie和Session在Node.JS中的实践(二) cookie篇在作者的上一篇文章Cookie和Session在Node.JS中的实践(一)已经是写得算是比较详细了,有兴趣可以翻看,这篇是s ...

随机推荐

  1. .NET Core/.NET5/.NET6 开源项目汇总7:电商项目

    系列目录     [已更新最新开发文章,点击查看详细] 谈起.NET/.NET Core的企业级实战案例,电商项目是典型代表.其中高负载.高并发.高可用性等问题是考核.NET技术性能的重要指标.下面整 ...

  2. Centos7安装部署搭建gitlab平台、汉化

    Centos7安装部署搭建gitlab平台.汉化 安装环境要求:内存不要小于4G,否则后期web界面可能会报错 一.准备工作 1.1 查看系统版本 首先查询系统版本,下载Gitlab的对应版本 [ro ...

  3. Kubernetes的亲和性和反亲和性

    节点亲缘性规则可以影响pod被调度到哪个节点.但是,这些规则只影响了pod和节点之间的亲缘性.然而,有些时候也希望能有能力指定pod自身之间的亲缘性. 举例来说,想象一下有一个前端pod和一个后端po ...

  4. css input 设置只读样式

    input[readonly]{ background-color: #EEEEEE !important; }

  5. AD设计中地铜突然消失且无法选中删除的解决办法

    作者:struct_mooc 博客地址: https://www.cnblogs.com/structmooc/p/14984466.html   前几天在设计一块电路板的时候,已经全部设计完了!但是 ...

  6. gitlab 设置tag保护及取消tag保护功能

    用gitlab管理员登录系统 进入项目->设置->Repository 设置项目的Tag保护 效果展示 取消Tag保护 效果展示

  7. DRF之权限和频率限制

    一.权限 权限可以限制用户对视图的访问和对具体数据对象的访问. 在执行视图的dispatch方法前,会先进行视图访问权限的判断 在通过get_object获取对象时,会进行模型对象访问权限的判断 源码 ...

  8. 『无为则无心』Python函数 — 26、Python函数参数的传递方式

    目录 1.位置参数 2.关键字参数 3.缺省参数(默认参数) 4.不定长参数(可变参数) (1)包裹位置传递 (2)包裹关键字传递 5.位置参数.默认参数.可变参数的混合使用 6.拓展:参数解包 提示 ...

  9. PHP解决并发问题的几种实现(转)

      对于商品抢购等并发场景下,可能会出现超卖的现象,这时就需要解决并发所带来的这些问题了 在PHP语言中并没有原生的提供并发的解决方案,因此就需要借助其他方式来实现并发控制. 方案一:使用文件锁排它锁 ...

  10. php+redis+lua实现分布式锁(转载)

    以下是我在工作中用到的类,redis加锁两种方式,解锁为了保证原子性所以只用lua+redis的方式 缺陷:虽然死锁问题解决了,但业务执行时间超过锁有效期还是存在多客户端加锁问题.不过,这个类已经满足 ...