廢話部分
上次廢話太多,估計有些小夥伴沒看太清楚。這次會精簡很多。只拿關鍵的出來講。
個人屬於前端,後端看到了扣下留情。希望本文能幫助大家
項目地址:
前端: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、口令唯一化問題
個人:不處理了。目前這樣就可以保證併發的問題,只要不口令唯一化,那麼就不用擔心上述問題。而且個人屬於前端,不打算深入探究了。