第十三节:总结几种校验方式(Cookie、Session、JWT--openssl生成公钥私钥)
一. Cookie
1. 简介
(1). Cookie(复数形态Cookies),又称为“小甜饼”。类型为“小型文本文件”,某些网站为了辨别用户身份而存储在用户本地终端(Client Side)上的数据。 浏览器会在特定的情况下携带上cookie来发送请求,我们可以通过cookie来获取一些信息;
(2). Cookie总是保存在客户端中,按在客户端中的存储位置,Cookie可以分为内存Cookie和硬盘Cookie。
内存Cookie由浏览器维护,保存在内存中,浏览器关闭时Cookie就会消失,其存在时间是短暂的;
硬盘Cookie保存在硬盘中,有一个过期时间,用户手动清理或者过期时间到时,才会被清理;
(3). 如果判断一个cookie是内存cookie还是硬盘cookie呢?
没有设置过期时间,默认情况下cookie是内存cookie,在关闭浏览器时会自动删除;
有设置过期时间,并且过期时间不为0或者负数的cookie,是硬盘cookie,需要手动或者到期时,才会删除;
2. 常见属性
(1). cookie的生命周期
默认情况下的cookie是内存cookie,也称之为会话cookie,也就是在浏览器关闭时会自动被删除;
我们可以通过设置 expires 或者 max-age 来设置过期的时间;
➢ expires:设置的是Date.toUTCString(),设置格式是;expires=date-in-GMTString-format;
➢ max-age:设置过期的秒钟,;max-age=max-age-in-seconds (例如一年为60* 60* 24*365);
(2). cookie的作用域:(允许cookie发送给哪些URL)
(3). Domain:指定哪些主机可以接受cookie
➢ 如果不指定,那么默认是 origin,不包括子域名。
➢ 如果指定Domain,则包含子域名。例如,如果设置 Domain=mozilla.org,则 Cookie 也包含在子域名中(如developer.mozilla.org)。
(4). Path:指定主机下哪些路径可以接受cookie
➢ 例如,设置 Path=/docs,则以下地址都会匹配:
/docs
/docs/Web/
/docs/Web/HTTP
3. 客户端设置
(PS:很少用)
(1).没有设置max-age时, 是一个内存cookie, 浏览器关闭时会消失
(2).设置max-age时, 是一个硬盘cookie, 只能等到过期时间到达的时候, 才会销毁
document.cookie = "name=ypf;max-age=30;"
document.cookie = "age=18;max-age=60;"
查看代码
<body>
<button id="myBtn">设置cookie</button>
<script>
const btn1 = document.querySelector('#myBtn');
btn1.onclick = function () {
// 在浏览器中通过js设置cookie(在开发中很少使用)
// 1.没有设置max-age时, 是一个内存cookie, 浏览器关闭时会消失
// 2.设置max-age时, 是一个硬盘cookie, 只能等到过期时间到达的时候, 才会销毁
document.cookie = "name=ypf;max-age=30;"
document.cookie = "age=18;max-age=60;"
}
</script>
</body>
4. 服务端设置
详见代码
ctx.cookies.set
ctx.cookies.get
const koaRouter = require("@koa/router");
const router = new koaRouter();
/**
* 1.服务器设置cookie
* 2.客户端(浏览器)保存cookie
* 3.在同一个作用域下访问服务器, 自动携带cookie
* 4.服务器验证客户端携带的cookie
*/
router.post("/SetCookie", (ctx, next) => {
ctx.cookies.set("myCookie1", "ypf", { maxAge: 60 * 1000 * 10 });
ctx.body = "设置成功";
});
// 获取Cookie
router.post("/GetCookie", (ctx, next) => {
const value = ctx.cookies.get("myCookie1");
ctx.body = "获取成功:" + value;
});
二. Session
Session是基于cookie实现机制,在koa中,我们可以借助于 koa-session 来实现session认证
【npm install koa-session】
//路由
const koaRouter = require("@koa/router");
const router = new koaRouter();
// Session配置
const mySession = koaSession(
{
keys: "sessionId",
signed: true,
maxAge: 60 * 1000 * 10,
// httpOnly: true,
},
app
);
app.keys = ["sdfsdfsdf"]; //加盐
app.use(mySession);
//设置Session
router.post("/SetSession", (ctx, next) => {
ctx.session.myName = "ypf";
ctx.body = "Session设置成功";
});
//获取Session
router.post("/GetSession", (ctx, next) => {
const value = ctx.session.myName;
ctx.body = "Session获取成功:" + value;
});
三. JWT
1. 背景
(1). cookie和session的方式有很多的缺点:
Cookie会被附加在每个HTTP请求中,所以无形中增加了流量(事实上某些请求是不需要的);
Cookie是明文传递的,所以存在安全性的问题;
Cookie的大小限制是4KB,对于复杂的需求来说是不够的;
对于浏览器外的其他客户端(比如iOS、Android),必须手动的设置cookie和session;
对于分布式系统和服务器集群中如何可以保证其他系统也可以正确的解析session? 所以,在目前的前后端分离的开发过程中,使用token来进行身份验证的是最多的情况.
(2). token可以翻译为令牌;
也就是在验证了用户账号和密码正确的情况,给用户颁发一个令牌;这个令牌作为后续用户访问一些接口或者资源的凭证; 我们可以根据这个凭证来判断用户是否有权限来访问;所以token的使用应该分成两个重要的步骤:
生成token:登录的时候,颁发token;
验证token:访问某些资源或者接口时,验证token
2. 简介
JWT生成的Token由三部分组成:
(1). header
alg:采用的加密算法,默认是 HMAC SHA256(HS256),采用同一个密钥进行加密和解密;
typ:JWT,固定值,通常都写成JWT即可;
会通过base64Url算法进行编码;
(2). payload
携带的数据,比如我们可以将用户的id和name放到payload中;
默认也会携带iat(issued at),令牌的签发时间;
我们也可以设置过期时间:exp(expiration time);
会通过base64Url算法进行编码
(3). signature
设置一个secretKey,通过将前两个的结果合并后进行HMACSHA256的算法;
HMACSHA256(base64Url(header)+.+base64Url(payload), secretKey);
但是如果secretKey暴露是一件非常危险的事情,因为之后就可以模拟颁发token,也可以解密token;
PS:这里采用jsonwebtoken包来进行jwt的相关操作,详见: https://www.npmjs.com/package/jsonwebtoken
3. 默认加密-HMAC SHA256
默认采用 sha256对称加密算法,即加密和解密都是使用同一个key
生成token:jwt.sign
校验token:jwt.verify
采用 (Bearer xxxx) 的模式传递,然后通过ctx.headers.authorization;获取传递的值,通过auth.replace("Bearer ", ""); 截取token
代码分享:
const jwt = require("jsonwebtoken");
// 通用密钥
const secretKey = "lkdslfskfskfl";
/**
* Token生成
* http://localhost:9090/GetToken
*/
router.post("/GetToken", (ctx, next) => {
// token生成
const payLoad = { id: "001", name: "ypf" };
const token = jwt.sign(payLoad, secretKey, {
//格式可以是:数字或字符串 Eg: 60, "2 days", "10h", "7d"
// 下面代表2小时
expiresIn: "2h",
});
// 返回
ctx.body = {
status: "ok",
msg: "获取成功",
data: { token },
};
});
/**
* Token校验
* 在header中通过Bearer xxx的模式传递过来token
*/
router.post("/CheckToken", (ctx, next) => {
// 1. 获取token
const auth = ctx.headers.authorization;
if (!auth) {
ctx.body = { status: "error", msg: "token为空" };
}
const token = auth.replace("Bearer ", ""); //截取token
// 2. token校验
try {
const result = jwt.verify(token, secretKey);
ctx.body = { status: "ok", msg: "校验通过", data: result };
} catch (error) {
ctx.body = { status: "error", msg: "无效token或token过期", data: error };
}
});
4. 非对称加密--RS256
(1). jwt加密推荐使用非对称加密算法RS256,这样就需要公钥和私钥,使用公钥生成token,使用私钥解密token。
(2). openssl生成公钥和私钥
注:不要使用命令行和powershell,使用git bash,另外在jsonwebtoken 9.x版本对密钥长度有限制,必须大于等于2048,所以生成的指令为:
先输入: 【openssl】
私钥: 【genrsa -out private.key 2048】 这里的2048为密钥的长度。
公钥: 【rsa -in private.key -pubout -out public.key】

特别说明:
如果密钥的长度就是少于2048,那么需要生成密钥的时候需要配置一下参数 allowInsecureKeySizes: true,关掉的限制。
const token2 = jwt.sign({ id, name }, PRIVATE_KEY, {
expiresIn: '1h',
algorithm: 'RS256',
allowInsecureKeySizes: true, //设置9.x版本允许密钥size低于2048 (如果密钥本身长度>=2048, 则不需要设置)
});
PS:9.0版本变化,详见 https://github.com/auth0/node-jsonwebtoken/wiki/Migration-Notes:-v8-to-v9
(3). 生成的公钥和私钥存放在config/key文件夹下,然后通过 screct.js 读取公钥私钥并导出。
注:默认情况下相对目录和node程序的启动目录有关系- 所以需要写 ./src, 但这里建议写绝对路径即可。--详见项目章节
(4). 生成与校验
生成token的方法:jwt.sign(payload, secretOrPrivateKey, [options, callback])
校验token的方法:jwt.verify(token, secretOrPublicKey, [options, callback])
代码分享:
const Koa = require("koa");
const jwt = require("jsonwebtoken");
const app = new Koa();
const fs = require("fs");
app.listen(9090, () => {
console.log("---------------koa服务器启动成功,端口9090----------------");
});
//路由
const koaRouter = require("@koa/router");
const router = new koaRouter();
// 通用密钥
const privateKey = fs.readFileSync("./keys/private.key");
const publicKey = fs.readFileSync("./keys/public.key");
/**
* Token生成-私钥加密
* http://localhost:9090/GetToken
*/
router.post("/GetToken", (ctx, next) => {
// token生成
const payLoad = { id: "001", name: "ypf" };
const token = jwt.sign(payLoad, privateKey, {
//格式可以是:数字或字符串 Eg: 60, "2 days", "10h", "7d"
// 下面代表2小时
expiresIn: "2h",
algorithm: "RS256",
});
// 返回
ctx.body = {
status: "ok",
msg: "获取成功",
data: { token },
};
});
/**
* Token校验---私钥解密
* 在header中通过Bearer xxx的模式传递过来token
*/
router.post("/CheckToken", (ctx, next) => {
// 1. 获取token
const auth = ctx.headers.authorization;
if (!auth) {
ctx.body = { status: "error", msg: "token为空" };
}
const token = auth.replace("Bearer ", ""); //截取token
// 2. token校验
try {
const result = jwt.verify(token, publicKey, { algorithms: ["RS256"] });
ctx.body = { status: "ok", msg: "校验通过", data: result };
} catch (error) {
ctx.body = { status: "error", msg: "无效token或token过期", data: error };
}
});
// 路由中间件的注册需要放到上述其它中间件的后面
app.use(router.routes());
app.use(router.allowedMethods()); //加上这句话,当发送的请求方式没有定义的话,会提示method not found
!
- 作 者 : Yaopengfei(姚鹏飞)
- 博客地址 : http://www.cnblogs.com/yaopengfei/
- 声 明1 : 如有错误,欢迎讨论,请勿谩骂^_^。
- 声 明2 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。

浙公网安备 33010602011771号