添加登录内容,尚未完善,先创建分支

This commit is contained in:
2025-07-14 12:31:43 +08:00
parent e3109423d4
commit fff474f46b
25 changed files with 2693 additions and 1102 deletions
+576
View File
@@ -0,0 +1,576 @@
# 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平台版本而有所差异,请以实际平台配置为准。