koa 實現 jwt 認證

關於 Token 認證機制,這裏不作更多解釋。不清楚的能夠看個人這篇文章:Web開發中常見的認證機制
GitHub 地址:koa-jwt-samplenode

所需庫

  • bcrypt - 用於加密密碼
  • koa-jwt - jwt 中間件
  • jsonwebtoken - 用於生成token下發給瀏覽器,在 koa2 之後的版本再也不提供 jsonwebtoken 的方法,因此須要另行安裝。

實現思路

整個方案實現的流程和思路很清晰,大體分爲下面幾步:git

  • 自定義 401 攔截中間件,用於攔截 token 不存在或者失效的狀況
  • 配置 koa-jwt
  • 註冊實現
  • 登陸實現

運行項目

該項目須要你已經裝好 mongodb,並啓動。關於 mongodb 的配置見 config/index.jsgithub

npm run start

該項目提供了三個 apiweb

  • /api/register
  • /api/login
  • /api/users

其中 /api/register/api/login 爲 public api,無需token就能訪問。/users 則爲 private api,須要傳入正確的 token 才能訪問。mongodb

自定義 401 handler

使用了 koa-jwt 中間件後,若是沒有token,或者token失效,該中間件會給出對應的錯誤信息。若是沒有自定義中間件的話,會直接將 koa-jwt 暴露的錯誤信息直接返回給用戶。數據庫

// server/middlewares/errorHandle.js
export default errorHandle = (ctx, next) => {
  return next().catch((err) => {
    if (err.status === 401) {
      ctx.status = 401;
      ctx.body = {
        error: err.originalError ? err.originalError.message : err.message,
      };
    } else {
      throw err;
    }
  });
}

而後在 index.js 中使用該中間件npm

app
  .use(errorHandle)

使用 koa-jwt

index.js 中加入 koa-jwt 中間件。json

const secert = 'jwt_secret'
  app
  .use(jwt({
    secret,
  }).unless({
    path: [/\/register/, /\/login/],
  }))

其中 secret 是用於加密的key,不侷限於字符串,也能夠是一個文件。後端

// https://github.com/koajs/jwt#token-verification-exceptions
var publicKey = fs.readFileSync('/path/to/public.pub');
app.use(jwt({ secret: publicKey }));

unless() 用於設置哪些 api 是不須要經過 token 驗證的。也就是咱們一般說的 public api,無需登陸就能訪問的 api。在這個例子中,設置了 /register/login 兩個 api 無需 token 檢查。api

在使用 koa-jwt 後,全部的路由(除了 unless() 設置的路由除外)都會檢查 Header 首部中的 token,是否存在、是否有效。只有正確以後才能正確的訪問。

註冊實現

註冊很簡單,這裏只是簡單的將密碼加密,將信息存入數據庫。實際項目中,還須要對用戶輸入的字段進行驗證。

/**
   * you can register with
   * curl -X POST http://localhost:3200/api/register  -H 'cache-control: no-cache' -H 'content-type: application/x-www-form-urlencoded'  -d 'username=superman2&password=123456'
   */
  async register(ctx) {
    const { body } = ctx.request;
    try {
      if (!body.username || !body.password) {
        ctx.status = 400;
        ctx.body = {
          error: `expected an object with username, password but got: ${body}`,
        }
        return;
      }
      body.password = await bcrypt.hash(body.password, 5)
      let user = await User.find({ username: body.username });
      if (!user.length) {
        const newUser = new User(body);
        user = await newUser.save();
        ctx.status = 200;
        ctx.body = {
          message: '註冊成功',
          user,
        }
      } else {
        ctx.status = 406;
        ctx.body = {
          message: '用戶名已經存在',
        }
      }
    } catch (error) {
      ctx.throw(500)
    }
  }

登陸實現

用戶輸入用戶名和密碼登陸,若是用戶名和密碼正確的話,使用 jsonwebtoken.sign() 生成 token,並返回給客戶端。客戶端將token存儲在本地存儲,在每次的 HTTP 請求中,都將 token 添加在 HTTP Header Authorazition: Bearer token 中。而後後端每次去驗證該token的正確與否。只有token正確後才能訪問到對應的資源。

/** you can login with
   * curl -X POST http://localhost:3200/api/login/ -H 'cache-control: no-cache' -H 'content-type: application/x-www-form-urlencoded' -d 'username=superman2&password=123456'
   */
  async login(ctx) {
    const { body } = ctx.request
    try {
      const user = await User.findOne({ username: body.username });
      if (!user) {
        ctx.status = 401
        ctx.body = {
          message: '用戶名錯誤',
        }
        return;
      }
      // 匹配密碼是否相等
      if (await bcrypt.compare(body.password, user.password)) {
        ctx.status = 200
        ctx.body = {
          message: '登陸成功',
          user: user.userInfo,
          // 生成 token 返回給客戶端
          token: jsonwebtoken.sign({
            data: user,
            // 設置 token 過時時間
            exp: Math.floor(Date.now() / 1000) + (60 * 60), // 60 seconds * 60 minutes = 1 hour
          }, secret),
        }
      } else {
        ctx.status = 401
        ctx.body = {
          message: '密碼錯誤',
        }
      }
    } catch (error) {
      ctx.throw(500)
    }
  }

須要注意的是,在使用 jsonwebtoken.sign() 時,須要傳入的 secret 參數,這裏的 secret 必需要與 前面設置 jwt() 中的 secret 一致。

更多關於 jsonwebtoken 的方法,可見:https://github.com/auth0/node-jsonwebtoken

在登陸後,拿着返回的 token,這時候去訪問 /api/users,就能正確得到用戶列表。

curl -X GET http://localhost:3200/api/users -H 'authorization: Bearer token' -H 'cache-control: no-cache'
相關文章
相關標籤/搜索