uniquejava / blog

My notes regarding the vibrating frontend :boom and the plain old java :rofl.
Creative Commons Zero v1.0 Universal
11 stars 5 forks source link

jwt #106

Open uniquejava opened 7 years ago

uniquejava commented 7 years ago

What is a JSON Web Token?

没事就读读: What is a JSON Web Token?

JWT规范: http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html

0 _tckw18m85-50ofn

JWT分三部分 header.payload.signature

header: {typ: ‘JWT’, alg: ‘HS256’}
payload: {userId: 2, admin: true}
signature: secret(header+payload)

其中header和payload是给浏览器用的, 仅是base64 encoded, (所以不要保存敏感信息) 最后的signature是给server用的, 是用服务端secret加密过的header+payload信息.

header和payload可以直接在jwt.io这个网站上显示出来, 如下图:

express-passport-json-web-token

JWT的传输方式

一共有三种, 分别是http header, http body 和 http url(query parameter)

1. Authorization Request Header Field

先了解一下Authorization的格式

W3C HTTP1.0规范中最早引入了如下风格的Authorization:

Authorization: <type> <credentials>

这里有解释为什么要这么定义, 简而言之, server端可能同时支持多种方式的authorization type.

如果type为Bearer那么server端有可能是实现了OAuth2(Bearer, 翻译: 持有者.)

但是这个Bearer含义最终是由你的server决定, 这里的JWT按照业内的通用做法放在Bearer后面完全可行.

     GET /resource HTTP/1.1
     Host: server.example.com
     Authorization: Bearer mF_9.B5f-4.1JqM

2. Form-Encoded Body Parameter

     POST /resource HTTP/1.1
     Host: server.example.com
     Content-Type: application/x-www-form-urlencoded

     access_token=mF_9.B5f-4.1JqM

3. URI Query Parameter

     GET /resource?access_token=mF_9.B5f-4.1JqM HTTP/1.1
     Host: server.example.com

4. 而server端响应token的格式如下:

     HTTP/1.1 200 OK
     Content-Type: application/json;charset=UTF-8
     Cache-Control: no-store
     Pragma: no-cache

     {
       "access_token":"mF_9.B5f-4.1JqM",
       "token_type":"Bearer",
       "expires_in":3600,
       "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA"
     }

node jwt Tutorial 1 (仅做参考)

Make an API with Node.JS, MongoDB, and JWT Authentication

$ mkdir api-jwt && cd $_
$ npm init -y
$ npm i -S express body-parser mongoose mongoose-paginate kerberos cors morgan bcryptjs moment jwt-simple

这篇的作者写的代码真是漂亮, 知识点:

  1. 如何强制使用https (以及为什么jwt要配合https来使用)
  2. Mongoose分页
  3. 分离受保护和不受保护的routes.
  4. app/routes/controllers代码分层
  5. 如何用curl来测试api.
// 4. Force https in production
if (app.get('env') === 'production') {
  app.use(function(req, res, next) {
    var protocol = req.get('x-forwarded-proto');
    protocol == 'https' ? next() : res.redirect('https://' + req.hostname + req.url);
  });
}

node jwt Tutorial 2 (用得最多)

Simple Authentication in Node/Express using JWT (JSON Web Tokens) 这篇只通过两段代码演示了如何使用jwt. 我直接拷贝过来吧..

npm install jsonwebtokens

服务端生成JWT

var User = require('./userModel');
var jwt = require('jsonwebtokens');
var newUser = function (req, res){
  User.findOne({where:{ username: req.body.username }})
    .then(function (user) {
      if(!user){
        User.create({ 
          username: req.body.username, 
          password: req.body.password, 
          email: req.body.email 
        })
        .then(function(user){
              var myToken = jwt.sign({ user: user.id },
                                      'secret',
                                     { expiresIn: 24 * 60 * 60 });
              res.send(200, {'token': myToken,
                             'userId':    user.id,
                             'username': user.username });
        });
      } else {
        res.status(404).json('Username already exist!');
      }
    })
    .catch(function (err) {
      res.send('Error creating user: ', err.message);
    });
};

服务端解密JWT

var authorize = function(req, res, next) {
 var token = req.body.token || req.headers[‘x-access-token’];
  if (token) {
   jwt.verify(token, 'secret', function(err, decoded) {
      if (err) {
         console.error(‘JWT Verification Error’, err);
         return res.status(403).send(err);
      } else {
         req.decoded = decoded;
         return next();
      }
   });
  } else {
   res.status(403).send(‘Token not provided’);
   }
}

Cyper in Action

最终我选择了"jsonwebtoken": "^7.4.1",

uniquejava commented 7 years ago

refresh token

https://codeforgeek.com/2018/03/refresh-token-jwt-nodejs-authentication/

https://solidgeargroup.com/refresh-token-with-jwt-authentication-node-js

我的理解

在client登录的时候, 如果用户POST的username和password从DB中可以找到, 那么就把用户的user_id或user_name加密成满足jwt规则的字符串 (不要把密码放进去, 危险!)

client拿到这个token(仅包含加密的user_id)可以将其放在localStorage

然后在每个http request中带上这个token

如果传给server的token是有效的(能正常解密), 则无需再向DB验证用户的身份.
如果是无效的token(无法解密), 直接返回401

client需要妥善保存这个token, 并且传输协议最好是HTTPS.