美文网首页
个人博客的Token验证实践(基于Node)

个人博客的Token验证实践(基于Node)

作者: 追星人小豪 | 来源:发表于2020-07-03 09:59 被阅读0次

    前言

    • 为什么要用 token

    HTTP 是一种无状态的协议,也就是 HTTP 没法保存客户端的信息,没办法区分每次请求的不同。

    设想这样的场景,A 和 B 同时修改个人的文章,服务器同时收到两个 post 请求,但浏览器并不知道哪个请求是 A 哪个请求是 B,需要一个标识符(token)来标记一串信息并且在请求的时候带上

    • token 是什么

    Token 是服务器生成的一串字符,作为客户端请求的令牌。当第一次登陆后,服务器会分发 Tonken 字符串给客户端。后续的请求,客户端只需带上这个 Token,服务器即可知道是该用户的访问。

    个人理解就是一串被服务器加密过的个人信息,比如下面这个:

    acess_token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI1ZWRhMTBjOTI0NThmNDAwMmFjZDEyMTAiLCJuaWNrX25hbWUiOiLlsI_osaoxOTk2IiwiY3JlYXRlZF90aW1lIjoiMjAyMC0wNi0wNVQwOTozMDo0OS42MThaIiwidXBkYXRlZF90aW1lIjoiMjAyMC0wNi0wNVQwOToyOToyMC4wNzlaIiwiaWF0IjoxNTkxMzQ5NDY4LCJleHAiOjE1OTE5NTQyNjh9.GmUJRXHed7M1xJyPaFFgaQKJoS-w8-l3N_PQFPiwwTE
    

    服务器通过秘钥解密从而获得当前请求者的信息

    技术栈

    • 前端:vue + ssr
    • 后端:egg(一个 node 框架) + ts
    • 数据库:redis + mongo
    • 部署:Docker
    • 构建:Jenkins

    blog 已经基本完成,并且用上了 ssr 渲染

    本文主要是讲 token 验证,其他不再累述

    token 验证设计

    我的 blog 对 get 类型的请求不做 token 验证,其他会修改资源的请求如 POST、PUT 会做 token 验证

    可拆解为下面 3 个步骤

    • 客户端用户登录,服务端根据用户信息生成 token 并在客户端持久化存储
    • 客户端请求,带上 token
    • 服务端验证 token,若失败则直接返回错误状态

    1.前端(vue.js)

    使用 axios 库,并且在 request 拦截器中把 token 塞到请求头 header ,在 response 拦截器中统一对错误状态码进行全局提示

    登录成功之后把 token 写入 浏览器缓存 中,每次请求都带上

    import Vue from 'vue';
    import axios, { AxiosRequestConfig, AxiosResponse } from 'axios';
    
    axios.interceptors.request.use(
      async (config: AxiosRequestConfig) => {
        const acess_token = await Vue.prototype.$getCacheData('acess_token'); # 缓存中读取token
        if (acess_token) {
          config.headers.acess_token = acess_token;
        }
        return config;
      },
      (err: any) => Promise.reject(err),
    );
    
    axios.interceptors.response.use(
      (response: AxiosResponse) => {
        if (response.data.ret === 200) {
          return response;
        } else {
          Vue.prototype.$global_fail(response.data.content);
          return Promise.reject(response);
        }
      },
      (err: any) => {
        console.log(err);
        if (err.code === 'ECONNABORTED' && err.message.indexOf('timeout') !== -1) {
          Vue.prototype.$global_error('请求超时,请联系管理员');
        }
        if (err.response) {
          Vue.prototype.$global_error(decodeURI(err.response.data.msg || err.response.data.message));
        }
        return Promise.reject(err);
      },
    );
    

    关于前端缓存,这里我推荐一个 localForage 库,很实用

    localForage 是一个 JavaScript 库,能存储多种类型的数据,而不仅仅是字符串。localForage 有一个优雅降级策略,若浏览器不支持 IndexedDB 或 WebSQL,则使用 localStorage。

    但注意,它的操作都是异步的,可以自己封装一层把它改成同步的

    import Vue from 'vue';
    import localForage from 'localforage';
    
    Vue.prototype.$setCacheData = async (key: string, data: any): Promise<void> => await localForage.setItem(key, data);
    Vue.prototype.$getCacheData = async (key: string): Promise<string | null> => await localForage.getItem(key) || null;
    Vue.prototype.$clearCache = () => localForage.clear();
    

    2.后端(egg.js)

    • 生成 token

    用户登录,使用 jsonwebtoken 生成 token

    jsonwebtoken 的详情请点击: node-jsonwebtoken

    加密解密使用也比较简单,直接给出 UserService 方法,其中 secret 为秘钥,且可以设置 token 过期时间

    # /app/service/user.ts
    import * as jwt from 'jsonwebtoken';
    
    export default class UserService extends Service {
    
      private secret = 'Hello__World'; # 秘钥
      async createToken(user: User): Promise<string> {
        const payload = {
          _id: user._id,
          nick_name: user.nick_name,
          created_time: user.created_time,
          updated_time: user.updated_time,
        };
        return jwt.sign(payload, this.secret, { expiresIn: '7d' }); # 过期时间
      }
    
      checkToken(token: string): User {
        try {
          # 根据秘钥解密token
          return jwt.verify(token, this.secret);
        } catch (e) {
          throw '无效的token';
        }
      }
    
    }
    

    在中间件中进行 token 验证,若失败直接返回

    • 中间件验证

    开启 verify 中间件,并只对特定的 POST 请求进行验证:

    开启中间件

    # /config/config.default.ts
    config.middleware = ['verify'];
    config.verify = {
      enable: true,
      # 只对POST请求做验证
      match(ctx) {
        return ctx.request.method === 'POST';
      },
    };
    
    

    在 verify 调用 checkToken 方法验证 token

    # /app/middleware/verify.ts
    module.exports = () => {
      return async (ctx, next) => {
        if (ctx.path.startsWith('/api/user/login') || ctx.path.startsWith('/api/user/sendCode') || ctx.path.startsWith('/api/user/register')) {
          return await next();
        }
        try {
          const acess_token: string = ctx.request.header.acess_token;
          if (!acess_token) {
            throw '请登录';
          } else {
            await ctx.service.user.checkToken(acess_token); # 验证token
            return await next();
          }
        } catch (e) {
          # token验证失败会走到这里,返回自定义状态码
          console.log(e);
          ctx.body = {
            ret: 304,
            content: `${e}`,
          };
        }
      };
    };
    

    至此 token 验证就完了,如有不足,欢迎指出


    END

    相关文章

      网友评论

          本文标题:个人博客的Token验证实践(基于Node)

          本文链接:https://www.haomeiwen.com/subject/abbjqktx.html