跳转到内容

Native Application 流程

对于代表已登录用户从其自己的机器上执行操作的集成(如桌面应用、CLI 或 CAD 插件),请使用 Native app 流程。它使用带 PKCE 的 Authorization Code本地主机回环重定向无客户端密钥。本指南将引导你完成从创建 OAuth 应用到首次 API 调用的设置。

如果你尚未了解前提条件、网络访问和安全模型,请先查看快速入门指南。


  1. 创建 OAuth 应用:在 Prevu3D 中注册一个原生应用并获取 Client ID
  2. 配置用户访问权限:确保登录用户能够访问其所需的数据
  3. 使用 PKCE 授权:引导用户通过浏览器同意页面
  4. 用代码交换令牌:用授权码换取访问令牌
  5. 查找你的 API URL:发现你组织的基础 URL(因区域而异)
  6. 进行首次调用:使用简单请求测试连接
  1. 登录 Prevu3D 平台
  2. 前往 SettingsOAuth
  3. 点击以创建新应用。
  4. 启用 Authorization Code flow
  5. 启用 Native Application。重定向 URI 被固定为 http://localhost
  6. 将客户端密钥保持关闭。原生应用不使用客户端密钥。
  7. 复制并安全存储你的 Client ID。每次授权都需要它。

原生流程以已登录用户身份调用 API,而非服务用户。这涵盖安全模型的第 2 层和第 3 层:用户可以看到哪些节点(内容访问),以及它可以对它们做什么(角色 / 权限访问)。

  1. 确保将要登录的用户对你的用例所需节点(组织、部门、站点等)具有内容访问权限
  2. 确保该用户在这些节点上拥有一个角色,具备你的集成所需的权限(读取、编辑、管理等)。

在同意屏幕上,用户还会选择要授权哪个组织。用户在此处批准的作用域配置第 1 层

原生应用无法保存密钥,因此该流程使用 PKCE 来保护授权码。在打开同意页面之前,生成三个值:

方法
code_verifier随机字符串,43 到 128 个字符
code_challenge无填充的 BASE64URL(SHA256(code_verifier))
state随机字符串;当重定向返回时验证它

在一个空闲端口上启动本地回环服务器,然后打开用户的浏览器至同意页面。

同意 URL: https://cloud.prevu3d.com/oauth

查询参数(全部必需):

参数
response_typecode
code_challenge_methodS256
client_id你的 Client ID
redirect_uri你的回环 URI,例如 http://localhost:8765(包含端口,无尾部斜杠)
scopes每个作用域一个,例如 scopes=read:basic&scopes=read:hierarchy
code_challenge上面的 PKCE 挑战
state你的随机 state 值

用户登录并点击 Allow 后,浏览器重定向到你的回环 URI:

http://localhost:8765/?code={authorization_code}&state={state}

如果用户拒绝访问,你会收到 ?error=access_denied。授权码在 60 秒后过期,因此请立即交换它们。

端点: POST https://cloud-api.prevu3d.com/oauth/token

标头: Content-Type: application/x-www-form-urlencoded

正文:

字段
grant_typeauthorization_code
client_id你的 Client ID
code来自重定向的代码
redirect_uri步骤 3 中使用的相同 URI(包含端口)
code_verifier原始的 PKCE verifier

不要为原生应用发送客户端密钥。

示例响应:

{
"hasError": false,
"expires_in": 86400,
"access_token": "eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9...",
"refresh_token": "..."
}

保存两个令牌。使用 access_token 进行 API 调用。当它过期时,用 grant_type=refresh_token 和你存储的 refresh_token 请求新令牌(无需客户端密钥)。

调用发现端点以获取你组织的 apiUrl。不要硬编码区域 URL。

端点: GET https://cloud-api.prevu3d.com/oauth/api-info

标头: Authorization: Bearer <your_access_token>

示例响应:

{
"user": { "..." : "..." },
"organization": {
"id": "217ebd23-ec54-4af0-a6d6-4a441a6d1966",
"name": "Test Organization"
},
"scopes": ["read:basic", "read:hierarchy"],
"apiUrl": "https://api-ue1.prevu3d.com/realityconnect-api"
}

apiUrl 值用作你所有 API 调用的基础。另请注意 organization.id,许多端点都需要它。

你现在拥有所需的一切:访问令牌和你的基础 URL。让我们进行一个简单的请求来检索你组织的部门:

GET {apiUrl}/v1/nodes/{organization_id}/browse
Authorization: Bearer <your_access_token>

使用真实值的示例:

GET https://api-ue1.prevu3d.com/realityconnect-api/v1/nodes/217ebd23-ec54-4af0-a6d6-4a441a6d1966/browse HTTP/1.1
Authorization: Bearer eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9...

包含部门列表的成功响应确认你的集成正在正确工作。

这是一个完成上述所有操作的完整脚本。将 client_id 替换为你的 Client ID,然后运行它。它会启动一个临时回环服务器,在你的浏览器中打开同意页面,并在你批准后打印你组织的部门。

import base64
import hashlib
import json
import secrets
import webbrowser
from http.server import BaseHTTPRequestHandler, HTTPServer
from urllib.parse import parse_qs, quote, urlencode, urlparse
import requests
client_id = "your-client-id"
base_url = "https://cloud-api.prevu3d.com"
scopes = ["read:basic", "read:hierarchy"]
# Step 1: Authorize with PKCE and get an access token
verifier = secrets.token_urlsafe(48)[:64]
challenge = base64.urlsafe_b64encode(hashlib.sha256(verifier.encode()).digest()).decode().rstrip("=")
state = secrets.token_urlsafe(32)
auth_result = {"code": None, "state": None}
class Handler(BaseHTTPRequestHandler):
def log_message(self, *args):
pass
def do_GET(self):
query = parse_qs(urlparse(self.path).query)
auth_result["code"] = query.get("code", [None])[0]
auth_result["state"] = query.get("state", [None])[0]
self.send_response(200)
self.end_headers()
server = HTTPServer(("localhost", 0), Handler)
redirect_uri = f"http://localhost:{server.server_address[1]}"
consent_params = urlencode(
{
"response_type": "code",
"code_challenge_method": "S256",
"client_id": client_id,
"redirect_uri": redirect_uri,
"code_challenge": challenge,
"state": state,
}
)
consent_url = f"{base_url.replace('cloud-api.', 'cloud.', 1)}/oauth?{consent_params}"
consent_url += "&" + "&".join(f"scopes={quote(scope)}" for scope in scopes)
webbrowser.open(consent_url)
server.handle_request()
if auth_result["state"] != state:
raise RuntimeError("Invalid state")
if not auth_result["code"]:
raise RuntimeError("Authorization failed")
token_response = requests.post(
f"{base_url}/oauth/token",
data={
"grant_type": "authorization_code",
"client_id": client_id,
"code": auth_result["code"],
"redirect_uri": redirect_uri,
"code_verifier": verifier,
},
)
token_response.raise_for_status()
access_token = token_response.json()["access_token"]
# Step 2: Get your API URL and org ID
api_info = requests.get(
f"{base_url}/oauth/api-info",
headers={"Authorization": f"Bearer {access_token}"},
).json()
api_url = api_info["apiUrl"]
organization_id = api_info["organization"]["id"]
# Step 3: Browse divisions
divisions = requests.get(
f"{api_url}/v1/nodes/{organization_id}/browse",
headers={"Authorization": f"Bearer {access_token}"},
).json()
print("Divisions:", json.dumps(divisions, indent=2))
如果你看到…尝试这样做…
点击 Allow 后出现 Invalid OAuth grant request确认你使用的是原生应用的 Client ID(已启用 Authorization Code 和 Native app)。仅支持 Client Credentials 的应用没有重定向 URI,无法完成此流程。
关于查询参数的同意页面错误确保 response_type=codecode_challenge_method=S256 以及所有必需参数都存在,包括 statescopes
令牌交换时出现 400403代码可能已过期(60 秒限制),redirect_uri 可能与步骤 3 不匹配,或者 PKCE verifier 和 challenge 可能不匹配。
调用 API 时出现 403 Forbidden请求必须通过全部三个安全层。检查 OAuth 作用域、已登录用户的内容访问权限,以及该用户在目标节点上的角色。
API 响应中的组织错误用户在同意屏幕上选择组织。重新授权并选择正确的组织。

有关连接、DNS 和 API URL 问题,请参阅快速入门

  • 添加逻辑,使用 grant_type=refresh_token 和你存储的刷新令牌在令牌过期前刷新它。
  • 探索 API 参考以查看所有可用操作。