Native Application 流程
对于代表已登录用户从其自己的机器上执行操作的集成(如桌面应用、CLI 或 CAD 插件),请使用 Native app 流程。它使用带 PKCE 的 Authorization Code、本地主机回环重定向且无客户端密钥。本指南将引导你完成从创建 OAuth 应用到首次 API 调用的设置。
如果你尚未了解前提条件、网络访问和安全模型,请先查看快速入门指南。
你将要做什么
Section titled “你将要做什么”- 创建 OAuth 应用:在 Prevu3D 中注册一个原生应用并获取 Client ID
- 配置用户访问权限:确保登录用户能够访问其所需的数据
- 使用 PKCE 授权:引导用户通过浏览器同意页面
- 用代码交换令牌:用授权码换取访问令牌
- 查找你的 API URL:发现你组织的基础 URL(因区域而异)
- 进行首次调用:使用简单请求测试连接
步骤 1:创建你的 OAuth 应用
Section titled “步骤 1:创建你的 OAuth 应用”- 登录 Prevu3D 平台。
- 前往 Settings → OAuth。
- 点击以创建新应用。
- 启用 Authorization Code flow。
- 启用 Native Application。重定向 URI 被固定为
http://localhost。 - 将客户端密钥保持关闭。原生应用不使用客户端密钥。
- 复制并安全存储你的 Client ID。每次授权都需要它。
步骤 2:配置用户访问权限
Section titled “步骤 2:配置用户访问权限”原生流程以已登录用户身份调用 API,而非服务用户。这涵盖安全模型的第 2 层和第 3 层:用户可以看到哪些节点(内容访问),以及它可以对它们做什么(角色 / 权限访问)。
- 确保将要登录的用户对你的用例所需节点(组织、部门、站点等)具有内容访问权限。
- 确保该用户在这些节点上拥有一个角色,具备你的集成所需的权限(读取、编辑、管理等)。
在同意屏幕上,用户还会选择要授权哪个组织。用户在此处批准的作用域配置第 1 层。
步骤 3:使用 PKCE 授权
Section titled “步骤 3:使用 PKCE 授权”原生应用无法保存密钥,因此该流程使用 PKCE 来保护授权码。在打开同意页面之前,生成三个值:
| 值 | 方法 |
|---|---|
code_verifier | 随机字符串,43 到 128 个字符 |
code_challenge | 无填充的 BASE64URL(SHA256(code_verifier)) |
state | 随机字符串;当重定向返回时验证它 |
在一个空闲端口上启动本地回环服务器,然后打开用户的浏览器至同意页面。
同意 URL: https://cloud.prevu3d.com/oauth
查询参数(全部必需):
| 参数 | 值 |
|---|---|
response_type | code |
code_challenge_method | S256 |
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 秒后过期,因此请立即交换它们。
步骤 4:用代码交换访问令牌
Section titled “步骤 4:用代码交换访问令牌”端点: POST https://cloud-api.prevu3d.com/oauth/token
标头: Content-Type: application/x-www-form-urlencoded
正文:
| 字段 | 值 |
|---|---|
grant_type | authorization_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 请求新令牌(无需客户端密钥)。
步骤 5:获取你的 API 基础 URL
Section titled “步骤 5:获取你的 API 基础 URL”调用发现端点以获取你组织的 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,许多端点都需要它。
步骤 6:进行你的首次 API 调用
Section titled “步骤 6:进行你的首次 API 调用”你现在拥有所需的一切:访问令牌和你的基础 URL。让我们进行一个简单的请求来检索你组织的部门:
GET {apiUrl}/v1/nodes/{organization_id}/browseAuthorization: Bearer <your_access_token>使用真实值的示例:
GET https://api-ue1.prevu3d.com/realityconnect-api/v1/nodes/217ebd23-ec54-4af0-a6d6-4a441a6d1966/browse HTTP/1.1Authorization: Bearer eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9...包含部门列表的成功响应确认你的集成正在正确工作。
用 Python 试试
Section titled “用 Python 试试”这是一个完成上述所有操作的完整脚本。将 client_id 替换为你的 Client ID,然后运行它。它会启动一个临时回环服务器,在你的浏览器中打开同意页面,并在你批准后打印你组织的部门。
import base64import hashlibimport jsonimport secretsimport webbrowserfrom http.server import BaseHTTPRequestHandler, HTTPServerfrom 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 tokenverifier = 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 IDapi_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 divisionsdivisions = 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=code、code_challenge_method=S256 以及所有必需参数都存在,包括 state 和 scopes。 |
| 令牌交换时出现 400 或 403 | 代码可能已过期(60 秒限制),redirect_uri 可能与步骤 3 不匹配,或者 PKCE verifier 和 challenge 可能不匹配。 |
| 调用 API 时出现 403 Forbidden | 请求必须通过全部三个安全层。检查 OAuth 作用域、已登录用户的内容访问权限,以及该用户在目标节点上的角色。 |
| API 响应中的组织错误 | 用户在同意屏幕上选择组织。重新授权并选择正确的组织。 |
有关连接、DNS 和 API URL 问题,请参阅快速入门。
下一步是什么?
Section titled “下一步是什么?”- 添加逻辑,使用
grant_type=refresh_token和你存储的刷新令牌在令牌过期前刷新它。 - 探索 API 参考以查看所有可用操作。