Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

项目中碰到的JSON Web Token & 代理请求 等 #79

Open
5Mi opened this issue Sep 20, 2017 · 1 comment
Open

项目中碰到的JSON Web Token & 代理请求 等 #79

5Mi opened this issue Sep 20, 2017 · 1 comment

Comments

@5Mi
Copy link
Owner

5Mi commented Sep 20, 2017

项目中碰到了个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 use Bearer <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;
}

参考:

@5Mi
Copy link
Owner Author

5Mi commented Jan 4, 2018

记录项目中服务代理

前端用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/;

@5Mi 5Mi changed the title 项目中碰到的JSON Web Token 项目中碰到的JSON Web Token & 代理请求 等 Jan 4, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant