Files
leaudit-platform-frontend/login/OAuth2.0认证协议集成指南.md
T

576 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# OAuth2.0 认证协议集成开发指南
## 📋 目录
- [1. 术语定义](#1-术语定义)
- [2. 业务场景说明](#2-业务场景说明)
- [3. 集成流程概览](#3-集成流程概览)
- [4. 详细集成步骤](#4-详细集成步骤)
- [5. API接口详解](#5-api接口详解)
- [6. 错误处理](#6-错误处理)
- [7. 注意事项](#7-注意事项)
- [8. 示例代码](#8-示例代码)
## 1. 术语定义
### 🔍 核心概念
| 术语 | 全称 | 说明 |
|------|------|------|
| **SP** | Service Provider | 业务系统,如OA系统、订单系统 |
| **IDaaS** | Identity as a Service | 提供统一身份服务的认证系统平台,即IDP |
## 2. 业务场景说明
### 📱 应用场景
业务系统作为SP,需要集成IDaaS的单点登录和单点登出功能。
### 🎯 核心目标
- **单点登录**:用户通过IDaaS认证后,可以访问所有授权的应用
- **单点登出**:用户在任意应用登出后,所有关联应用都会登出
- **用户信息同步**:获取用户在IDaaS平台的身份信息
### 💡 实现方式
1. **门户集成**:用户通过IDaaS门户选择应用进行登录
2. **独立登录**:业务系统提供独立登录页面,调用IDaaS接口
3. **API直接调用**:通过AK/SK方式直接调用IDaaS登录接口
## 3. 集成流程概览
```mermaid
sequenceDiagram
participant User as 用户
participant SP as 业务系统(SP)
participant IDaaS as IDaaS平台
Note over User,IDaaS: 1. 配置OAuth2应用
SP->>IDaaS: 在IDaaS平台创建OAuth2应用
IDaaS-->>SP: 返回client_id和client_secret
Note over User,IDaaS: 2. 用户登录流程
User->>SP: 访问业务系统
SP->>User: 重定向到IDaaS登录页
User->>IDaaS: 在IDaaS完成登录
IDaaS->>SP: 返回authorization code
SP->>IDaaS: 使用code获取access_token
IDaaS-->>SP: 返回access_token
SP->>IDaaS: 使用access_token获取用户信息
IDaaS-->>SP: 返回用户详细信息
SP-->>User: 完成登录,访问业务系统
Note over User,IDaaS: 3. 用户登出流程
User->>SP: 请求登出
SP->>IDaaS: 调用IDaaS登出接口
IDaaS-->>SP: 登出成功
SP-->>User: 重定向到登录页
```
## 4. 详细集成步骤
### 4.1 配置OAuth2第三方应用
#### 📝 配置步骤
1. 使用管理员登录IDaaS平台
2. 创建新应用,选择标准协议 → OAuth2模式
3. 配置应用基本信息
#### ⚙️ 关键配置项
| 配置项 | 说明 | 示例 |
|--------|------|------|
| **Redirect URI** | 授权码模式下,接收IDaaS返回code的回调地址 | `http://oa.com/callback` |
| **Grant Type** | 授权类型,固定选择 | `authorization_code` |
| **Client ID** | 应用唯一标识 | `1d0f8349ad981fe9a306e39d86a3a124uHW6fd0sC7U` |
| **Client Secret** | 应用密钥 | `vdDtBPNiHRCz8S267fURHYnr2bMlgL7ahqrpgrDscG` |
### 4.2 对接IDaaS登录
#### 🚀 登录方式选择
##### 方式一:使用IDaaS统一登录页
**适用场景**:不需要自定义登录页面样式的应用
**流程说明**
1. 构建授权URL,引导用户跳转到IDaaS登录页
2. 用户完成登录后,IDaaS回调业务系统
3. 业务系统获取code,换取access_token
4. 使用access_token获取用户信息
## 5. API接口详解
### 5.1 获取授权码(Authorization Code
#### 📌 接口描述
引导用户到IDaaS登录页面,获取授权码
http://<u>10.79.112.85</u>/oauth/authorize?response_type=code&scope=read&client_id=<u>54d2a619fe5c81ae1250434c441fccccqMtKwh7H4fO</u>&redirect_uri=<u>http%3a%2f%2f10.79.97.17%2f</u>&state=<u>10ff0be64971c07f893afc332877f68arS8FH2iyZni</u>
#### 🔗 请求URL格式
```
http(s)://{IDaaS_server}/oauth/authorize?response_type=code&scope=read&client_id={client_id}&redirect_uri={redirect_uri}&state={state}
```
#### 📋 请求参数
| 参数名 | 类型 | 必填 | 示例值 | 说明 |
|--------|------|------|--------|------|
| `response_type` | string | ✅ | `code` | 响应类型,固定为code |
| `scope` | string | ✅ | `read` | 授权范围,固定为read |
| `client_id` | string | ✅ | `1d0f8349ad981fe9a306e39d86a3a124uHW6fd0sC7U` | OAuth2应用的Client ID |
| `redirect_uri` | string | ✅ | `http%3A%2F%2Foa.com%2Fcallback` | 回调地址(需URL编码) |
| `state` | string | ✅ | `10ff0be64971c07f893afc332877f68arS8FH2iyZni_idp` | 状态值,建议包含`_idp`后缀 |
#### 💡 完整示例
```
http://idaas.example.com/oauth/authorize?response_type=code&scope=read&client_id=1d0f8349ad981fe9a306e39d86a3a124uHW6fd0sC7U&redirect_uri=http%3A%2F%2Foa.com%2Fcallback&state=10ff0be64971c07f893afc332877f68arS8FH2iyZni_idp
```
### 5.2 获取访问令牌(Access Token
#### 📌 接口描述
使用授权码换取访问令牌
#### 🔗 请求信息
- **URL**: `http(s)://{IDaaS_server}/oauth/token`
- **方法**: `POST`
- **Content-Type**: `application/x-www-form-urlencoded`
#### 📋 请求参数
| 参数名 | 类型 | 必填 | 示例值 | 说明 |
|--------|------|------|--------|------|
| `grant_type` | string | ✅ | `authorization_code` | 授权类型,固定值 |
| `code` | string | ✅ | `WgWQe6` | 从回调中获取的授权码 |
| `client_id` | string | ✅ | `1d0f8349ad981fe9a306e39d86a3a124uHW6fd0sC7U` | OAuth2应用的Client ID |
| `client_secret` | string | ✅ | `vdDtBPNiHRCz8S267fURHYnr2bMlgL7ahqrpgrDscG` | OAuth2应用的Client Secret |
| `redirect_uri` | string | ✅ | `http%3A%2F%2Foa.com%2Fcallback` | 回调地址(需URL编码) |
#### 📤 cURL示例
```bash
curl -X POST 'http://idaas.example.com/oauth/token' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d 'grant_type=authorization_code&code=dIKvfA&client_id=1d0f8349ad981fe9a306e39d86a3a124uHW6fd0sC7U&client_secret=vdDtBPNiHRCz8S267fURHYnr2bMlgL7ahqrpgrDscG&redirect_uri=http%3A%2F%2Foa.com%2Fcallback'
```
#### ✅ 成功响应
```json
{
"access_token": "eyJhbGciO...",
"token_type": "bearer",
"refresh_token": "eyJhbGciOiJIUzI1...",
"expires_in": 7199,
"scope": "read",
"jti": "17147278-7f3e-45f2-be6f-8105c4334a30"
}
```
### 5.3 获取用户信息
#### 📌 接口描述
使用访问令牌获取用户详细信息
#### 🔗 请求信息
- **URL**: `https://{IDaaS_server}/api/bff/v1.2/oauth2/userinfo`
- **方法**: `GET`
- **认证**: Bearer Token
#### 📋 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| `access_token` | string | ✅ | 访问令牌(可作为URL参数或Header) |
#### 📤 请求示例
```bash
# 方式1: URL参数
GET https://idaas.example.com/api/bff/v1.2/oauth2/userinfo?access_token=eyJhbGc1NiIs...
# 方式2: Authorization Header
curl -H "Authorization: Bearer eyJhbGc1NiIs..." \
https://idaas.example.com/api/bff/v1.2/oauth2/userinfo
```
#### ✅ 成功响应
```json
{
"success": true,
"code": "200",
"message": null,
"requestId": "149DA248-8F49-4820-B87A-5EA36D932354",
"data": {
"sub": "823071756087671783",
"ou_id": "2079225187122667069",
"nickname": "测试用户",
"phone_number": "11136618971",
"ou_name": "测试组织IDAAS",
"email": "test@test.com",
"username": "test"
}
}
```
#### 📊 响应字段说明
| 字段名 | 类型 | 说明 |
|--------|------|------|
| `sub` | string | 用户唯一标识 |
| `ou_id` | string | 组织ID |
| `nickname` | string | 用户昵称 |
| `phone_number` | string | 手机号码 |
| `ou_name` | string | 组织名称 |
| `email` | string | 邮箱地址 |
| `username` | string | 用户名 |
### 5.4 单点登出(SLO
#### 📌 接口描述
实现全局统一登出功能
#### 🔗 请求信息
- **URL**: `http(s)://{IDaaS_server}/public/sp/slo/{appId}`
- **方法**: `GET``POST`(推荐POST
#### 📋 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| `appId` | string | ✅ | 应用ID(路径参数) |
| `redirect_url` | string | ❌ | 登出成功后的重定向URL(需URL编码) |
| `access_token` | string | ❌ | 用户的访问令牌 |
#### 📤 请求示例
```bash
# GET请求
http://idaas.example.com/public/sp/slo/idaasoauth2?access_token=xxxxxxx&redirect_url=https%3A%2F%2Fwww.example.com%2F
# POST请求(推荐)
curl -X POST 'http://idaas.example.com/public/sp/slo/idaasoauth2' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d 'access_token=xxxxxxx&redirect_url=https%3A%2F%2Fwww.example.com%2F'
```
## 6. 错误处理
### ❌ 常见错误响应
#### Token相关错误
```json
// 客户端认证失败
{
"error": "invalid_client",
"error_description": "Bad client credentials"
}
// 授权码无效
{
"error": "invalid_grant",
"error_description": "Invalid authorization code: dIKvfA"
}
// 授权码过期
{
"error": "invalid_grant",
"error_description": "authorization code expired: WgWQe6"
}
```
#### HTTP状态码说明
| 状态码 | 错误类型 | 说明 |
|--------|----------|------|
| `401` | Unauthorized | 未授权的访问 |
| `403` | Forbidden | 权限不足 |
| `404` | ResourceNotFound | 访问的资源不存在 |
| `415` | UnsupportedMediaType | 不支持的媒体类型 |
| `500` | InternalError | 服务器内部错误 |
## 7. 注意事项
### ⚠️ 重要提醒
#### 多端访问处理
当企业内网同时有PC端Web应用和移动端H5应用时,需要根据`remote-user`请求头字段进行判断:
- **`remote-user`为NULL**: 从企业内网登录 → 使用原始地址
- **`remote-user`不为NULL**: 从企业外网登录 → 使用代理地址
#### URL地址转换规则
```
原始地址: http://xx.YY.zzz.AA
代理地址: https://xx-YY-zzz-AA-kkkkkkkkkkkk.ztna-dingtalk.com
```
#### 移动端适配
可以通过UserAgent等信息进行设备类型判断,实现不同终端的差异化跳转。
### 🔐 安全建议
1. **HTTPS传输**: 生产环境务必使用HTTPS协议
2. **State参数**: 使用随机且不可预测的state值防止CSRF攻击
3. **Token保护**: 妥善保存client_secret和access_token
4. **回调验证**: 验证回调请求的来源和参数完整性
5. **Token过期**: 及时处理token过期和刷新逻辑
## 8. 示例代码
### 🐍 Python集成示例
```python
import requests
import urllib.parse
from typing import Dict, Optional
class IDaaSClient:
def __init__(self, server_url: str, client_id: str, client_secret: str, redirect_uri: str):
self.server_url = server_url.rstrip('/')
self.client_id = client_id
self.client_secret = client_secret
self.redirect_uri = redirect_uri
def get_authorize_url(self, state: str) -> str:
"""生成授权URL"""
params = {
'response_type': 'code',
'scope': 'read',
'client_id': self.client_id,
'redirect_uri': self.redirect_uri,
'state': state
}
query_string = urllib.parse.urlencode(params)
return f"{self.server_url}/oauth/authorize?{query_string}"
def get_access_token(self, code: str) -> Optional[Dict]:
"""使用授权码获取访问令牌"""
url = f"{self.server_url}/oauth/token"
data = {
'grant_type': 'authorization_code',
'code': code,
'client_id': self.client_id,
'client_secret': self.client_secret,
'redirect_uri': self.redirect_uri
}
try:
response = requests.post(url, data=data)
response.raise_for_status()
return response.json()
except requests.RequestException as e:
print(f"获取token失败: {e}")
return None
def get_user_info(self, access_token: str) -> Optional[Dict]:
"""获取用户信息"""
url = f"{self.server_url}/api/bff/v1.2/oauth2/userinfo"
headers = {'Authorization': f'Bearer {access_token}'}
try:
response = requests.get(url, headers=headers)
response.raise_for_status()
return response.json()
except requests.RequestException as e:
print(f"获取用户信息失败: {e}")
return None
def logout(self, app_id: str, access_token: str, redirect_url: str) -> bool:
"""单点登出"""
url = f"{self.server_url}/public/sp/slo/{app_id}"
data = {
'access_token': access_token,
'redirect_url': redirect_url
}
try:
response = requests.post(url, data=data)
return response.status_code == 200
except requests.RequestException as e:
print(f"登出失败: {e}")
return False
# 使用示例
if __name__ == "__main__":
# 初始化客户端
client = IDaaSClient(
server_url="http://idaas.example.com",
client_id="your_client_id",
client_secret="your_client_secret",
redirect_uri="http://your-app.com/callback"
)
# 1. 生成登录URL(重定向用户到此URL)
state = "random_state_value_with_idp"
login_url = client.get_authorize_url(state)
print(f"登录URL: {login_url}")
# 2. 处理回调(从query参数获取code)
code = "received_code_from_callback"
token_response = client.get_access_token(code)
if token_response:
access_token = token_response['access_token']
print(f"Access Token: {access_token}")
# 3. 获取用户信息
user_info = client.get_user_info(access_token)
if user_info and user_info['success']:
user_data = user_info['data']
print(f"用户信息: {user_data}")
# 4. 登出
logout_success = client.logout("your_app_id", access_token, "http://your-app.com/login")
print(f"登出结果: {'成功' if logout_success else '失败'}")
```
### 🌐 JavaScript集成示例
```javascript
class IDaaSClient {
constructor(serverUrl, clientId, clientSecret, redirectUri) {
this.serverUrl = serverUrl.replace(/\/$/, '');
this.clientId = clientId;
this.clientSecret = clientSecret;
this.redirectUri = redirectUri;
}
// 生成授权URL
getAuthorizeUrl(state) {
const params = new URLSearchParams({
response_type: 'code',
scope: 'read',
client_id: this.clientId,
redirect_uri: this.redirectUri,
state: state
});
return `${this.serverUrl}/oauth/authorize?${params.toString()}`;
}
// 获取访问令牌
async getAccessToken(code) {
const url = `${this.serverUrl}/oauth/token`;
const data = new URLSearchParams({
grant_type: 'authorization_code',
code: code,
client_id: this.clientId,
client_secret: this.clientSecret,
redirect_uri: this.redirectUri
});
try {
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: data
});
return await response.json();
} catch (error) {
console.error('获取token失败:', error);
return null;
}
}
// 获取用户信息
async getUserInfo(accessToken) {
const url = `${this.serverUrl}/api/bff/v1.2/oauth2/userinfo`;
try {
const response = await fetch(url, {
headers: {
'Authorization': `Bearer ${accessToken}`
}
});
return await response.json();
} catch (error) {
console.error('获取用户信息失败:', error);
return null;
}
}
// 单点登出
async logout(appId, accessToken, redirectUrl) {
const url = `${this.serverUrl}/public/sp/slo/${appId}`;
const data = new URLSearchParams({
access_token: accessToken,
redirect_url: redirectUrl
});
try {
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: data
});
return response.ok;
} catch (error) {
console.error('登出失败:', error);
return false;
}
}
}
// 使用示例
const client = new IDaaSClient(
'http://idaas.example.com',
'your_client_id',
'your_client_secret',
'http://your-app.com/callback'
);
// 处理登录流程
async function handleLogin() {
// 1. 重定向到IDaaS登录页
const state = 'random_state_value_with_idp';
const loginUrl = client.getAuthorizeUrl(state);
window.location.href = loginUrl;
}
// 处理回调
async function handleCallback() {
const urlParams = new URLSearchParams(window.location.search);
const code = urlParams.get('code');
const state = urlParams.get('state');
if (code) {
// 2. 获取访问令牌
const tokenResponse = await client.getAccessToken(code);
if (tokenResponse && tokenResponse.access_token) {
const accessToken = tokenResponse.access_token;
// 3. 获取用户信息
const userInfo = await client.getUserInfo(accessToken);
if (userInfo && userInfo.success) {
console.log('用户信息:', userInfo.data);
// 保存用户信息到localStorage或状态管理
localStorage.setItem('access_token', accessToken);
localStorage.setItem('user_info', JSON.stringify(userInfo.data));
}
}
}
}
// 处理登出
async function handleLogout() {
const accessToken = localStorage.getItem('access_token');
if (accessToken) {
const success = await client.logout('your_app_id', accessToken, window.location.origin + '/login');
if (success) {
localStorage.removeItem('access_token');
localStorage.removeItem('user_info');
window.location.href = '/login';
}
}
}
```
---
## 📞 技术支持
如需更多技术支持,请参考:
- IDaaS平台管理后台
- 相关API文档
- 集成Demo项目
**注意**: 本文档基于OAuth2.0标准协议,具体实现可能因IDaaS平台版本而有所差异,请以实际平台配置为准。