本文档主要描述基于 JWT 应用如何获得网盘与相册服务PDS授权访问凭证access_token。
本文档中的JWT应用是指使用 JWT(JSON Web Token)机制进行身份认证的自定义应用。
JWT应用可以在服务端通过私钥对数据进行签名得到一个JWT字符串,该JWT字符串作为访问已经配置了公钥的服务端的凭证。
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 创建或选择域
1.2 创建或选择应用
进入域详情,在应用列表界面,创建(选择)一个应用:
1.3 设置公钥
创建(选择)应用后,点击”设置公钥”:
生成公私钥:
生成公私钥后,记得复制私钥,自己保存。然后点确定即可。
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
调用OAuth API换取access_token:
POST /v2/oauth/tokenContent-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 | 申请授权的类型,此处应为字符串常量: |
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。
调用OAuth API换取access_token的请求内容如下:
POST /v2/oauth/tokenContent-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
即可。
方法3:
BasicUI 通过 iframe 嵌入自定义登录页面。
在系统配置中,配置自定义登录页面的 url,和 jwt 的APPID(让 BasicUI 自动刷新token):
用户登录时,不在打开BasicUI的默认登录页面,而是iframe嵌入的打开自定义登录页面。
登录成功后, 通过postMessage 向宿主页传递 token:
if(parent!=self){ let origin = '' parent.postMessage({ code: 'token', message: { access_token: 'xxxx', refresh_token: 'xxxx', ... } }, endpoint || "*") }
JWT应用获取 access_token 以及刷新 access_token 示例代码:
const fs = require('fs')const JWT = require('jsonwebtoken');const axios = require('axios')const DOMAIN_ID = '' // 域IDconst APP_ID = '' // 应用IDconst USER_ID = '' // 用户UIDconst 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})();
示例数据
{ access_token: 'eyJhbG.....g7M0p28', refresh_token: '62f1acc.......9b781f3', expires_in: 7200, token_type: 'Bearer', ...... }
阿里云盘企业版 | 阿里云企业网盘 | 阿里云网盘 | 阿里企业网盘 | 华为云企业网盘 | 腾讯云企业网盘 | 腾讯企业网盘 | 腾讯企业云盘 | 华为云企业邮箱 | 阿里企业邮箱 | 网站建设 | 阿里云服务器 | 异开软件 | 信创邮箱 | 企业网盘帮助