天天看點

前後端分離OAuth授權登入實作方式

作者:速學前端

大家好,今天我們來講講OAuth的實作方式。OAuth有授權碼模式和密碼模式,通常情況下我們都是使用授權碼模式。應用首先會點選一個授權連結,然後攜帶應用ID和重定向位址,第三方認證伺服器在接收到對應請求後,如果驗證成功,會繼續跳轉到上一步傳過來的重定向位址,并攜帶一個code,應用可以根據code向第三方認證伺服器擷取通路token,到了這一步授權就完成了。後面可以根據token向第三方授權伺服器擷取對應的授權資訊。

我們以碼雲作為OAuth的示範,碼雲OAuth的驗證流程如下圖:

前後端分離OAuth授權登入實作方式

為了示範,我們需要先在碼雲上申請一個應用。

1、在 修改資料 -> 第三方應用,建立要接入碼雲的應用。

前後端分離OAuth授權登入實作方式

2、填寫應用相關資訊,勾選應用所需要的權限。其中: 回調位址是使用者授權後,碼雲回調到應用,并且回傳授權碼的位址。

前後端分離OAuth授權登入實作方式

3、建立成功後,會生成 Cliend ID 和 Client Secret。他們将會在上述OAuth2 認證基本流程用到。

前後端分離OAuth授權登入實作方式

應用建立成功後,就可以進入編碼了。我們以egg.js作為我們後端的實作。

1、安裝

$ mkdir oauth && cd oauth
$ npm init egg --type=ts
$ npm i
$ npm run dev           

2、全局配置gitee參數

修改oauth/config/config.default.ts,在其中添加一個gitee的配置,并擴充到bizConfig。

const giteeOauthConfig = {
  client_id: '510c2a54e0c942bc13b5ca8e88d4048f46af9a5535f',
  client_secret: '2c64bcefcd37f10ff99f5cc84bc9e6739',
  redirect_uri: 'http://localhost:7001/api/users/passport/gitee/callback',
  authURL: 'https://gitee.com/oauth/token?grant_type=authorization_code',
  giteeUserAPI: 'https://gitee.com/api/v5/user',
};

const bizConfig = {
  giteeOauthConfig,
};           

3、編寫controller方法

在oauth/app/controller目錄下添加一個user.ts,我們在其中實作兩個方法oauth,oauthByGitee,分别處理授權連結的跳轉與code換取token的操作。

import { Controller } from 'egg';

export default class UserController extends Controller {
  public async oauth() {
    
  }
  public async oauthByGitee() {
   
  }
}
           

同時我們在oauth/app/router.ts中添加對應對控制器路由

import { Application } from 'egg';

export default (app: Application) => {
  const { controller, router } = app;

  router.get('/', controller.home.index);

  // 使用者子產品
  router.get('/api/users/passport/gitee', controller.user.oauth);
  router.get('/api/users/passport/gitee/callback', controller.user.oauthByGitee);
};
           

4、oauth方法實作

oauth方法中主要處理應用授權連結的跳轉,跳轉時我們需要傳遞應用ID與回調位址。

public async oauth() {
  const { ctx, app } = this;
  const { client_id, redirect_uri } = app.config.giteeOauthConfig;
  const url = `https://gitee.com/oauth/authorize?client_id=${client_id}&redirect_uri=${redirect_uri}&response_type=code`;
  ctx.redirect(url);
}           

我們從app.config.giteeOauthConfig擷取對應的配置,然後拼接成授權連結,并使用egg提供的ctx.redirect方法進行重定向跳轉。目前端通路授權接口時,服務端重定向到第三方授權伺服器頁面

前後端分離OAuth授權登入實作方式

使用者點選同意授權,第三方授權伺服器會根據傳過去的redirect_uri參數進行重定向跳轉,并攜帶一個code。我們在前面配置了redirect_uri,根據路由配置重定向後就進入到了oauthByGitee方法中。我們添加oauthByGitee的實作:

public async oauthByGitee() {
  const { ctx } = this;
  const { code } = ctx.query;
  try {
    const result = await ctx.service.user.loginByGitee(code);
    ctx.body = result;
  } catch (error) {
    console.log(error);
  }
}           

oauthByGitee中通過code,擷取了通路token,服務端根據擷取到的token向碼雲擷取了對應的使用者資訊,對應的service代碼如下:

import { Service } from 'egg';

/**
 * Test Service
 */
export default class User extends Service {


  /**
     * 通過使用者授權碼從碼雲擷取對應使用者資訊
     * @param code 使用者授權碼
     */
  public async getAccessToken(code:string) {
    const { ctx, app } = this;
    const { client_id, redirect_uri, client_secret, authURL } = app.config.giteeOauthConfig;
    const access_token = await ctx.curl(authURL, {
      method: 'POST',
      contentType: 'json',
      dataType: 'json',
      data: {
        code,
        client_id,
        redirect_uri,
        client_secret,
      },
    });
    return access_token;
  }

  public async getGiteeUserData(access_token: string) {
    const { ctx, app } = this;
    const { giteeUserAPI } = app.config.giteeOauthConfig;
    const { data } = await ctx.curl(`${giteeUserAPI}?access_token=${access_token}`, {
      dataType: 'json',
    });
    return data;
  }

  public async loginByGitee(code: string) {
    // 擷取access_token
    const access_token = await this.getAccessToken(code);
    // 擷取使用者資訊
    const user = await this.getGiteeUserData(access_token.data.access_token);
    return user;
  }
}
           

在loginByGitee,我們擷取了access_token,并根據access_token擷取了使用者資訊。此時oauth授權登入我們就完成了一半,也就是隻完成了授權,登入還沒實作。我們從第三方拿到了使用者資訊,此時我們就可以根據這些資訊來完成登入的操作。

前後端分離OAuth授權登入實作方式

5、前後端分離授權方式

目前市面上的應用基本是前後端分離的,通常我們前後端是分開部署的,此時就會有一個跨源通信的問題。前面我們已經根據第三方提供的資訊生成了自己的使用者token,那麼在前後端分離的情況下我們怎麼将token傳給前端呢?其實浏覽器已經給我們提供了方法,我們可以通過postMessage來實作跨源通信。

前後端分離OAuth授權登入實作方式

前端通過window.open打開授權連結,使用者同意授權後,跳轉到回調頁面。後端拿到token後自己渲染一個頁面提示授權成功,渲染的頁面邏輯:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" href="/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>授權成功</title>
  </head>
  <body>
   <h1>授權 成功</h1>
   <h2>兩秒後關閉</h2>
  </body>
  <script>
    window.onload=function(){
        setTimeout(()=>{
            window.opener.postMessage('{{token}}','http://127.0.0.1:5173/')
            window.close()
        },2000)
    }
  </script>
</html>
           

這時候前端通過addEventListener就可以監聽到對應事件,并接收對應參數了,這樣就授權成功了。

繼續閱讀