本文由簡體中文內容自動轉碼而成。阿里雲不保證此自動轉碼的準確性、完整性及時效性。本文内容請以簡體中文版本為準。

JWT應用接入

更新時間:2024-07-06 01:53

本文檔主要描述基於 JWT 應用如何獲得網盤與相簿服務PDS授權訪問憑證access_token。

JWT 應用介紹

本文檔中的JWT應用是指使用 JWT(JSON Web Token)機制進行身份認證的自訂應用

JWT應用可以在服務端通過私密金鑰對資料進行簽名得到一個JWT字串,該JWT字串作為訪問已經配置了公開金鑰的服務端的憑證。

image

適用情境

  1. 企業已有內部的軟體系統,包含獨立的帳號體系,想通過內部的登入頁面登入,然後使用PDS的功能。

  2. 企業已有獨立的帳號體系和登入入口,想要使用已有的登入入口結合 PDS 搭建一套已有獨立帳號的雲端儲存系統。

接入步驟概覽

  1. 在 PDS 控制台建立自訂網域和JWT應用。

  2. 利用RSA演算法建立一對公私密金鑰,將公開金鑰儲存到PDS服務端,私密金鑰儲存到JWT應用服務端。

  3. JWT應用服務端將資料進行編碼並用私密金鑰進行簽名產生JWT Assertion字串,然後發送給PDS服務端。

  4. PDS服務端使用公開金鑰驗證 JWT Assertion 字串合法後,返回 AccessToken 給JWT應用服務端,JWT應用服務端可以通過 AccessToken 來調用PDS服務端提供的API。

詳細步驟

1 配置密鑰

1.1 建立或選擇域

a1

1.2 建立或選擇應用

進入域詳情,在應用列表介面,建立(選擇)一個應用:

k1

1.3 設定公開金鑰

建立(選擇)應用後,點擊”設定公開金鑰”:

k3

產生公私密金鑰:k5

產生公私密金鑰後,記得複製私密金鑰,自己儲存。然後點確定即可。

k4

2 擷取ACCESS_TOKEN

2.1 應用服務端計算JWT字串

將待簽名的資料進行編碼,並使用私密金鑰通過指定的密碼編譯演算法對其進行簽名,產生JWT字串。下面是Node.js 的參考代碼:

const JWT = require('jsonwebtoken');

function signAssertion({ domain_id, client_id, user_id, privateKeyPEM }) {
  var now_sec = parseInt(Date.now() / 1000);
  var opt = {
    iss: client_id,
    sub: user_id,
    sub_type: "user",
    aud: domain_id,
    jti: Math.random().toString(36).substring(2),
    exp: now_sec + 60,
    // iat: now_sec,
    // nbf: '',
    auto_create: false,
  };
  return JWT.sign(opt, privateKeyPEM, {
    algorithm: "RS256",
  });
}

opt 參數說明

欄位名

是否必選

類型

描述

欄位名

是否必選

類型

描述

iss

必選

String

App ID

sub

必選

String

User ID、Domain ID

sub_type(擴充欄位)

必選

String

帳號類型,目前支援填 user、service,此處填user,則sub為userID,簽發普通使用者accessToken。 此處填service,則sub為domainID,簽發domain服務帳號accessToken(超級管理員權限)

aud

必選

String

Domain ID

jti

必選

String

應用產生JWT的唯一標識,長度16-128位,推薦使用uuid即可

exp

必選

Integer

JWT到期時間, Unix Time,單位秒,生效時間和到期時間不能超過15分鐘。為防止用戶端和伺服器時間不一致,此時間建議設定為目前時間加5分鐘。

iat

可選

Integer

簽發時間,Unix Time,單位秒,在此時間之前無法使用,如:1577682075

nbf

可選

Integer

生效時間,Unix Time,單位秒,不指定則預設為目前時間。生效時間和到期時間不能超過15分鐘。 為防止用戶端和伺服器時間不一致,此時間建議設定為目前時間減5分鐘,或者不設定。

auto_create(擴充欄位)

可選

Boolean

如果使用者不存在,則自動建立,預設不建立使用者。

更多關於JWT的三方庫和計算方法請參考JWT官網

2.2 通過JWT字串換取的access_token

調用 Authorize 介面換取 access_token:

POST /v2/oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&client_id=${APP_ID}&assertion=xxxxxxxxxx
說明

注意:要佈建要求的 content-type 為 application/x-www-form-urlencoded 注意:請求參數要放在body裡

請求參數說明

欄位名

是否必選

類型

描述

欄位名

是否必選

類型

描述

grant_type

必選

String

申請授權的類型,此處應為字串常量:urn:ietf:params:oauth:grant-type:jwt-bearer

client_id

必選

String

應用ID

assertion

必選

String

上一步驟計算出來的JWT

返回 token json 範例

{
  "access_token": "eyJh****eQdnUTsEk4",
  "refresh_token": "kL***Lt",
  "expires_in": 7200,
  "token_type": "Bearer"
}

應用服務端拿到 access_token 後返回給應用Web端,調用PDS API的時候帶上 access_token 就可以訪問使用者在PDS 上的資源。

2.3 更新access_token

通過JWT方式擷取的access_token的有效期間只有2小時,超過2小時後access_token將到期,到期後可以再次執行步驟2.1和2.2的方法來擷取一個新的access_token。 還有一種方法是在7天內可以調用PDS API通過到期的access_token來擷取新的access_token,7天后需要重新按照步驟2.1和2.2擷取access_token。

調用 Authorize 介面換取 access_token 的請求內容如下:

POST /v2/oauth/token
Content-Type: application/x-www-form-urlencoded

client_id=${APPID}&refresh_token=${access_token}&grant_type=refresh_token&redirect_uri=${REDIRECT_URI}

欄位名

是否必選

類型

描述

欄位名

是否必選

類型

描述

client_id

必選

String

應用ID

refresh_token

必選

String

已到期的access_token

grant_type

必選

String

申請授權的類型,此處應為字串常量”refresh_token”

redirect_uri

必選

String

建立App時填寫的回調地址

3 使用 Basic UI (可選)

如果您不想自己開發UI,而我們官方提供的Basic UI可以滿足您的要求,可以直接使用Basic UI。

方法1:

使用window.open 開啟 basic ui,postMessage傳遞AccessToken過去即可。

範例程式碼:

const endpoint = `https://${domain_id}.apps.aliyunpds.com`
const url = `${endpoint}/accesstoken?origin=${location.origin}`
var win = window.open(url)

window.addEventListener('message', onMessage, false)
async function onMessage(e) {
  if (e.data.code == 'token' && e.data.message == 'ready') {
    var result = await getToken();//  從服務端擷取 AccessToken
    //result = {"access_token": ...}
    win.postMessage({
      code: 'token',
      message: result
    }, endpoint || '*')

    window.removeEventListener('message', onMessage)
  }
}

方法2:

使用 iframe 嵌入 basic ui,postMessage 傳遞 AccessToken 過去即可。

範例程式碼:

const endpoint = `https://${domain_id}.apps.aliyunpds.com`
//iframe嵌入URL構成:
const iframeURL = `${endponit}/accesstoken?origin=${location.origin}`

html代碼:

//注意替換變數iframeURL
<iframe id="ifr" src="iframeURL"></iframe>
window.addEventListener('message', onMessage)
async function onMessage(e) {
  if (e.data.code == 'token' && e.data.message == 'ready') {
    var result = await getToken();//  從服務端擷取 AccessToken
    //result = {"access_token": ......}
    document.getElementById('ifr').contentWindow.postMessage({
        code: 'token',
        message: result
    }, endpoint || '*')

    window.removeEventListener('message', onMessage)
  }
}
說明

注意:使用方法2,還需要在basic ui中配置這個安全設定,把宿首頁的origin配置上

假設宿首頁為 https://example.com/a.html, origin為 https://example.com, 這裡配置example.com 即可。

image

方法3:

BasicUI 通過 iframe 嵌入自訂登入頁面。

在系統配置中,配置自訂登入頁面的 url,和 jwt 的APPID(讓 BasicUI 自動重新整理token):

image

使用者登入時,不在開啟BasicUI的預設登入頁面,而是iframe嵌入的開啟自訂登入頁面。

登入成功後, 通過postMessage 向宿首頁傳遞 token:

image

if(parent!=self){
  let origin = ''
  parent.postMessage({
    code: 'token',
    message: {
       access_token: 'xxxx',
       refresh_token: 'xxxx',
       ...
    }
  }, endpoint || "*")
}

附錄1:Node.js 代碼實現

JWT應用擷取 access_token 以及重新整理 access_token 範例程式碼:

const fs = require('fs')
const JWT = require('jsonwebtoken');
const axios = require('axios')

const DOMAIN_ID = '' // 域ID
const APP_ID = '' // 應用ID
const USER_ID = '' // 使用者UID
const PRIVATE_KEY_PEM = '' // 私密金鑰,步驟1.3配置的私密金鑰
const PRE = `https://${domain_id}.api.aliyunpds.com`

async function init() {
  try {
    //這幾個變數需要根據實際情況填寫 
    var params = {
      domain_id: DOMAIN_ID,
      client_id: APP_ID,
      user_id: USER_ID,
      privateKeyPEM: PRIVATE_KEY_PEM,
    };
    var assertion = signAssertion(params)
    var obj = await getToken(assertion)
    return obj.data
  } catch (e) {
    if (e.response) {
      console.log(e.response.status)
      console.log(e.response.headers)
      console.log(e.response.data)
    } else {
      console.error(e)
    }
  }
}

function signAssertion({ domain_id, client_id, user_id, privateKeyPEM }) {
  var now_sec = parseInt(Date.now()/1000)
  var opt = {
    iss: client_id,
    sub: user_id,
    sub_type: 'user',
    aud: domain_id,
    jti: Math.random().toString(36).substring(2),
    exp: now_sec + 300,
    // iat: now_sec,
    // nbf: '',
    auto_create: true,
  };
  return JWT.sign(opt, privateKeyPEM, {
    algorithm: 'RS256'
  });
}

async function getToken(assertion) {
  return await axios({
    method: 'post',
    url: PRE + '/v2/oauth/token',
    //注意:要佈建要求的 content-type 為 application/x-www-form-urlencoded
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded'
    },
    //注意:請求參數要放在body裡
    data: params({
      grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
      client_id: APP_ID,
      assertion
    })
  })
}

async function refreshToken(refresh_token) {
  return await axios({
    method: 'post',
    url: PRE + '/v2/oauth/token',
    //注意:要佈建要求的 content-type 為 application/x-www-form-urlencoded
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded'
    },
    //注意:請求參數要放在body裡
    data: params({
      grant_type: 'refresh_token',
      client_id: APP_ID,
      refresh_token,
    })
  })
}

function params(m){
  const params = new URLSearchParams();
  for(var k in m){ 
     params.append(k, m[k]);
  }
  return params;
}

//調用測試
;(async ()=>{
  let result = await init() 
  console.log(result) // 返回token對象{access_token:...},對象結構參考附錄2
  // access_token 失效後
  refreshToken(result.refreshToken) // 返回一個新的token對象{access_token:...},對象結構考附錄2
})();

附錄2:token對象結構

樣本資料

{
  access_token: 'eyJhbG.....g7M0p28',
  refresh_token: '62f1acc.......9b781f3',
  expires_in: 7200,
  token_type: 'Bearer',
  ......
}

參數說明請參考Token - 擷取存取權杖

  • 本頁導讀 (1, M)
  • JWT 應用介紹
  • 適用情境
  • 接入步驟概覽
  • 詳細步驟
  • 1 配置密鑰
  • 2 擷取ACCESS_TOKEN
  • 3 使用 Basic UI (可選)
  • 附錄1:Node.js 代碼實現
  • 附錄2:token對象結構
文檔反饋
phone 聯絡我們