Cookie与Session

并不是真实项目案例,仅为个人学习笔记,仅供学习参考javascript

Cookie简介

为何有cookie

如下内容摘自维基百科:html

为何有Cookie?
由于HTTP协议是无状态的,即服务器不知道用户上一次作了什么,这严重阻碍了交互式Web应用程序的实现。
在典型的网上购物场景中,用户浏览了几个页面,买了一盒饼干和两瓶饮料。
最后结账时,因为HTTP的无状态性,不经过额外的手段,服务器并不知道用户到底买了什么。
因此Cookie就是用来绕开HTTP的无状态性的“额外手段”之一。
服务器能够设置或读取Cookies中包含信息,借此维护用户跟服务器会话中的状态。

在刚才的购物场景中,当用户选购了第一项商品,服务器在向用户发送网页的同时,还发送了一段Cookie,记录着那项商品的信息。
当用户访问另外一个页面,浏览器会把Cookie发送给服务器,因而服务器知道他以前选购了什么。
用户继续选购饮料,服务器就在原来那段Cookie里追加新的商品信息。
结账时,服务器读取发送来的Cookie就好了。

Cookie另外一个典型的应用是当登陆一个网站时,网站每每会请求用户输入用户名和密码,而且用户能够勾选“下次自动登陆”。
若是勾选了,那么下次访问同一网站时,用户会发现没输入用户名和密码就已经登陆了。
这正是由于前一次登陆时,服务器发送了包含登陆凭据(用户名加密码的某种加密形式)的Cookie到用户的硬盘上。
第二次登陆时,若是该Cookie还没有到期,浏览器会发送该Cookie,服务器验证凭据,因而没必要输入用户名和密码就让用户登陆了。
复制代码

以上内容是维基百科对Cookie的解释,为何有Cookie? 说白了: 由于浏览器与人须要一种交互,Cookie则实现了这种交互;服务器经过向用户发送Cookie给予了用户某种“权限”,这比如是一张门票,当浏览器携带着“门票”向服务器发起请求时,服务器经过验证“门票”信息就能够赋予用户某种“权限”。接下来我用注册与登录的示例来简单地介绍下 Cookie的具体应用场景。前端

注册登录

代码连接
java

本案例中,注册登录作的并不全面,可是基本思想和真实的注册登录还算吻合。案例中并无真实的数据库,而是使用了json文件充当数据库,json文件中仅有一个数组,用来存储用户的信息。当用户注册成功后,则将用户的帐号密码存储到咱们的"数据库"中,在此以前须要经过前端校验代码以及后端的校验代码,本案例中仅有前端的校验,然后端校验代码并无在案例中展现。在校验成功时,页面会由注册界面自动跳转到登录界面,而且用户的信息会被添加到json文件中,若是用户再次使用相同的邮箱进行注册时,界面则会提示“用户已存在”的信息。示例效果以下:
node


若是注册成功,json文件中则会新增用户的信息:


再次使用该邮箱进行注册时:


跳转到登录界面后,若是用户输入的邮箱及密码与数据库的数据信息匹配,则登录成功,页面会跳转至index页面,同时服务器会在返回给客户端的响应头中添加cookie,在cookie中存入了用户的信息:

if(isEmailMatch && isPasswordMatch){
    response.setHeader('Set-Cookie',[`mysite_email=${userMessage.email};Max-Age=300;HttpOnly`,`mysite_password=${userMessage.password};Max-Age=300;HttpOnly`]);
    response.statusCode = 200;
}else if(isEmailMatch && !isPasswordMatch){

    //  错误代码 401 : 未受权,登录失败
    response.statusCode = 401;
    response.setHeader('Content-Type','application/json;charset=utf-8');
    response.write(`
        {
            "errors":{
                "error":"passwordWrong"
            }
        }
    `);
}else if (!isEmailMatch){
    response.statusCode = 401;
    response.setHeader('Content-Type','application/json;charset=utf-8');
    response.write(`
        {
            "errors":{
                "error":"notRegister"
            }
        }
    `);
}
复制代码

本段代码中若是用户登录的邮箱及密码均正确则服务器会在响应头中“Set-Cookie”,本示例中演示了如何设置多个cookie的方法即即:git

response.setHeader('Set-Cookie',['cookieName1=val1','cookieName2=val2']);
复制代码

同时在Set-Cookie中 Max-Age表明在 cookie 失效以前须要通过的秒数;设置了 HttpOnly 属性的 cookie 不能使用 JavaScript 经由 Document.cookie 属性、XMLHttpRequest 和 Request APIs 进行访问,以防范跨站脚本攻击(XSS)。若是须要了解具体详细的Set-Cookie设置信息能够参考 MDN的Set-Cookie。 在代码中,我也作出了一些最简单的后台判断,例如邮箱输入正确但密码输入错误时,服务器reponse一个json格式的数据流,前端代码经过判断会将错误信息反馈至页面上,部分代码以下:github

// ajax post
$.post('/login',userMessage)
    .then(
        // success
        ()=>{
        // 跳转至 index 页面
        window.location.href = '/'
        },
        // fail
        (request)=>{
            let errorMessage = request.responseJSON.errors.error;
                        
            if(errorMessage === 'passwordWrong'){
                errorText("password","密码错误");
                return
            }else if(errorMessage === 'notRegister'){
                errorText("email","无此用户信息");
                return
            }
        }
    )
复制代码

具体的效果以下:
密码错误:
ajax


无此用户信息:


在登录成功后,跳转至index主页,当浏览器向服务器发起get请求获取主页时,由于浏览器中携带了同域名的cookie信息,也就是携带了"入场票据",在服务器检查了"票据"后,响应给用户一个经处理过的页面:


若是咱们在未携带cookie信息访问index页面时,则会获取到这样的内容~


在点击退出登录后,页面会跳转到login页面,同时也会清除cookie的信息,固然咱们须要 先将后台代码的 Set-Cookie中的HttpOnly删除掉!! 若是不将HttpOnly删除,那么咱们是没法调用document.cookie的~ 退出登录button事件的代码以下:

// 退出登录时清空 cookie
$('#clearCookie').on('click',()=>{
    deleteCookie('mysite_password');
    deleteCookie('mysite_email');
    window.location.href = '/login';
})
function deleteCookie (name){
    // 设置让 cookie 无效的方法 让cookie的期限 expires 为 如今
    document.cookie = name + '=; expires='+new Date().toGMTString();
}
复制代码

咱们看到了Cookie在注册登录中的做用,当用户携带着某域名能够识别的Cookie时,服务器就能够经过用户的"票据"返回给用户不一样的页面,若是咱们未注册登录直接访问 index主页,得到到的信息与携带Cookie访问主页时得到的信息是大相径庭的。这就是Cookie应用最多的一个场景,可是这样作实际上也是有问题的,用户彻底能够仿造Cookie,只要用户有一点点的HTTP知识,修改Cookie伪造用户是彻底能够的,这就不免带来了安全问题。算法

Session

代码连接
数据库

Cookie带来的问题就是安全问题,它能够被任意地修改和伪造,因而Session就出现了,在说明什么是Session以前,咱们先试图去改进代码带来的安全性问题:
首先在 server.js 中 声明一个全局变量 mySession

var mySession = {};
复制代码

在用户登录成功以后,传给用户的cookie就不是用户的信息了,咱们传给用户一个sessionId,sessionId的值是一个随机数:

if(isEmailMatch && isPasswordMatch){
    // add Session
    let sessionId = Math.random();
    mySession[`${sessionId}`] = {'mysite_email':userMessage.email,'mysite_password':userMessage.password};

    response.setHeader('Set-Cookie',`sessionId=${sessionId}`);
    response.statusCode = 200;
}
复制代码

当用户访问index首页时,咱们须要对用户cookie中 sessionId的值进行判断:

if(path === '/'){
    let string = fs.readFileSync('./index.html','utf8');
    //  若是请求头中 携带 cookie 信息
    if(request.headers.cookie){
    let hash = {};
    request.headers.cookie.split('; ') .forEach((item)=>{ 
        if(item.split('=')[0] === 'sessionId' && mySession[item.split('=')[1]]){
            hash['mysite_email'] = mySession[item.split('=')[1]].mysite_email;
            hash['mysite_password'] = mySession[item.split('=')[1]].mysite_password;
        }
    });
    ... ...
}
复制代码

咱们仅仅修改了几行代码,cookie中再也不存入用户的隐私信息而是改成了sessionId 而sessionId对应的value是一串随机数,后台代码只须要验证这串随机数便可,这样依赖即使用户拥有修改cookie的能力,也无能为力,由于cookie存储在客户端,而session存储的隐私信息都是在服务器上的。cookie携带的"票证"仅仅是sessionId,服务器则是经过cookie携带的sessionId 来进行判断的。咱们来总结下:

Cookie:
1. 服务器经过Response-Header 的 Set-Cookie给客户端一串字符串
2. 客户端每次访问相同域名的网页时,带上这串字符串,服务器能够经过这串字符串去读取客户端的信息
3. 客户端要在一段时间内保存这个Cookie
4. Cookie默认在用户关闭页面后就会失效,可是后台代码能够任意设置Cookie的过时时间

Session:
1. 将SessionId经过Cookie发给客户端
2. 客户端访问服务器时,服务器读取SessionId
3. 服务器有一块内存保存了全部的Session
4. 经过SessionId能够获得对应用户的隐私信息
5. 这块内存就是服务器上的全部session

Session与Cookie的联系与区别:
1. Session是依赖于Cookie实现的
2. Cookie存储在客户端,Session则存储在服务器上

复制代码

LocalStorage与 SessionStorage

如今假设咱们的网站更新了,须要在首页提示用户更新的内容。咱们只须要很简单的代码就能够实现这一项功能:

// update message
alert('咱们的网站更新啦~更新的内容有:...');
复制代码

可是,从用户体验上来说,用户只但愿能被提醒一次,有什么方法能够作到呢?答案就是LocalStorage。MDN: LocalStorage.
与LocalStorage类似的是SessionStorage。两者的区别是:存储在 localStorage 的数据能够长期保留;而当页面会话结束——也就是说,当页面被关闭时,存储在 sessionStorage 的数据会被清除 。如下是MDN LocalStorage的示例:

  1. setItem
    localStorage.setItem('myCat', 'Tom');
    复制代码
  2. getItem
    let cat = localStorage.getItem('myCat');
    复制代码
  3. removeItem
    localStorage.removeItem('myCat');
    复制代码
  4. clear
    // 移除全部
    localStorage.clear();
    复制代码

咱们使用LocalStorage对本案例的更新提示功能作一个优化:

// update message
let already = localStorage.getItem('already',true);
if(!already){
    let updateMessage = localStorage.setItem('already',true);
    alert('咱们的网站更新啦~更新的内容有:...');
}
复制代码

这样一来,就能保证每一个用户都看到更新提示,且更新提示只出现了一次。
LocalStorage 及 SessionStorage 总结:

LocalStorage:
1. LocalStorage 同 HTTP无关
2. HTTP不会带上LocalStorage的值
3. 只有相同域名的页面才能互相读取LocalStorage(没有同源策略那么严格)
4. 每一个域名的LocalStorage 最大存储量 为 5Mb左右
5. 经常使用场景:记录有没有提示过用户
6. LocalStorage 永久有效,除非主动清除数据

SessionStorage与LocalStorage的区别:
1. SessionStorage在关闭页面后(会话结束)就会失效
2. LocalStorage则永久有效(除非用户本身清除)

Cookie与LocalStorage的区别:
1. LocalStorage和HTTP无关,Cookie则是服务器在
响应头中设置了Set-Cookie后传给浏览器,每次浏览器访问
服务器都要带上Cookie
2. Cookie的存储量通常有4kb LocalStorage则有 5mb左右
复制代码

Cache-Control 与 ETag

MDN: Cache-Control
Cache-Control能够对用户体验进行优化~ 假设我这个项目用到了Vue,(事实上我将Vue.js代码下载到了main.js当中,而且在node.js中新增了/main.js的路由,并在index页面中的script标签中引入了main.js),试想一下,用户每次刷新页面都会去向服务器发起请求 main.js这个文件 。在个人电脑上,请求的时间大约为:


一个文件请求到下载则须要13ms的时间,试想一下当有多个文件须要下载时,时间必然更为长久,那么有什么办法能够进行优化呢?首先可使用Cache-Control。

if(path === '/main.js'){
    let string = fs.readFileSync('./main.js','utf8');
    response.setHeader('Content-Type','application/javascript;charset=utf-8');
    response.setHeader('Cache-Control','max-age=31536000'); // 设置缓存时间为1年
    response.statusCode = 200;
    response.write(string);
    response.end();
}
复制代码

在/main.js的路由下,咱们添加了这一行代码:

response.setHeader('Cache-Control','max-age=31536000');
复制代码

当咱们再次刷新页面时:


能够看到在开发者工具的文件大小这一栏下:本来369KB的文件变为了"from memor cache"的字样,而时间则由本来的13ms变为了0 ms。这是由于,在第一次请求时main.js这个文件时,main.js已经被客户端的本地缓存了,当用户向index页面发其请求时,浏览器由于已经缓存了main.js这个文件,因此就不会再向服务器发起main.js文件的请求。那么问题来了,我虽然设置了 response.setHeader('Cache-Control','max-age=31536000');这行代码,而且用户能够缓存文件一年之久,可是若是我在中途更新了文件,用户怎么才能请求到呢?其实很简单,咱们只须要再index的script标签的src中添加query参数便可:

<script src="./main.js?version=2"></script>
复制代码

这样一来,每当咱们的 main.js 文件更新须要用户进行下载的时候,咱们在src的path后面使用query参数指定版本号便可。除了可使用Cache-Control外,咱们还可使用ETag来进行优化,那么什么是ETag呢?MDN的解释以下:

ETagHTTP响应头是资源的特定版本的标识符。
这可让缓存更高效,并节省带宽,由于若是内容没有改变,Web服务器不须要发送完整的响应。
而若是内容发生了变化,使用ETag有助于防止资源的同时更新相互覆盖(“空中碰撞”)。
复制代码

在了解到底什么是ETag以前,咱们首先须要了解下 md5 消息摘要算法。使用npm install md5 下载md5后,在node.js中添加MD5模块后咱们就可使用md5了,在文件中添加:var md5 = require('md5');便可。首先,先简单了解下md5的应用场景,MDN的解释以下:MD5已经普遍使用在为文件传输提供必定的可靠性方面。例如,服务器预先提供一个MD5校验和,用户下载完文件之后,用MD5算法计算下载文件的MD5校验和,而后经过检查这两个校验和是否一致,就能判断下载的文件是否出错。说白了md5的本质是用来检测文件下载是否一致的,当文件出现了丝毫的变更,经过md5算法获得的字符串也会出现很大的差别。示例代码以下:

if(path === '/main.js'){
    let string = fs.readFileSync('./main.js','utf8');
    response.setHeader('Content-Type','application/javascript;charset=utf-8');
    // md5 ETag 缓存
    let fileMD5 = md5(string);
    response.setHeader('ETag',fileMD5);
    if(request.headers['if-none-match'] === fileMD5){
        // 304: not modified
        response.statusCode = 304;
    }else{
        response.statusCode = 200;
        response.write(string);
    }
        response.end();
    }
复制代码

首先咱们将fs.readFileSync读取的文件字符串经过md5算法变成一个字符串长串,在响应头中设置了response.setHeader('ETag',fileMD5);,浏览器的请求头中会带有'if-none-match'这样一个属性,它的内容与示例中的fileMD5内容是同样的。第一次用户向服务器发起请求:


main.js文件大小为 366KB,请求到下载完成的时间为119ms,好,咱们刷新页面:


在main.js文件未改动的状况下,大小变为了182B,而且时间上也有了显著的缩短。仔细想想程序所做的事情:若是文件未发生变更 那么就向用户发送 304 即 not modified ;反之 则将 改动的文件响应给用户。那么看到这里,想必你已经知道了Cache-Control与ETag的缓存有什么区别了;个人总结以下:

如:response.setHeader('Cache-Control','max-age=31536000'); 
    的含义是请求到文件后,将文件缓存1年,当用户刷新页面时浏览器则不会再向
    服务器发起请求
对于 ETag的缓存则是:
    浏览器会向服务器发起请求,但服务器则会判断
    若是服务器发现要响应给浏览器的内容的md5字符串与request.headers['if-none-match']
    相等则说明服务器以前响应给用户的文件信息是一致的,若是不相等
    则说明须要从新响应给用户
    浏览器仍是会向服务器发起请求,但服务器不必定会响应给浏览器

复制代码

文章结束~感谢您的耐心阅读,若是有错误,还望批评指出。

相关文章
相关标签/搜索