前端開發搭建博客系統(二),jwt權鑑設計以及中途遇到的問題。獻給前端

廢話部分

上次廢話太多,估計有些小夥伴沒看太清楚。這次會精簡很多。只拿關鍵的出來講。

個人屬於前端,後端看到了扣下留情。希望本文能幫助大家

項目地址:

前端:https://github.com/ht-sauce/dream

後端:https://github.com/ht-sauce/dream-admin

一、權鑑選型

這塊沒太多可以說的,基本百度的結果就是jwt

而eggjs官方是egg-jwt,其本質也是jsonwebtoken

egg-jwt地址:https://www.npmjs.com/package/egg-jwt

jsonwebtoken地址:https://www.npmjs.com/package/jsonwebtoken

eggjs的插件配置方面我就不多說了。太簡單了(再次讚美eggjs)。

二、先寫登錄接口,保證生成權鑑信息

1、先放完整的登錄接口函數

// 登錄接口
async login() {
  const { ctx, service } = this;
  const userBusiness = service.consumer.user;
  const query = ctx.request.body; // 接口請求數據
  // 參數校驗
  const rule = {
    account: { type: 'string', required: true },
    // 有format纔能有message信息
    password: { type: 'string', required: true },
  };

  try {
    await ctx.validate(rule, query);
    // 優先處理用戶是否存在
    const login = await userBusiness.userLogin(query);
    if (login) {
      // 登錄之後生成口令,有效期24小時
      const token = ctx.helper.generate_token(query.account);
      // 前端肯定會需要用戶信息,返回給前端用戶基本信息
      const userInfo = {
        userInfo: await userBusiness.find(query),
        sign: token,
      };

      ctx.body = ctx.helper.result(userInfo);
    } else {
      ctx.body = ctx.helper.result('', -1, '用戶不存在請註冊');
    }
  } catch (e) {
    ctx.body = ctx.helper.result('', -1, e);
  }
}

2、原理解析

主要是先判斷用戶名密碼,然後再是用自己封裝的helper(eggjs的擴展)來生成口令。

至於helper就是進行統一的封裝函數處理。

然後一併丟給前端。

三、中間件解析口令,進行校驗攔截

1、中間件代碼

'use strict';

module.exports = (options, app) => {
  return async function(ctx, next) {
    const token = ctx.request.header.authorization;

    try {
      if (token) {
        // 驗證當前token
        const decode = app.jwt.verify(token, options.secret);
        // 驗證用戶信息是否正常
        if (!decode || !decode.account) {
          ctx.body = ctx.helper.result('', -1, '用戶信息缺失', 1);
        }
        // 驗證用戶是否存在
        const user = await ctx.model.Consumer.User.findOne(
          { where: { account: decode.account } }
        );
        if (user) {
          // 如果口令有效期小於15分鐘則發送新口令給前端
          if (decode.exp - Date.now() / 1000 < 60 * 15) {
            const token = ctx.helper.generate_token(decode.account);
            ctx.set('Authorization', token);
          }
          // 當所有驗證都通過之後,可以正常訪問
          await next();
        } else {
          ctx.body = ctx.helper.result('', -1, '用戶信息驗證失敗', 1);
        }
      } else {
        ctx.body = ctx.helper.result('', -1, '口令驗證失敗', 1);
      }
    } catch (e) {
      console.log(e);
      ctx.body = ctx.helper.result('', -1, e, 1);
    }
  };
};

2、原理解析

大家仔細看代碼。在各種驗證之後最後只有await next()部分纔是最終正確代碼執行下去的。代表權鑑沒有問題。

從前端的角度來看,無非就是if else之後通過了。

3、口令刷新問題

上面的代碼有一個部分

// 如果口令有效期小於15分鐘則發送新口令給前端
          if (decode.exp - Date.now() / 1000 < 60 * 15) {
            const token = ctx.helper.generate_token(decode.account);
            ctx.set('Authorization', token);
          }

這裏比較關鍵,在於實現當用戶不停操作之後能不會因爲口令過期而突然退出登錄。所以就需要校驗口令的快過期時間。當快過期的時候發一個新的口令給前端。讓前端刷新當前緩存的口令。注意不能時間太短。15分鐘算一個比較合理的時間。

4、後端某些接口不校驗口令直接過。

這個在後臺管理的項目中基本不用太擔心。但是開放式的博客就很有必要了。這裏主要是看eggjs官方對於中間件的處理。

地址:https://eggjs.org/zh-cn/basics/middleware.html

我個人也對應進行配置

// 中間件配置
config.middleware = [ 'jwtAuthorize' ];
// 給jwtAuthorize中間件傳入的參數
config.jwtAuthorize = {
  secret: 'daihaitian19940329',
  // 忽略指定路由
  ignore: [ `${config.dreamCustom.prefix}/noauth` ],
};

三、前端存儲口令以及口令發送問題

1、登錄接口

登錄部分很簡單,就是緩存用戶信息就行了。主要核心在於ajax函數封裝的地方

// 登錄
login() {
  let data = {
    account: this.logindata.userName,
    password: userLoginPassword(this.logindata.password)
  };
  this.logining = true;
  this.axios
    .ajax({
      url: this.$api.consumer().user.login,
      data: data,
      method: "post"
    })
    .then(e => {
      this.logining = false;
      // 存儲用戶數據到緩存
      store.clearAll();
      store.set("user_info", e.data);
      console.log(e.data);
    })
    .catch(e => {
      this.logining = false;
      console.log(e);
    });

2、封裝ajax函數,並且全局處理口令以及口令刷新的問題

代碼放出核心封裝部分

1、一個headers的封裝。需要在前端有口令數據的情況將口令發送給後端

// 授權函數封裝
const authorize = herders => {
  const user_info = store.get("user_info");
  if (user_info && user_info.sign) {
    herders.Authorization = user_info.sign;
    return herders;
  } else {
    return herders;
  }
};

2、口令刷新函數的封裝

進行一系列的驗證之後,如果後端返回的數據headers裏面產生了口令需要及時刷新口令

// 刷新口令以及判斷數據類型來判斷是否退出登錄
refresh_sign_or_out(res) {
  console.log(res);
  if (!res || !res.data) {
    this.logout();
    return false;
  }
  // type類型爲1必定退出登錄
  if (res.data.type === 1) {
    this.logout();
    return false;
  }
  if (res.headers.authorization) {
    const user_info = store.get("user_info");
    user_info.sign = res.headers.authorization;
    store.set("user_info", user_info);
  }
  return true;
}

四、缺陷回顧

1、目前最大的缺陷是口令只要生成了,那麼就可以驗證通過。

2、如果解決上一個問題,讓口令唯一的話,那麼就需要解決併發情況下口令更新,保證後續接口不會因爲口令刷新而報錯

3、口令唯一化問題

個人:不處理了。目前這樣就可以保證併發的問題,只要不口令唯一化,那麼就不用擔心上述問題。而且個人屬於前端,不打算深入探究了。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章