cookie/session/sessionStorage/localStorage/indexDB
-
cookie
- tcp三次握手之后就可以不停的去发请求,每次请求都是基于http协议的。http协议的特性是“无状态”,因为这个特性,服务器并不知道用户的状态(是不是第一次访问)。可以通过浏览器添加cookie,服务端也可以设置cookie。设置完cookie之后 每次请求都会携带上cookie,所以可以用cookie来识别用户
- cookie设置上之后 “每次请求”都会携带,会浪费流量(可能某个请求根本不需要校验信息,还是会把cookie携带上)
- cookie是在http header中的,所以数据不宜过大,如果过大可能会造成页面白屏
- cookie默认不能跨域 两个完全不同的域名 父子域(可以设置子域能拿到父域中的数据)
- 可以用cookie来做用户识别 cookie存在前端(存在安全问题,重要信息不能放在cookie里)
-
session
- 信息存在服务器里 默认浏览器是拿不到的
- session存放数据 原则上没有上限而且安全
- 是基于cookie的
- session默认都是存在内存中,如果服务器down了,session就丢失了=>存到数据库 数据库数据也可能丢失
-
jwt
- 服务器根据用户提供的信息生成一个令牌,这个令牌是独一无二的。每次带上令牌和用户信息,用用户信息再次生成令牌和用户信息进行对比
- 不能存储隐私
- token
-
sessionStorage 浏览器关闭就丢失 (不能跨域)
-
localStorage 浏览器关闭也不会丢失
-
indexDB 浏览器的数据库
-
静态资源如何优化
- 压缩 gzip
- 缓存
- localStorage存储js文件
cookie
- cookie的签名只是为了防止用户篡改数据,如果用户篡改过了,就丢弃掉。并不是为了安全
const Koa = require("koa");
const Router = require("@koa/router");
const querystring = require("querystring");
// 不能用md5 是公开的算法 会知道
// sha256(express) sha1(koa) 比md5多了个盐值 不知道密钥 基本撞库撞不到 更安全
const crypto = require("crypto");
const app = new Koa();
const secret = "mysecret"; // 密钥
const toBase64URL = (str) => {
return str.replace(/\=/g, '').replace(/\+/g, '-').replace(/\//,'_')
}
// 自实现ctx.cookies.set同样的功能
app.use(async (ctx, next) => {
const arr = [];
// 拓展ctx.my 相当于原有的ctx.cookies
ctx.my = {
set(key, value, options = {}) {
let optsArr = [];
if(options.domain) {
optsArr.push(`domain=${options.domain}`)
}
if(options.httpOnly) {
optsArr.push(`httpOnly=${options.httpOnly}`)
}
if(options.maxAge) {
optsArr.push(`max-age=${options.maxAge}`)
}
if(options.signed){ // 说明 为了安全 需要给数据签名(加盐)
// base64在传输的时候会把 + / = 做特殊处理
let sign = toBase64URL(crypto.createHmac('sha1',secret).update([key, value].join("=")).digest("base64"));
arr.push(`agesign=${sign}`);
}
arr.push(`${key}=${value};${optsArr.join(";")}`);
ctx.res.setHeader("Set-Cookie", arr);
},
get(key,options) {
/*
获取来使用的时候
ctx.my.get('age', {signed: true}) 表示要校验
*/
let cookieObj = querystring.parse(ctx.req.headers['cookie'], ';', '=');
if(options.signed){
// 上一次的签名
const bool = toBase64URL(cookieObj[`${key}sign`] == crypto.createHmac('sha1', mysecret).update(`${key}=${cookieObj[key]}`).digest('base64'))
if(bool){
return cookieObj[key];
}else{ // 签名失效
return 'error';
}
}
}
}
return next();
})
const router = new Router();
router.get("/write", async (stc) => {
// ctx.body = "ok";
// 浏览器添加cookie: document.cookie
// 服务器设置cookie
// 如果是多对值 用数组
// ctx.res.setHeader('Set-Cookie', 'name=zhuzhu');
// ctx.res.setHeader('Set-Cookie', 'age=28'); // 这样写会覆盖
ctx.res.setHeader('Set-Cookie', ['name=zhuzhu', 'age=28']);
// 默认是给当前域名添加cookie 设置domain会给指定域名设置cookie
ctx.res.setHeader('Set-Cookie', ['name=zhuzhu', 'age=28;domain=.zhuzhu.com;httpOnly=true']);
// 在浏览器对cookie里对键值对勾选httpOnly document.cookie就获取不到了
/*
cdn有一个好处: cdn是一个特殊域名 不会携带发送cookie
key cookie的key
value cookie的值
domain 域名
path 路径
expires或max-age 存活时间
httpOnly
xsrf 诱导用户点击一个图片 发请求通过url把本地的cookie传递给他自己的服务器 (通过document.cookie)
*/
// 上面设置的方式比较麻烦
ctx.cookies.set("name", "zhuzhu", {
domain: ".zhuzhu.com",
httpOnly: true,
maxAge: 10
});
ctx.cookies.set("age", "28");
// 因为设置的cookie在浏览器是可以更改的 这时候服务器再去读就会不安全,所以要给cookie加盐
ctx.cookies.set("age", "28",{signed : true});
ctx.body = "write ok";
});
router.get('/read', async (ctx) => {
// 获取浏览器发送过来的cookie
ctx.body = ctx.req.headers['cookie']
// 获取cookie的另一种方式 和ctx.cookies.set对应
ctx.body = ctx.cookies.get("name")
})
// 后面是指如果请求不支持 会报这个方法不认 “method not allowed”
app.use(router.routes()).use(router.allowedMethods());
app.listen(3000, function() {
console.log('server start 3000')
})
- koa中提供了现成的做法
-
const app = new Koa(); const secret = 'mysecret'
-
app.keys = [secret]
提供cookie用于签名的密钥 -
ctx.cookies.set('name', 'zhuzhu', {domain:'baidu.com', httpOnly:true})
-
ctx.cookies.set('age', '28', {signed: true})
-
ctx.cookies.get('age', {signed: true})
-
session
- session是基于cookie
- ssr前后端同构时 用session实现用户鉴别是最方便的
- koa-session
const Koa = require("koa");
const Router = require("@koa/router");
const uuid = require('uuid');
const app = new Koa();
const router = new Router();
const cardName = "zhuzhu"; //店铺名称
const session = {}; // session就是服务器的一个记账本 为了稍后能通过这个本找到具体信息
router.get('/wash', async (ctx) => {
const hasVisit = ctx.cookies.get(cardName, {signed: true});
if(hasVisit && session[hasVisit]) {
session[hasVisit].mny -= 100;
ctx.body="消费了100元"
}else{
const id = uuid.v4();
session[id] = {mny: 500};
ctx.cookies.set(cardName, id, {signed: true});
ctx.body = "成为本店会员,有500元"
}
})
// 后面是指如果请求不支持 会报这个方法不认 “method not allowed”
app.use(router.routes()).use(router.allowedMethods());
app.listen(3000, function() {
console.log('server start 3000')
})
jwt
-
为了鉴别身份 不考虑安全
-
token 可以在url/header/请求体
-
主流的方案都是token jwt = json web token session不方便共享
-
jwt 通过令牌来识别身份
-
非常像cookie的签名 服务器只存一个密钥 只是为了鉴别用户身份
-
最终开发会使用jsonwebtoken这个包(功能强大,默认支持一些令牌的过期处理)
-
生成令牌
const token = jwt.encode(user, secret);
-
反解
let user = jwt.decode(cts.get('Authorization').split(" ")[1], secret);
const Koa = require("koa");
const Router = require("@koa/router");
const jwt = require('jwt-simple');
const app = new Koa();
const router = new Router();
const secret = "mysecret"; // 密钥
router.get('/login', async (ctx) => {
// 这个数据在每次发过来请求的时候都会带上 所以数据不要太多
// 一般情况下用用户的id就可以
let user = {
id: "111",
name: "zhuzhu"
};
// 生成令牌
const token = jwt.encode(user, secret);
ctx.body = {
err: 0,
data: {
token,
user
}
}
})
router.get("/validate", async(ctx, next) => {
// token 可以放请求体里 也可以放header中
// ctx.get('Authorization') jwt的规范是 Authorization: Bearar token
try{
let user = jwt.decode(cts.get('Authorization').split(" ")[1], secret);
ctx.body = {
err: 0,
data: {
user
}
}
}catch(e) {
ctx.body = {
err: 1
}
}
})
// 后面是指如果请求不支持 会报这个方法不认 “method not allowed”
app.use(router.routes()).use(router.allowedMethods());
app.listen(3000, function() {
console.log('server start 3000')
})
// 生成的token由三段组成
// 第一段是固定的头 {typ: 'JWT', alg:'HS256'}
// 第二段是用户的内容
// 第三段是签名
const jwt = {
sign(content, secret) {
return this.toBase64URL(crypto.createHmac('sha256', secret).update(content).digest('base64'))
},
toBase64URL(str) {
return str.replace(/\=/g, '').replace(/\+/g, '-').replace(/\//g, '_')
},
base64urlUnescape(str) {
str += new Array(5 - str.length % 4).join('=');
return str.replace(/-/g, '+').replace(/_/g, '/')
},
toBase64(content) { // 将内容进行base64
return this.toBase64URL(Buffer.from(JSON.stringify(content)).toString('base64'))
},
encode(info, secret) {
// 这里面只是转成了base64 所以是可以解码的
const head = this.toBase64({typ:'JWT', alg:'HS256'});
const content = this.toBase64(info);
const sign = this.sign([head, '.', content].join(''), secret);
return `${head}.${content}.${sign}`
},
decode(token, secret) {
let [head, content, sign] = token.split(".");
let newSign = this.sign([head, content].join("."), secret);
if(newSign == sign) {
const str = Buffer.from(this.base64urlUnescape(content), 'base64').toString();
return JSON.parse(str);
}else{
throw new Error('用户更改了信息')
}
}
}