如何用Eggjs從零開始開發一個專案(2)
在上一篇文章,我們已經使用Sequelize連線上了資料庫,並能進行簡單的資料庫操作,在此基礎上,我們試著來開發一個完整的專案。這篇文章我們從使用者的註冊、登入著手,試著開發使用者模組的相關的程式碼。
使用者註冊
1. 註冊邏輯
使用者註冊的邏輯很簡單:
- 客戶端:使用者輸入輸入賬號,密碼等資訊進行使用者註冊;
- 服務端:接收到客戶端提交的註冊資訊後,進行欄位的檢驗(是否必填、欄位長度等),欄位符合要求後,根據使用者註冊的賬號查詢資料庫,根據返回結果判斷該使用者是否是新使用者,如果是新使用者,將使用者資訊寫入到資料庫,完成註冊流程。
2. 使用者密碼處理
客戶端使用者提交資料後,服務端驗證通過進行資料庫寫入,但是其中使用者密碼是敏感資訊,為了服務安全考慮,不能直接將明文密碼寫入到資料庫,防止資料庫被攻擊,使用者密碼洩露。所以一般在儲存使用者密碼時,會先對使用者密碼進行加鹽加密處理,這樣哪怕資料庫儲存的密碼洩露,其他人也無法通過處理後的密碼進行登入。這裡使用雜湊演算法對密碼進行加密,因為雜湊的特性是不可逆,具體的細節可以參考
廢話不多說,我們下面來寫程式碼。
首先,我們安裝一下bcryptjs
,我們使用它對密碼進行加鹽加密和比對:
npm install bcryptjs --save
然後我們把這兩個方法寫到app/extend/helper.js
:
const bcrypt = require('bcryptjs'); module.exports = { encrypt(password) { const salt = bcrypt.genSaltSync(5); //加鹽 const hash = bcrypt.hashSync(password, salt); //雜湊(同步呼叫) return hash; }, compare(password, hash) { return bcrypt.compareSync(password, hash); //比對 } };
這樣我們在專案裡就可以通過this.ctx.helop.encrypt
和this.ctx.helop.compare
的方式去使用這些公用的方法了。
然後,在UserController
中新增一個register
方法:
async register() { const params = this.ctx.request.body; // 引數校驗 if (!params.name || !params.password || !params.phone || !params.email) { this.ctx.body = { code: '500', msg: '引數不合法' }; } // 查詢該使用者是否已經註冊 const user = await this.ctx.model.User.findOne({ where: { name: params.name } }); if (user) { this.ctx.body = { code: '500', msg: '該使用者已存在' }; } // 插入資料庫 const result = await this.ctx.model.User.create({ ...params, password: this.ctx.helper.encrypt(params.password) }); if (result) { this.ctx.body = { code: '200', msg: '註冊成功' }; } }
最後,新增路由:
// app/router.js
router.post('/register', controller.user.register);
3.功能測試
測試一下,用postman建立一個post請求到localhost:7001/register
,傳入註冊使用者資訊:
{
"name":"xiaoming",
"password":"test1234",
"phone":"13412341234",
"email":"[email protected]"
}
服務端返回“註冊成功”的提示語,這時候我們去資料庫就能看到這條資料了,而且密碼是一坨看不懂的密文,這個時候我們用同樣的資料再次發起請求,服務端返回“該使用者已註冊”,說明我們的註冊功能已經完成了!
使用者登入
使用者登入的邏輯很簡單,就是一個客戶端傳入的使用者名稱密碼與伺服器儲存的相比較,匹配就登入成功,不然就是使用者名稱密碼錯誤。但是,我們還需要額外考慮一個問題,http請求是無狀態的,那我們怎麼去記住使用者的登入狀態和登入資訊呢,並且每次向服務端發起請求都帶上這些資訊呢?
常用的使用者認證方式有兩種,一種是通過Cookie實現,一種是Token的實現方式。關於使用者授權認證可以看看這篇文章:授權認證登入之 Cookie、Session、Token、JWT 詳解,Eggjs官網也有一個章節講了Cookie和Session相關的知識Cookie 與 Session,學習服務端,掌握這些知識還是很必要的。
在這裡,我們選擇JWT作為我們的解決方案。關於JWT,這裡提供兩篇文章作為參考,JSON Web Token 入門教程,Introduction to JSON Web Tokens。OK,原理看完以後我們來寫程式碼。
首先我們安裝egg-jwt
外掛:
npm install egg-jwt --save
引入外掛:
// app/config/plugin.js
jwt: {
enable: true,
package: "egg-jwt"
}
配置jwt secret:
// app/config/config.default.js
config.jwt = {
secret: '12312456
};
OK,我們下面編寫程式碼的程式碼:
// app/controller/user.js
async login() {
const params = this.ctx.request.body;
if (!params.name || !params.password) {
this.ctx.body = {
code: '500',
msg: '引數不合法'
}
}
const user = await this.ctx.model.User.findOne({ where: {
name: params.name
} });
if (!user) {
this.ctx.body= {
code: '500',
msg: '使用者名稱密碼錯誤'
}
}
//校驗密碼
const checkPassword = this.ctx.helper.compare(
params.password,
user.password
);
if (checkPassword) {
// 根據使用者名稱稱建立token,過期時間為2小時
const token = this.app.jwt.sign({
name: user.name
}, this.app.config.jwt.secret, { expiresIn: '2h' });
this.ctx.body = {
code: '200',
data: token
}
} else {
this.ctx.body = {
code: '500',
msg: '使用者名稱密碼錯誤'
}
}
}
配置路由:
// app/router.js
router.post('/login', controller.user.login);
程式碼完畢,讓我們測試一下,建立一個post請求到localhost:7001/login
,輸入使用者名稱密碼:
{
"name":"xiaoming",
"password":"test1234"
}
結果如下:
{
"code": "200",
"data": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoieGlhb21pbmciLCJpYXQiOjE2MDg1NTkzMTYsImV4cCI6MTYwODU2NjUxNn0.SyXAhVvrwAql-4FzaZrlEs6dsEJ4wXbdjQsHv43CSOI"
}
成功了,這一大坨就是我們建立的token。
嗯……我們是拿到了token,但是怎麼用呢?還記得我們最開始寫的兩個介面嗎?我們現在給他配置jwt驗證,然後試試不登入能不能訪問。
// app/router.js
router.post('/createUser', app.jwt, controller.user.createUser);
router.get('/getUsers', app.jwt, controller.user.getUsers);
然後我們重新測試一下這兩個介面,服務端返回401 Unauthorized
,這說明驗證生效了。那我們在請求的時候把剛才登入介面獲取到的token設定在Authorization
的Bearer Token
欄位中,再試一次,成功了!
如果你跟我走到了這一步,那說明你已經距離一個合格的服務端開發者更近了一步,但是這裡我們好像並沒有用到token
裡面攜帶的資訊,下一篇我們將會解析token
攜帶的資訊,知道每次都是哪個使用者在訪問我們的服務,而且我們將優化我們的程式碼,讓它看起來更簡潔、合理。