wumi_blog
wumi_blog copied to clipboard
项目中碰到的JSON Web Token & 代理请求 等
项目中碰到了个token失效的问题,明明有不停发心跳的.之前对token这个也是一知半解(虽然现在仍是),自己一点点看了项目中的代码才稍微清晰了一些.这次简单记录下
JSON Web Token
What's JSON Web Token? JSON Web Token (JWT, pronounced jot) is a relatively new token format used in space-constrained environments such as HTTP Authorization headers. JWT is architected as a method for transferring security claims based between parties.
server side 以koa为例
var koa = require('koa');
var jwt = require('koa-jwt');
var app = new Koa();
// Middleware below this line is only reached if JWT token is valid
// use jwt 下面的中间件 只能在token验证后才能到达
// unless the URL starts with '/public'
app.use(jwt({ secret: 'shared-secret' }).unless({ path: [/^\/public/] }));
// Unprotected middleware
app.use(function(ctx, next){
if (ctx.url.match(/^\/public/)) {
ctx.body = 'unprotected\n';
} else {
return next();
}
});
// Protected middleware
app.use(function(ctx){
if (ctx.url.match(/^\/api/)) {
ctx.body = 'protected\n';
}
});
app.listen(3000);
项目伪代码
...
app.use(koaJwt({
secret: config.secret,
key: 'user',
}).unless({
method: 'GET',
path: [
/^\/login/,
],
}));
//请求先经过token验证
app.use(router.middleware());
...
在login完成登录返回用户信息时可将生成的token存入Storage中.
Now we have the JWT saved on sessionStorage. If the token is set, we are going to set the
Authorization
header for every outgoing request done using$http
. As value part of that header we are going to useBearer <token>
.
客户端
// 发送请求 请求头带有Authorization
$.ajax({
type: "POST",
headers: {
'Content-Type': 'application/json;charset=utf-8',
Authorization: `Bearer ${store.get('token')}`,
},
contentType: "application/json;charset=utf-8",
url: "/someUrl/",
data: JSON.stringify({
datas
}),
success: function(){},
error: function (){},
});
token rolling
实际中会需要用户持续保持登录要与服务器长连接.会通过客户端每隔一段时间向服务器发送一次'心跳请求',
服务端接受到请求后会查看token过期时间.快接近token过期时间的时候,再设定新的过期时间重新生成token,将新token返回给客户端.客户端收到(监听到)新token后,用新token替换请求头Authorization
中的token,达到token续期的目的;
- 服务端伪代码
...
app.use(koaJwt({
secret: config.secret,
key: 'user',
}).unless({
method: 'GET',
path: [
/^\/login/,
],
}));
// 增加一个tokenrolling的中间件
app.use(jwt({
key: 'user',
rolling: config.tokenRolling,
}));
//请求先经过token验证
app.use(router.middleware());
...
//jwt.js
const moment = require('moment');
const jwtTool = require('../common/jwtTool');
const log4js = require('log4js');
const log = log4js.getLogger('jwt');
function jwt({ key, rolling, }) {
const midware = async (ctx, next) => {
try {
const userInfo = ctx.state[key];
if (userInfo) {
//从token生成时间到rolling时间的范围内无需重生成token (请求时超过rolling时间 时才重生成token)
if (moment.unix(userInfo.iat).add(...rolling).valueOf() < moment().valueOf()) {
const newToken = jwtTool.sign(userInfo, global.config.secret, { exp: userInfo.tokenInfo.expParam, });
//响应头设置token-rolling;
ctx.set('token-rolling', newToken);
ctx.cookies.set('token', newToken, { httpOnly: true, maxAge: 1000 * 60 * 60 * 24 * 365 * 20, expires: new Date('2050-01-01'), });
}
}
} catch (ex) {
log.error(`jwt midware 发生错误,错误信息:${ex}`);
}
await next();
}
return midware;
}
module.exports = jwt;
//上文中的jwtTool.js
const jwt = require('jsonwebtoken');
const moment = require('moment');
const log4js = require('log4js');
const log = log4js.getLogger('jwtTool');
function sign(data, secret, { exp = 1, } = {}) {
const iat = moment().unix();
//token过期时间
let expTime = moment.unix(iat).add(global.config.tokenExp, 'minutes').unix();
try {
const intExp = parseInt(exp, 10);
if (intExp === 30) expTime = moment.unix(iat).add(global.config.tokenLongExp, 'minutes').unix();
} catch (ex) {
log.error(`解析登录信息exp出错 exp:${exp}`);
}
const useInfo = Object.assign({}, data, {
//生成时间
iat,
//过期时间
exp: expTime,
tokenInfo: {
expParam: exp,
},
});
//通过jsonwebtoken的sign生成token,用于传递给客户端 在请求中会被服务端koaJwt中间件检查;
const token = jwt.sign(useInfo, global.config.secret);
return token;
}
module.exports = { sign, };
- 客户端
每次发送请求 检查
响应头
中是否有 之前服务端设置的token-rolling
(const = response.headers.get('token-rolling');
) 有就将token-rolling的值存入Storage中,下次请求就用新的token啦
export async function request(url, options) {
const response = await fetch(url, options);
checkStatus(response);
checkToken(response);
const contentType = response.headers.get('Content-type');
if (contentType.startsWith('application/json')) {
const data = await response.json();
return data;
}
if (contentType.startsWith('text/html')) {
const data = await response.text();
return data;
}
throw new Error('unknown content type');
}
function checkToken(response) {
const token = response.headers.get('token-rolling');
//onTokenRolling中将token存入Storge中
if (token && token.length > 0) onTokenRolling(token);
}
function checkStatus(response) {
if (response.status >= 200 && response.status < 300) {
return response;
}
if (response.status === 401) {
//鉴权 token过期
if (unauthorizedListener) unauthorizedListener();
}
const error = new Error(response.statusText);
error.response = response;
throw error;
}
参考:
记录项目中服务代理
前端用node服务中引入proxyMiddleware
const proxyMiddleware = require('http-proxy-middleware');
请求代理设置
const proxyOptions = {
// target host 会将请求代理到此
target: 'http://target.something.some:8080',
// needed for virtual hosted sites
changeOrigin: true,
// proxy websockets
ws: true,
pathRewrite:function (path, req) {
}
}
...
// 在需要代理的路径中使用中间件
app.use('/needProxyPath', proxyMiddleware(proxyOptions));
这时再在项目中访问/needProxyPath
会将请求代理到http://target.something.some:8080
项目中是将所有请求/api
转向代理
如果项目中后台也跑在本地可以结合hosts修改与nginx设置代理 如 hosts文件中增加
# 访问target.something.some时指向本地 (本地运行nginx)
127.0.0.1 target.something.some
在nginx.conf中增加
#target.something.some:8080 指向http://127.0.0.1:8087/someweb/
server {
listen 8080;
server_name target.something.some;
location / {
# 代理到本地跑着的后台服务
proxy_pass http://127.0.0.1:8087/someweb/;
}
}
其实只要在proxyMiddleware 的配置中 直接代理到 本地后台服务就行,但这种就不要再更改前端代码啦
这样下来开发时前端请求/needProxyPath
都会代理到本地后台服务http://127.0.0.1:8087/someweb/
了
上面大概流程就是 前端开发请求/needProxyPath时 会被前端node服务的proxyMiddleware 代理到http://target.something.some:8080
然后由于修改了hosts文件http://target.something.some:8080
会被指向本地127.0.0.1
,本地又跑着nginx,server_name target.something.some
,之后会被nginx指向proxy_pass http://127.0.0.1:8087/someweb/;