前言
首先一切原因都要从http是无状态协议谈起,http的每一次请求都是独立的请求,http是基于tcp协议的。如果http是有状态的就没它们三兄弟什么事情了,接下来跟大家彻底揉碎了一点一点喂进你们嘴里。
什么是无状态协议?
用最简单的话概述就是: 好比我们登录了淘宝网站,我们要进行购物,首先我们要进行登录才能完成后续的一系列操作,登录完购买完了东西,我们关闭了网页,当我们再次进入淘宝网的时候,这个时候我们又要重新登录,因为http每一次请求都是独立的请求,且你没有任何凭证证明你登录过,所以你又要重新登录,所以我们http是一个无状态的协议。
1.Cookie
定义: Cookie(复数形态:Cookies),又称“小甜饼”。类型为“小型文本文件”,指某些网站为了辨别用户身份而储存在用户本地终端(Client Side)上的数据(通常经过加密)。
分类: 分为内存Cookie和硬盘Cookie。
1.Cookie,有设置过期时间且过期时间不等于0,不能小于负数,时间到期或者手动进行清除,这种可以称之为硬盘Cookie。
2.内存Cookie,没有设置过期时间,当浏览器关闭后Cookie会随着浏览器的关闭并且进行自动清除。
作用域: Cookie是可以在客户端设置,也可以在服务端设置的,每次请求浏览器就会自动携带cookie在headers中(要包含在所处的作用域中),所处的作用域就分为两类。一类是域名,设置是否要求子域名上也携带cookie(www.baidu.com
,fanyi.baidu.com
,baidu
前面的就可以称之为子域)。另一类就是path,也称之为路由作用域(http:localhost:8000/login
,比如指定 /login
这个作用域下)。
客户端代码:
<button>点我</button>
<script>
document.querySelector("button").onclick = () =>
(document.cookie = "age=20;name=ice;max-age=5;");
</script>
复制代码
客户端设置max-age的时候这个是一个内存cookie,5s以后内存会失效。
服务端代码:
const Koa = require('koa')
const Router = require('koa-router')
const app = new Koa()
const loginRouter = new Router({prefix:"/login"})
loginRouter.get('/',(ctx,next) => {
ctx.cookies.set("name","ice",{
maxAge:5000,
})
ctx.body = "进行登录!"
})
app.use((ctx,next) => {
if(ctx.request.url === '/') {
const name = ctx.cookies.get('name');
ctx.body = `欢迎${name}登录`
}else{
next()
}
})
app.use(loginRouter.routes())
app.use(loginRouter.allowedMethods())
app.listen(3000,() => {
console.log("服务启动成功")
})
复制代码
这里使用的koa以及koa-router来测试cookie,如果这库不太清楚的话可以看下我前面的文章, 一次读懂Koa。
服务端设置一个maxAge 5s的过期时间,这里是毫秒为单位(5 * 1000),当我访问/login的时候进行登录,设置name=ice这个用户名,当我访问首页的时候,也就是没有任何url的情况下,就可以自动读取name了,5s以后就自动过期了。
2.Session
Session是基于cookie的,在它的基础上增加了签名,可以防止客户端模拟cookie从而攻击服务器,可以一定程度的加强服务器的保护,其次Session里面的value,通过了base64加密,且只能在服务端设置,session是不能在客户端进行设置的。
const Koa = require('koa')
const Router = require('koa-router')
const KoaSession = require('koa-session')
const app = new Koa()
const loginRouter = new Router({prefix:'/login'})
const session = KoaSession({
key: "sessionid", //cookie的key
maxAge: 10 * 1000, //过期时间
httpOnly: true, //不能通过获取cookie
rolling: true, //相应,刷新有效期
signed: true, //是否使用签名认证,防止数据被篡改
},app)
app.keys = ['ice']
app.use(session)
loginRouter.get('/',(ctx,next) => {
ctx.session.user = {
id:1,
name:'ice'
}
ctx.body = "session设置成功"
})
app.use((ctx,next) => {
if(ctx.request.url === '/') {
const user = ctx.session.user
if(!user) {
ctx.body = '请登录!'
return
}
ctx.body = user
}else {
next()
}
})
app.use(loginRouter.routes())
app.use(loginRouter.allowedMethods())
app.listen(3000,() => {
console.log('服务启动成功')
})
复制代码
为什么Cookie走向没落了?
- Cookie会被附加在每个HTTP请求中,所以无形中增加了流量(事实上某些请求是不需要的;
- Cookie是明文传递的,所以存在安全性的问题;
- Cookie的大小限制是4KB,对于复杂的需求来说是不够的;
- 对于浏览器外的其他客户端(比如iOS、Android),必须手动的设置cookie和session;
- 对于分布式系统和服务器集群中如何可以保证其他系统也可以正确的解析session?
3.Token(Json Web Token JWT)
在前后端分离的开发过程中,基本都是采用JWT的进行身份验证。token可以翻译为令牌,也就是当用户验证成功的时候给用户颁发一个令牌,根据这个令牌来判断用户是否有权限进行访问。
token有两个重要的步骤。
- 生成token:登录的时候,颁发token
- 验证token:访问某些资源和接口的时候,验证token
JWT生成的Token由三部分组成:
- header
- alg:采用的加密算法,默认是HS256,采用同一个密钥进行加密和解密
- typ: JWT,固定值,通常固定写为JWT
- 通过base64Url算法进行编码
- payload
- 携带的数据:比如用户的id,name
- 令牌的签发时间以及过期时间
- 通过base64Url算法进行编码
- signature
- 设置一个secretKey,通过将前两个的结果合并后进行HMACSHA256的算法
- HMACSHA256(base64Url(header)+.+base64Url(payload), secretKey);
在真实的开发中,我们直接可以使用jsonwebtoken这个库来完成。
1.对称加密
对称加密简单的来说,就是采用同一个密钥进行加密和解密的,如果这个密钥暴露出去是是及其危险的因为,如果设计到服务器集群以及分布式的这种情况,密钥会在多台服务器上,这样是及其不安全的。
代码如下:
const Koa = require('koa');
const Router = require('koa-router');
const jwt = require('jsonwebtoken');
const app = new Koa();
const router = new Router();
const SERCET_KEY = "ice";
// 登录接口
router.post('/login', (ctx, next) => {
const user = {id: 1, name: 'ice'};
const token = jwt.sign(user, SERCET_KEY, {
expiresIn: 10
});
ctx.body = token;
});
// 验证接口
router.get('/verify', (ctx, next) => {
const authorization = ctx.headers.authorization;
const token = authorization.replace("Bearer ", "");
try {
const result = jwt.verify(token, SERCET_KEY);
ctx.body = result;
} catch (error) {
console.log(error.message);
ctx.body = "token是无效的~";
}
});
app.use(router.routes());
app.use(router.allowedMethods());
app.listen(8000, () => {
console.log("服务器启动成功~");
})
复制代码
2.非对称加密
相当于对称加密采用的HS256加密算法,一旦密钥暴露出去是非常危险的,如果服务器采用的分布式或者服务器集群的这种采用非对称加密算法是最安全的也就是RS256算法。
- 公钥:进行验证令牌
- 私钥:进行颁发令牌 我们可以通过openssl进行生成公钥以及私钥,windows系统推荐使用git bash终端
openssl
genrsa -out private.key 1024
rsa -in private.key -pubout -out public.key
复制代码
代码如下
const Koa = require('koa');
const Router = require('koa-router');
const jwt = require('jsonwebtoken');
const fs = require('fs');
const app = new Koa();
const router = new Router();
const PRIVATE_KEY = fs.readFileSync('./keys/private.key');
const PUBLIC_KEY = fs.readFileSync('./keys/public.key');
// 登录接口
router.post('/login', (ctx, next) => {
const user = {id: 1, name: 'ice'};
const token = jwt.sign(user, PRIVATE_KEY, {
expiresIn: 10,
algorithm: "RS256"
});
ctx.body = token;
});
// 验证接口
router.get('/verify', (ctx, next) => {
const authorization = ctx.headers.authorization;
const token = authorization.replace("Bearer ", "");
try {
const result = jwt.verify(token, PUBLIC_KEY, {
algorithms: ["RS256"]
});
ctx.body = result;
} catch (error) {
console.log(error.message);
ctx.body = "token是无效的~";
}
});
app.use(router.routes());
app.use(router.allowedMethods());
app.listen(8000, () => {
console.log("服务器启动成功~");
})
复制代码