使用请求头 Authorization: Bearer <access_token></access_token> 来执行需要身份验证的请求。你可以通过组合以下两个请求来获取一个持有者令牌(bearer token)。
这里有一篇非常棒的 OAuth 入门介绍:OAuth 2.0 和 OpenID Connect(通俗易懂版)
GET /api/method/frappe.integrations.oauth2.authorize
获取用户的授权码,以便代表用户访问 ERPNext。
参数(在查询字符串中):
- client_id(字符串)
你的 OAuth2 应用程序的 ID
- state(字符串)
你的客户端应用程序用来在请求和回调之间维护状态的任意值。授权服务器在将用户代理重定向回客户端时会包含这个值。该参数应用于防止跨站请求伪造。
- response_type(字符串)
“code”
- scope(字符串)
应授予你的应用程序的访问范围。
- redirect_uri(字符串)
应用程序授权后,用户将被重定向到的回调 URI。然后可以从参数中提取授权码。
- code_challenge_method(字符串)
(可选)可以是 s256 或 plain 中的一个。
- code_challenge(字符串)
(可选)当 code_challenge_method=s256 时可以是 base64encode(sha256(random_string)),或者当 code_challenge_method=plain 时可以是 random_string。参考 https://tools.ietf.org/html/rfc7636#appendix-A
示例:
curl -X POST https://{your frappe instance}/api/method/frappe.integrations.oauth2.authorize \
--data-urlencode 'client_id=511cb2ac2d' \
--data-urlencode 'state=444' \
# base64encode(sha256('420')) => 21XaP8MJjpxCMRxgEzBP82sZ73PRLqkyBUta1R309J0
# --data-urlencode 'code_verifier=21XaP8MJjpxCMRxgEzBP82sZ73PRLqkyBUta1R309J0' \
--data-urlencode 'response_type=code'
--data-urlencode 'scope=openid%20all' \
--data-urlencode 'redirect_uri=https://app.getpostman.com/oauth2/callback'
返回:
- HTTP 状态码:200
- text/html
这将打开授权页面,然后会将你重定向到 redirect_uri。
如果用户点击”允许”,重定向 URI 将被调用,并在查询参数中附带授权码:
https://{redirect uri}?code=plkj2mqDLwaLJAgDBAkyR1W8Co08Ud&state=444
如果用户点击”拒绝”,你将收到一个错误:
https://{redirect uri}?error=access_denied
使用 ID Token 的授权码授予的令牌交换
POST /api/method/frappe.integrations.oauth2.get_token
Header Content-Type: application/x-www-form-urlencoded
注意:此端点也可用于获取刷新的访问令牌。只需在请求体中发送
refresh_token即可。
用(上面获取的)授权码换取访问令牌。
参数(在请求体中):
- grant_type(字符串)
“authorization_code”
- code(字符串)
在重定向 URI 中收到的授权码。
- client_id(字符串)
你的 OAuth2 应用程序的 ID
- redirect_uri(字符串)
客户端应用注册的重定向 URI
示例:
curl -X POST https://{your frappe instance}/api/method/frappe.integrations.oauth2.get_token \
-H 'Content-Type: application/x-www-form-urlencoded' \
-H 'Accept: application/json' \
-d 'grant_type=authorization_code&code=wa1YuQMff2ZXEAu2ZBHLpJRQXcGZdr
&redirect_uri=https%3A%2F%2Fapp.getpostman.com%2Foauth2%2Fcallback&client_id=af615c2d3a'
出于测试目的,你也可以像这样在 URL 中传递参数(并在浏览器中打开):
https://{your frappe instance}/api/method/frappe.integrations.oauth2.get_token?grant_type=authorization_code&code=A1KBRoYAN1uxrLAcdGLmvPKsRQLvzj&client_id=511cb2ac2d&redirect_uri=https%3A%2F%2Fapp.getpostman.com%2Foauth2%2Fcallback
返回:
{
"access_token": "pNO2DpTMHTcFHYUXwzs74k6idQBmnI",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "cp74cxbbDgaxFuUZ8Usc7egYlhKbH1",
"scope": "openid all",
"id_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.XbPfbIHMI6arZ3Y922BhjWgQzWXcXNrz0ogtVhfEd2o"
}
使用 ID Token 的授权码授予的令牌交换(PKCE)
POST /api/method/frappe.integrations.oauth2.get_token
Header Content-Type: application/x-www-form-urlencoded
用(上面获取的)授权码换取访问令牌。
参数(在请求体中):
- grant_type(字符串)
“authorization_code”
- code(字符串)
在重定向 URI 中收到的授权码。
- client_id(字符串)
你的 OAuth2 应用程序的 ID
- redirect_uri(字符串)
客户端应用注册的重定向 URI
- code_verifier(字符串)
在 Authorization Request 期间与 code_challenge_method 和 code_challenge 一起使用的 random_string。
Content-Type: application/x-www-form-urlencoded
示例:
curl -X POST https://{your frappe instance}/api/method/frappe.integrations.oauth2.get_token \
-H 'Content-Type: application/x-www-form-urlencoded' \
-H 'Accept: application/json' \
-d 'grant_type=authorization_code&code=wa1YuQMff2ZXEAu2ZBHLpJRQXcGZdr
&redirect_uri=https%3A%2F%2Fapp.getpostman.com%2Foauth2%2Fcallback&client_id=af615c2d3a&code_verifier=420'
出于测试目的,你也可以像这样在 URL 中传递参数(并在浏览器中打开):
https://{your frappe instance}/api/method/frappe.integrations.oauth2.get_token?grant_type=authorization_code&code=A1KBRoYAN1uxrLAcdGLmvPKsRQLvzj&client_id=511cb2ac2d&redirect_uri=https%3A%2F%2Fapp.getpostman.com%2Foauth2%2Fcallback&code_verifier=420
返回:
{
"access_token": "pNO2DpTMHTcFHYUXwzs74k6idQBmnI",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "cp74cxbbDgaxFuUZ8Usc7egYlhKbH1",
"scope": "openid all",
"id_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.XbPfbIHMI6arZ3Y922BhjWgQzWXcXNrz0ogtVhfEd2o"
}
撤销令牌端点
POST /api/method/frappe.integrations.oauth2.revoke_token
Header: Content-Type: application/x-www-form-urlencoded
撤销令牌端点。
参数:
- token
要撤销的访问令牌。
返回:
始终返回空响应,HTTP 状态码为 200。
{}
Open ID Connect id_token
ID Token 是一个 JWT。
aud声明包含已注册的client_id。iss声明包含 frappe 服务器 URL。sub声明包含 Frappe 用户的 userid。roles声明包含用户角色。exp声明包含过期时间。iat声明包含签发时间。
示例:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkouIERvZSIsImVtYWlsIjoiakBkb2UuY29tIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE1MTYyNDIwMjIsImF1ZCI6ImNsaWVudF9pZCJ9.ZEdnrHjLbArahVTN19b4zoRFoBO5a2BakRkR82O1VU8
使用 PyJWT 验证并提取它。
import jwt
id_token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkouIERvZSIsImVtYWlsIjoiakBkb2UuY29tIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE1MTYyNDIwMjIsImF1ZCI6ImNsaWVudF9pZCIsInJvbGVzIjpbIlN5c3RlbSBNYW5hZ2VyIiwiU2FsZXMgTWFuYWdlciJdLCJub25jZSI6Ijc4OTEyMyIsImlzcyI6Imh0dHBzOi8vZXJwLmV4YW1wbGUuY29tIn0.F8Wbh5dtD1loZPltJLj_sqF9DZNeBvEbo-ITtf3UPqk"
client_id = 'client_id'
client_secret = 'client_secret'
payload = jwt.decode(
id_token,
audience=client_id,
key=client_secret,
algorithm="HS256",
options={"verify_exp": False}, # Enabled by default to verify expiration time
)
print(payload)
# Output
{'sub': '1234567890', 'name': 'J. Doe', 'email': '[email protected]', 'iat': 1516239022, 'exp': 1516242022, 'aud': 'client_id', 'roles': ['System Manager', 'Sales Manager'], 'nonce': '789123', 'iss': 'https://erp.example.com'}
OpenID 用户信息端点
请求
GET /api/method/frappe.integrations.oauth2.openid_profile
Header: Authorization: Bearer valid_access_token
响应
{
"sub": "1234567890",
"name": "J. Doe",
"given_name": "J",
"family_name": "Doe",
"iss": "https://erp.example.com",
"picture": "https://erp.example.com/files/jdoe.jpg",
"email": "[email protected]",
"iat": 1516239022,
"exp": 1516242022,
"aud": "client_id",
"roles": ["System Manager", "Sales Manager"]
}
内省令牌端点
POST /api/method/frappe.integrations.oauth2.introspect_token
Header: Content-Type: application/x-www-form-urlencoded
撤销令牌端点。
参数:
- token_type_hint
access_token 或 refresh_token,如果未提供则默认为 access_token
- token
要内省的访问令牌或刷新令牌。取决于 token_type_hint
返回:
{
"client_id": "511cb2ac2d",
"trusted_client": 1,
"active": true,
"exp": 1619523326,
"scope": "openid all",
"sub": "1234567890",
"name": "J. Doe",
"given_name": "J",
"family_name": "Doe",
"iss": "https://erp.example.com",
"picture": "https://erp.example.com/files/jdoe.jpg",
"email": "[email protected]",
"iat": 1516239022,
"exp": 1516242022,
"aud": "511cb2ac2d",
"roles": ["System Manager", "Sales Manager"]
}
或者
{
"active": false,
"_server_messages": "..."
}
进一步阅读
请查看 Guides / Integration / How To Set Up Oauth 了解如何创建新的 OAuth 2 客户端。
- Content-Type 请求头,
- Authorization 请求头,
- OAuth 2 规范,
- 持有者令牌(Bearer token)。
作者:
- Raffael Meyer ([email protected])
- Revant Nandgaonkar ([email protected])