每年培训输出1000+合格IT工程师
培训影响全国各大知名IT企业和部门

什么是JWT

什么是JWT?

1、json web token 简称JWT,是基于JSON的一种开放标准。服务器和客户端可以通过JWT来建立一座通信的桥梁。 2、JWT主要分为三部分。header(头部),payload(载体), signature(签名)。

header: 头部
声明加密方式和声明类型
payload: 载体
存放JWT定义的一些声明(例如:过期时间,签发者等等),我们将用户的一些信息存放在这里(例如:用户名,用户ID等,千万不要存在用户密码等敏感信息)
signature: 签名
signature = [header(加密方式)](base64编码(header) + '.' + base64编码(payload), [服务器的私钥])

最后将上面三个组成部分用’.’连接起来就构成了JWT:

JWT = base64编码(header) + '.' + base64编码(payload) + '.' + signature

实现jwt认证

1、下载koa及相关模块

npm i koa koa-bodyparser koa-router
npm i jsonwebtoken  // 一个实现jwt的包
npm i koa-jwt  //验证jwt的koa中间价

2、实现

const Koa = require('koa')
const Router = require('koa-router')
const bodyParser = require('koa-bodyparser')
const jwt = require('jsonwebtoken')
const jwtKoa = require('koa-jwt')
const util = require('util')
const verify = util.promisify(jwt.verify) // 解密
const secret = 'jwt demo'
const app = new Koa()
const router = new Router()
app.use(bodyParser())
app
    .use(jwtKoa({secret}).unless({
        path: [/^\/api\/login/] //数组中的路径不需要通过jwt验证
    }))
router
    .post('/api/login', async (ctx, next) => {
        const user = ctx.request.body
        if(user && user.name) {
            let userToken = {
                name: user.name
            }
            const token = jwt.sign(userToken, secret, {expiresIn: '1h'})  //token签名 有效期为1小时
            ctx.body = {
                message: '获取token成功',
                code: 1,
                token
            }
        } else {
            ctx.body = {
                message: '参数错误',
                code: -1
            }
        }
    })
    .get('/api/userInfo', async (ctx) => {
        const token = ctx.header.authorization  // 获取jwt
        let payload
        if (token) {
            payload = await verify(token.split(' ')[1], secret)  // // 解密,获取payload
            ctx.body = {
                payload
            }
        } else {
            ctx.body = {
                message: 'token 错误',
                code: -1
            }
        }
    })
app
    .use(router.routes())
    .use(router.allowedMethods())
app.listen(3000, () => {
    console.log('app listening 3000...')
})
  1. 启动

  2. 通过curl模拟请求,访问 http://127.0.0.1:3000/api/userInfo

curl http://127.0.0.1:3000/api/userInfo

返回如下内容:

Authentication Erro  //验证错误,说明我们的jwt已经生效

访问 http://127.0.0.1:3000/api/login 带上参数name

curl -d "name=tiptoe" http://127.0.0.1:3000/api/login

结果如下:

{"message":"获取token成功","code":1,"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoidGlwdG9lIiwiaWF0IjoxNDk2Mzg4NzgwLCJleHAiOjE0OTYzOTIzODB9.N2e-84Pmf466DQJ2x3ldd1AWC1IL97ZRWwiDR-Oebhs"}

然后在header中加入token,在访问http://127.0.0.1:3000/api/userInfo

Authorization: Bearer  授权:令牌类型为Bearer
curl -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoidGlwdG9lIiwiaWF0IjoxNDk2Mzg4NzgwLCJleHAiOjE0OTYzOTIzODB9.N2e-84Pmf466DQJ2x3ldd1AWC1IL97ZRWwiDR-Oebhs" http://127.0.0.1:3000/api/userInfo

返回如下内容,说明认证已经通过:

{"payload":{"name":"tiptoe","iat":1496398614,"exp":1496402214}}

介绍一个jwt解密的网站,jwt.io。我们可以在这个网站输入假面的token和私钥,然后可以查看解密的JWT

前端开发使用的是Vue.js,发送HTTP请求使用的是axios。

  1. 登录成功之后将JWT存储到localStorage中(可根据个人需要存储,我个人是比较喜欢存储到localStorage中)。

    methods: {
        login: async function () {
          // ...
    
          let res = await api.login(this.userName, this.password);
          if (res.success === 1) {
            this.errMsg = '';
            localStorage.setItem('DON_BLOG_TOKEN', res.token);
            this.$router.push({ path: '/postlist' });
          } else {
            this.errMsg = res.message;
          }
        }
      }
  2. Vue.js的router(路由)跳转前校验JWT是否存在,不存在则跳转到登录页面。

    // /src/router/index.js
    router.beforeEach((to, from, next) => {
      if (to.meta.requireAuth) {
        const token = localStorage.getItem('DON_BLOG_TOKEN');
        if (token && token !== 'null') {
          next();
        } else {
          next('/login');
        }
      } else {
        next();
      }
    });
  3. axios拦截器中给HTTP统一添加Authorization信息

    // /src/fetch/fetch.js
    axios.interceptors.request.use(
      config => {
        const token = localStorage.getItem('DON_BLOG_TOKEN');
        if (token) {
          // Bearer是JWT的认证头部信息
          config.headers.common['Authorization'] = 'Bearer ' + token;
        }
        return config;
      },
      error => {
        return Promise.reject(error);
      }
    );
  4. axios拦截器在接收到HTTP返回时统一处理返回状态

    // /src/main.js
    axios.interceptors.response.use(
      response => {
        return response;
      },
      error => {
        if (error.response.status === 401) {
          Vue.prototype.$msgBox.showMsgBox({
            title: '错误提示',
            content: '您的登录信息已失效,请重新登录',
            isShowCancelBtn: false
          }).then((val) => {
            router.push('/login');
          }).catch(() => {
            console.log('cancel');
          });
        } else {
          Vue.prototype.$message.showMessage({
            type: 'error',
            content: '系统出现错误'
          });
        }
        return Promise.reject(error);
      }
    );

Node.js 应用:Koa2 使用 JWT 进行鉴权

前言

在前后端分离的开发中,通过 Restful API 进行数据交互时,如果没有对 API 进行保护,那么别人就可以很容易地获取并调用这些 API 进行操作。那么服务器端要如何进行鉴权呢?

Json Web Token 简称为 JWT,它定义了一种用于简洁、自包含的用于通信双方之间以 JSON 对象的形式安全传递信息的方法。JWT 可以使用 HMAC 算法或者是 RSA 的公钥密钥对进行签名。

说得好像跟真的一样,那么到底要怎么进行认证呢?

首先用户登录时,输入用户名和密码后请求服务器登录接口,服务器验证用户名密码正确后,生成token并返回给前端,前端存储token,并在后面的请求中把token带在请求头中传给服务器,服务器验证token有效,返回正确数据。

既然服务器端使用 Koa2 框架进行开发,除了要使用到 jsonwebtoken 库之外,还要使用一个 koa-jwt 中间件,该中间件针对 Koa 对 jsonwebtoken 进行了封装,使用起来更加方便。下面就来看看是如何使用的。

生成token

这里注册了个 /login 的路由,用于用户登录时获取token。

const router = require('koa-router')();
const jwt = require('jsonwebtoken');
const userModel = require('../models/userModel.js');

router.post('/login', async (ctx) => {
    const data = ctx.request.body;
    if(!data.name || !data.password){
        return ctx.body = {
            code: '000002',
            data: null,
            msg: '参数不合法'
        }
    }
    const result = await userModel.findOne({
        name: data.name,
        password: data.password
    })
    if(result !== null){
        const token = jwt.sign({
            name: result.name,
            _id: result._id
        }, 'my_token', { expiresIn: '2h' });
        return ctx.body = {
            code: '000001',
            data: token,
            msg: '登录成功'
        }
    }else{
        return ctx.body = {
            code: '000002',
            data: null,
            msg: '用户名或密码错误'
        }
    }
});

module.exports = router;

在验证了用户名密码正确之后,调用 jsonwebtoken 的 sign() 方法来生成token,接收三个参数,第一个是载荷,用于编码后存储在 token 中的数据,也是验证 token 后可以拿到的数据;第二个是密钥,自己定义的,验证的时候也是要相同的密钥才能解码;第三个是options,可以设置 token 的过期时间。

获取token

接下来就是前端获取 token,这里是在 vue.js 中使用 axios 进行请求,请求成功之后拿到 token 保存到 localStorage 中。这里登录成功后,还把当前时间存了起来,除了判断 token 是否存在之外,还可以再简单的判断一下当前 token 是否过期,如果过期,则跳登录页面

submit(){
    axios.post('/login', {
        name: this.username,
        password: this.password
    }).then(res => {
        if(res.code === '000001'){
            localStorage.setItem('token', res.data);
            localStorage.setItem('token_exp', new Date().getTime());
            this.$router.push('/');
        }else{
            alert(res.msg);
        }
    })
}

然后请求服务器端API的时候,把 token 带在请求头中传给服务器进行验证。每次请求都要获取 localStorage 中的 token,这样很麻烦,这里使用了 axios 的请求拦截器,对每次请求都进行了取 token 放到 headers 中的操作。

axios.interceptors.request.use(config => {
    const token = localStorage.getItem('token');
    config.headers.common['Authorization'] = 'Bearer ' + token;
    return config;
})

验证token

通过 koa-jwt 中间件来进行验证,用法也非常简单

const koa = require('koa');
const koajwt = require('koa-jwt');
const app = new koa();

// 错误处理
app.use((ctx, next) => {
    return next().catch((err) => {
        if(err.status === 401){
            ctx.status = 401;
            ctx.body = 'Protected resource, use Authorization header to get access\n';
        }else{
            throw err;
        }
    })
})

app.use(koajwt({
    secret: 'my_token'
}).unless({
    path: [/\/user\/login/]
}));

通过 app.use 来调用该中间件,并传入密钥 {secret: 'my_token'},unless 可以指定哪些 URL 不需要进行 token 验证。token 验证失败的时候会抛出401错误,因此需要添加错误处理,而且要放在 app.use(koajwt()) 之前,否则不执行。

如果请求时没有token或者token过期,则会返回401。

解析koa-jwt

我们上面使用 jsonwebtoken 的 sign() 方法来生成 token 的,那么 koa-jwt 做了些什么帮我们来验证 token。

resolvers/auth-header.js

module.exports = function resolveAuthorizationHeader(ctx, opts) {
    if (!ctx.header || !ctx.header.authorization) {
        return;
    }
    const parts = ctx.header.authorization.split(' ');
    if (parts.length === 2) {
        const scheme = parts[0];
        const credentials = parts[1];
        if (/^Bearer$/i.test(scheme)) {
            return credentials;
        }
    }
    if (!opts.passthrough) {
        ctx.throw(401, 'Bad Authorization header format. Format is "Authorization: Bearer <token>"');
    }
};

在 auth-header.js 中,判断请求头中是否带了 authorization,如果有,将 token 从 authorization 中分离出来。如果没有 authorization,则代表了客户端没有传 token 到服务器,这时候就抛出 401 错误状态。

verify.js

const jwt = require('jsonwebtoken');

module.exports = (...args) => {
    return new Promise((resolve, reject) => {
        jwt.verify(...args, (error, decoded) => {
            error ? reject(error) : resolve(decoded);
        });
    });
};

在 verify.js 中,使用 jsonwebtoken 提供的 verify() 方法进行验证返回结果。jsonwebtoken 的 sign() 方法来生成 token 的,而 verify() 方法则是用来认证和解析 token。如果 token 无效,则会在此方法被验证出来。

index.js

const decodedToken = await verify(token, secret, opts);
if (isRevoked) {
    const tokenRevoked = await isRevoked(ctx, decodedToken, token);
    if (tokenRevoked) {
        throw new Error('Token revoked');
    }
}
ctx.state[key] = decodedToken;  // 这里的key = 'user'
if (tokenKey) {
    ctx.state[tokenKey] = token;
}

在 index.js 中,调用 verify.js 的方法进行验证并解析 token,拿到上面进行 sign() 的数据 {name: result.name, _id: result._id},并赋值给 ctx.state.user,在控制器中便可以直接通过 ctx.state.user 拿到 name_id

安全性

  • 如果 JWT 的加密密钥泄露的话,那么就可以通过密钥生成 token,随意的请求 API 了。因此密钥绝对不能存在前端代码中,不然很容易就能被找到。

  • 在 HTTP 请求中,token 放在 header 中,中间者很容易可以通过抓包工具抓取到 header 里的数据。而 HTTPS 即使能被抓包,但是它是加密传输的,所以也拿不到 token,就会相对安全了。

总结

这上面就是 jwt 基本的流程,这或许不是最完美的,但在大多数登录中使用已经足够了。 上面的代码可能不够具体,这里使用 Koa + mongoose + vue.js 实现的一个例子 : jwt-demo,可以做为参考。

赞(1) 打赏
未经允许不得转载:徐礼文的技术博客 » 什么是JWT
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏