diff --git a/docs/oauth2-demo.html b/docs/oauth2-demo.html
deleted file mode 100644
index 594c2ba15..000000000
--- a/docs/oauth2-demo.html
+++ /dev/null
@@ -1,909 +0,0 @@
-
-
-
-
-
- OAuth2 自动登录 Demo
-
-
-
- OAuth2 服务器自动登录 Demo
- 这个演示展示了如何使用OAuth2实现自动登录功能。
-
-
-
-
-
-
-
登录状态
-
未登录
-
-
-
-
选择登录方式:
-
-
-
-
-
-
-
-
已登录
-
-
-
-
-
-
-
-
-
-
-
-
-
令牌信息
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/docs/oauth2-demo.md b/docs/oauth2-demo.md
deleted file mode 100644
index 41751af00..000000000
--- a/docs/oauth2-demo.md
+++ /dev/null
@@ -1,870 +0,0 @@
-# OAuth2服务端使用Demo - 自动登录流程
-
-本文档演示如何使用new-api的OAuth2服务器实现自动登录功能,包括两种授权模式的完整流程。
-
-## 📋 准备工作
-
-### 1. 启用OAuth2服务器
-在管理后台 -> 设置 -> OAuth2 & SSO 中:
-```
-启用OAuth2服务器: 开启
-签发者标识(Issuer): https://your-domain.com
-访问令牌有效期: 60分钟
-刷新令牌有效期: 24小时
-JWT签名算法: RS256
-允许的授权类型: client_credentials, authorization_code
-```
-
-### 2. 创建OAuth2客户端
-在OAuth2客户端管理中创建应用:
-```
-客户端名称: My App
-客户端类型: 机密客户端 (Confidential)
-授权类型: Client Credentials, Authorization Code
-权限范围: api:read, api:write
-重定向URI: https://your-app.com/callback
-```
-
-创建成功后会获得:
-- Client ID: `your_client_id`
-- Client Secret: `your_client_secret` (仅显示一次)
-
-## 🔐 方式一:客户端凭证流程 (Client Credentials)
-
-适用于**服务器到服务器**的API调用,无需用户交互。
-
-### 获取访问令牌
-
-```bash
-curl -X POST https://your-domain.com/api/oauth/token \
- -H "Content-Type: application/x-www-form-urlencoded" \
- -d "grant_type=client_credentials" \
- -d "client_id=your_client_id" \
- -d "client_secret=your_client_secret" \
- -d "scope=api:read api:write"
-```
-
-**响应示例:**
-```json
-{
- "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
- "token_type": "Bearer",
- "expires_in": 3600,
- "scope": "api:read api:write"
-}
-```
-
-### 使用访问令牌调用API
-
-```bash
-curl -X GET https://your-domain.com/api/user/self \
- -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
-```
-
-## 👤 方式二:授权码流程 (Authorization Code + PKCE)
-
-适用于**用户登录**场景,支持自动登录功能。
-
-### Step 1: 生成PKCE参数
-
-```javascript
-// 生成随机code_verifier
-function generateCodeVerifier() {
- const array = new Uint8Array(32);
- crypto.getRandomValues(array);
- return btoa(String.fromCharCode.apply(null, array))
- .replace(/\+/g, '-')
- .replace(/\//g, '_')
- .replace(/=/g, '');
-}
-
-// 生成code_challenge
-async function generateCodeChallenge(verifier) {
- const encoder = new TextEncoder();
- const data = encoder.encode(verifier);
- const digest = await crypto.subtle.digest('SHA-256', data);
- return btoa(String.fromCharCode.apply(null, new Uint8Array(digest)))
- .replace(/\+/g, '-')
- .replace(/\//g, '_')
- .replace(/=/g, '');
-}
-```
-
-### Step 2: 重定向用户到授权页面
-
-```javascript
-const codeVerifier = generateCodeVerifier();
-const codeChallenge = await generateCodeChallenge(codeVerifier);
-
-// 保存code_verifier到本地存储
-localStorage.setItem('oauth_code_verifier', codeVerifier);
-
-// 构建授权URL
-const authUrl = new URL('https://your-domain.com/api/oauth/authorize');
-authUrl.searchParams.set('response_type', 'code');
-authUrl.searchParams.set('client_id', 'your_client_id');
-authUrl.searchParams.set('redirect_uri', 'https://your-app.com/callback');
-authUrl.searchParams.set('scope', 'api:read api:write');
-authUrl.searchParams.set('state', 'random_state_value');
-authUrl.searchParams.set('code_challenge', codeChallenge);
-authUrl.searchParams.set('code_challenge_method', 'S256');
-
-// 重定向到授权页面
-window.location.href = authUrl.toString();
-```
-
-### Step 3: 处理授权回调
-
-用户授权后会跳转到`https://your-app.com/callback?code=xxx&state=xxx`
-
-```javascript
-// 在callback页面处理授权码
-const urlParams = new URLSearchParams(window.location.search);
-const code = urlParams.get('code');
-const state = urlParams.get('state');
-const codeVerifier = localStorage.getItem('oauth_code_verifier');
-
-if (code && codeVerifier) {
- // 交换访问令牌
- const tokenResponse = await fetch('https://your-domain.com/api/oauth/token', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/x-www-form-urlencoded',
- },
- body: new URLSearchParams({
- grant_type: 'authorization_code',
- client_id: 'your_client_id',
- client_secret: 'your_client_secret',
- code: code,
- redirect_uri: 'https://your-app.com/callback',
- code_verifier: codeVerifier
- })
- });
-
- const tokens = await tokenResponse.json();
-
- // 解析JWT令牌获取用户信息
- const userInfo = parseJWTToken(tokens.access_token);
- console.log('用户信息:', userInfo);
-
- // 保存令牌和用户信息
- localStorage.setItem('access_token', tokens.access_token);
- localStorage.setItem('refresh_token', tokens.refresh_token);
- localStorage.setItem('user_info', JSON.stringify(userInfo));
-
- // 清理临时数据
- localStorage.removeItem('oauth_code_verifier');
-
- // 跳转到应用首页
- window.location.href = '/dashboard';
-}
-```
-
-### Step 4: JWT令牌解析和用户信息获取
-
-授权码流程返回的`access_token`是一个JWT令牌,包含用户信息:
-
-```javascript
-// JWT令牌解析函数
-function parseJWTToken(token) {
- try {
- // JWT格式: header.payload.signature
- const parts = token.split('.');
- if (parts.length !== 3) {
- throw new Error('Invalid JWT token format');
- }
-
- // 解码payload部分
- const payload = JSON.parse(atob(parts[1]));
-
- // 提取用户信息
- return {
- userId: payload.sub, // 用户ID
- username: payload.preferred_username || payload.sub,
- email: payload.email, // 用户邮箱
- name: payload.name, // 用户姓名
- roles: payload.scope?.split(' ') || [], // 权限范围
- groups: payload.groups || [], // 用户组
- exp: payload.exp, // 过期时间
- iat: payload.iat, // 签发时间
- iss: payload.iss, // 签发者
- aud: payload.aud // 受众
- };
- } catch (error) {
- console.error('Failed to parse JWT token:', error);
- return null;
- }
-}
-
-// JWT令牌验证函数
-function validateJWTToken(token) {
- const userInfo = parseJWTToken(token);
- if (!userInfo) return false;
-
- // 检查令牌是否过期
- const now = Math.floor(Date.now() / 1000);
- if (userInfo.exp && now >= userInfo.exp) {
- console.log('JWT token has expired');
- return false;
- }
-
- return true;
-}
-
-// 获取用户信息示例
-async function getUserInfoFromToken() {
- const token = localStorage.getItem('access_token');
- if (!token) return null;
-
- if (!validateJWTToken(token)) {
- // 令牌无效或过期,尝试刷新
- const newToken = await refreshToken();
- if (newToken) {
- return parseJWTToken(newToken);
- }
- return null;
- }
-
- return parseJWTToken(token);
-}
-```
-
-**JWT令牌示例内容:**
-```json
-{
- "sub": "user123", // 用户唯一标识
- "preferred_username": "john_doe", // 用户名
- "email": "john@example.com", // 邮箱
- "name": "John Doe", // 真实姓名
- "scope": "api:read api:write", // 权限范围
- "groups": ["users", "developers"], // 用户组
- "iss": "https://your-domain.com", // 签发者
- "aud": "your_client_id", // 受众
- "exp": 1609459200, // 过期时间戳
- "iat": 1609455600, // 签发时间戳
- "jti": "token-unique-id" // 令牌唯一ID
-}
-```
-
-## 👤 自动创建用户登录流程
-
-### 用户信息收集和自动创建
-
-当启用了`AutoCreateUser`选项时,用户首次通过OAuth2授权后会自动创建账户:
-
-```javascript
-// 用户信息收集表单
-function showUserInfoForm(jwtUserInfo) {
- const formHTML = `
-
- `;
-
- document.body.innerHTML = formHTML;
-
- // 绑定表单提交事件
- document.getElementById('userRegistrationForm').addEventListener('submit', handleUserRegistration);
-}
-
-// 处理用户注册
-async function handleUserRegistration(event) {
- event.preventDefault();
-
- const formData = {
- username: document.getElementById('username').value.trim(),
- displayName: document.getElementById('displayName').value.trim(),
- email: document.getElementById('email').value.trim(),
- group: document.getElementById('group').value,
- oauth2Provider: 'oauth2',
- oauth2UserId: parseJWTToken(localStorage.getItem('access_token')).userId
- };
-
- try {
- // 调用自动创建用户API
- const response = await fetch('https://your-domain.com/api/oauth/auto_create_user', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- 'Authorization': `Bearer ${localStorage.getItem('access_token')}`
- },
- body: JSON.stringify(formData)
- });
-
- const result = await response.json();
-
- if (result.success) {
- // 用户创建成功,跳转到主界面
- localStorage.setItem('user_created', 'true');
- window.location.href = '/dashboard';
- } else {
- alert('创建用户失败: ' + result.message);
- }
- } catch (error) {
- console.error('用户创建失败:', error);
- alert('创建用户时发生错误,请重试');
- }
-}
-
-// 取消注册
-function cancelRegistration() {
- localStorage.removeItem('access_token');
- localStorage.removeItem('refresh_token');
- window.location.href = '/';
-}
-```
-
-### 完整的自动登录流程
-
-```javascript
-// 改进的自动登录初始化
-async function initAutoLogin() {
- try {
- // 1. 检查是否有有效的访问令牌
- const accessToken = localStorage.getItem('access_token');
- if (!accessToken || !validateJWTToken(accessToken)) {
- // 没有有效令牌,开始OAuth2授权流程
- startOAuth2Authorization();
- return;
- }
-
- // 2. 解析JWT令牌获取用户信息
- const jwtUserInfo = parseJWTToken(accessToken);
- console.log('JWT用户信息:', jwtUserInfo);
-
- // 3. 检查用户是否已存在于系统中
- const userExists = await checkUserExists(jwtUserInfo.userId);
-
- if (!userExists && !localStorage.getItem('user_created')) {
- // 4. 用户不存在且未创建,显示用户信息收集表单
- showUserInfoForm(jwtUserInfo);
- return;
- }
-
- // 5. 用户已存在或已创建,直接登录
- const apiUserInfo = await oauth2Client.callAPI('/api/user/self');
- console.log('API用户信息:', apiUserInfo);
-
- // 6. 显示主界面
- showDashboard(jwtUserInfo, apiUserInfo);
-
- } catch (error) {
- console.error('自动登录失败:', error);
- // 清理令牌并重新开始授权流程
- localStorage.removeItem('access_token');
- localStorage.removeItem('refresh_token');
- localStorage.removeItem('user_created');
- startOAuth2Authorization();
- }
-}
-
-// 检查用户是否存在
-async function checkUserExists(userId) {
- try {
- const response = await fetch(`https://your-domain.com/api/oauth/user_exists/${userId}`, {
- headers: {
- 'Authorization': `Bearer ${localStorage.getItem('access_token')}`
- }
- });
-
- const result = await response.json();
- return result.exists;
- } catch (error) {
- console.error('检查用户存在性失败:', error);
- return false;
- }
-}
-
-// 开始OAuth2授权流程
-function startOAuth2Authorization() {
- const oauth2Client = new OAuth2Client({
- clientId: 'your_client_id',
- clientSecret: 'your_client_secret',
- serverUrl: 'https://your-domain.com',
- redirectUri: window.location.origin + '/callback',
- scopes: 'api:read api:write'
- });
-
- oauth2Client.startAuthorizationCodeFlow();
-}
-```
-
-### 服务器端自动创建用户API
-
-需要在服务器端实现相应的API端点:
-
-```go
-// 用户存在性检查
-func CheckUserExists(c *gin.Context) {
- oauthUserId := c.Param("oauth_user_id")
-
- var user model.User
- err := model.DB.Where("oauth2_user_id = ?", oauthUserId).First(&user).Error
-
- if errors.Is(err, gorm.ErrRecordNotFound) {
- c.JSON(http.StatusOK, gin.H{
- "exists": false,
- })
- } else if err != nil {
- c.JSON(http.StatusInternalServerError, gin.H{
- "error": "Database error",
- })
- } else {
- c.JSON(http.StatusOK, gin.H{
- "exists": true,
- "user_id": user.Id,
- })
- }
-}
-
-// 自动创建用户
-func AutoCreateUser(c *gin.Context) {
- settings := system_setting.GetOAuth2Settings()
- if !settings.AutoCreateUser {
- c.JSON(http.StatusForbidden, gin.H{
- "success": false,
- "message": "自动创建用户功能未启用",
- })
- return
- }
-
- var req struct {
- Username string `json:"username" binding:"required"`
- DisplayName string `json:"displayName"`
- Email string `json:"email"`
- Group string `json:"group"`
- OAuth2UserId string `json:"oauth2UserId" binding:"required"`
- }
-
- if err := c.ShouldBindJSON(&req); err != nil {
- c.JSON(http.StatusBadRequest, gin.H{
- "success": false,
- "message": "无效的请求参数",
- })
- return
- }
-
- // 检查用户是否已存在
- var existingUser model.User
- err := model.DB.Where("username = ? OR oauth2_user_id = ?", req.Username, req.OAuth2UserId).First(&existingUser).Error
- if err == nil {
- c.JSON(http.StatusConflict, gin.H{
- "success": false,
- "message": "用户已存在",
- })
- return
- }
-
- // 创建新用户
- user := model.User{
- Username: req.Username,
- DisplayName: req.DisplayName,
- Email: req.Email,
- Group: settings.DefaultUserGroup,
- Role: settings.DefaultUserRole,
- Status: 1,
- Password: common.GenerateRandomString(32), // 随机密码,用户通过OAuth2登录
- OAuth2UserId: req.OAuth2UserId,
- }
-
- if req.DisplayName == "" {
- user.DisplayName = req.Username
- }
-
- if user.Group == "" {
- user.Group = "oauth2"
- }
-
- err = user.Insert(0)
- if err != nil {
- c.JSON(http.StatusInternalServerError, gin.H{
- "success": false,
- "message": "创建用户失败: " + err.Error(),
- })
- return
- }
-
- c.JSON(http.StatusOK, gin.H{
- "success": true,
- "message": "用户创建成功",
- "user_id": user.Id,
- })
-}
-```
-
-## 🔄 自动登录实现
-
-### 令牌刷新机制
-
-```javascript
-async function refreshToken() {
- const refreshToken = localStorage.getItem('refresh_token');
-
- if (!refreshToken) {
- // 重新授权
- redirectToAuth();
- return;
- }
-
- try {
- const response = await fetch('https://your-domain.com/api/oauth/token', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/x-www-form-urlencoded',
- },
- body: new URLSearchParams({
- grant_type: 'refresh_token',
- client_id: 'your_client_id',
- client_secret: 'your_client_secret',
- refresh_token: refreshToken
- })
- });
-
- const tokens = await response.json();
-
- if (tokens.access_token) {
- localStorage.setItem('access_token', tokens.access_token);
- if (tokens.refresh_token) {
- localStorage.setItem('refresh_token', tokens.refresh_token);
- }
- return tokens.access_token;
- }
- } catch (error) {
- // 刷新失败,重新授权
- redirectToAuth();
- }
-}
-```
-
-### 自动认证拦截器
-
-```javascript
-class OAuth2Client {
- constructor(clientId, clientSecret, baseURL) {
- this.clientId = clientId;
- this.clientSecret = clientSecret;
- this.baseURL = baseURL;
- }
-
- // 自动处理认证的请求方法
- async request(url, options = {}) {
- let accessToken = localStorage.getItem('access_token');
-
- // 检查令牌是否即将过期
- if (this.isTokenExpiringSoon(accessToken)) {
- accessToken = await this.refreshToken();
- }
-
- // 添加认证头
- const headers = {
- 'Authorization': `Bearer ${accessToken}`,
- 'Content-Type': 'application/json',
- ...options.headers
- };
-
- try {
- const response = await fetch(`${this.baseURL}${url}`, {
- ...options,
- headers
- });
-
- // 如果401,尝试刷新令牌
- if (response.status === 401) {
- accessToken = await this.refreshToken();
- headers['Authorization'] = `Bearer ${accessToken}`;
-
- // 重试请求
- return fetch(`${this.baseURL}${url}`, {
- ...options,
- headers
- });
- }
-
- return response;
- } catch (error) {
- console.error('Request failed:', error);
- throw error;
- }
- }
-
- // 检查令牌是否即将过期
- isTokenExpiringSoon(token) {
- if (!token) return true;
-
- try {
- const parts = token.split('.');
- if (parts.length !== 3) return true;
-
- const payload = JSON.parse(atob(parts[1]));
- const exp = payload.exp * 1000; // 转换为毫秒
- const now = Date.now();
- return exp - now < 5 * 60 * 1000; // 5分钟内过期
- } catch (error) {
- console.error('Token validation failed:', error);
- return true;
- }
- }
-
- // 获取当前用户信息
- getCurrentUser() {
- const token = localStorage.getItem('access_token');
- if (!token || !this.validateJWTToken(token)) {
- return null;
- }
-
- return this.parseJWTToken(token);
- }
-
- // 解析JWT令牌
- parseJWTToken(token) {
- try {
- const parts = token.split('.');
- if (parts.length !== 3) {
- throw new Error('Invalid JWT token format');
- }
-
- const payload = JSON.parse(atob(parts[1]));
-
- return {
- userId: payload.sub,
- username: payload.preferred_username || payload.sub,
- email: payload.email,
- name: payload.name,
- roles: payload.scope?.split(' ') || [],
- groups: payload.groups || [],
- exp: payload.exp,
- iat: payload.iat,
- iss: payload.iss,
- aud: payload.aud
- };
- } catch (error) {
- console.error('Failed to parse JWT token:', error);
- return null;
- }
- }
-
- // 验证JWT令牌
- validateJWTToken(token) {
- const userInfo = this.parseJWTToken(token);
- if (!userInfo) return false;
-
- const now = Math.floor(Date.now() / 1000);
- if (userInfo.exp && now >= userInfo.exp) {
- return false;
- }
-
- return true;
- }
-
- // 获取用户信息
- async getUserInfo() {
- const response = await this.request('/api/user/self');
- return response.json();
- }
-
- // 调用API示例
- async callAPI(endpoint, data = null) {
- const options = data ? {
- method: 'POST',
- body: JSON.stringify(data)
- } : { method: 'GET' };
-
- const response = await this.request(endpoint, options);
- return response.json();
- }
-}
-```
-
-### 使用示例
-
-```javascript
-// 初始化OAuth2客户端
-const oauth2Client = new OAuth2Client(
- 'your_client_id',
- 'your_client_secret',
- 'https://your-domain.com'
-);
-
-// 应用启动时自动检查登录状态
-async function initApp() {
- try {
- // 尝试获取用户信息(会自动处理令牌刷新)
- const userInfo = await oauth2Client.getUserInfo();
- console.log('User logged in:', userInfo);
-
- // 显示用户界面
- showDashboard(userInfo);
- } catch (error) {
- // 用户未登录,重定向到授权页面
- redirectToAuth();
- }
-}
-
-// 页面加载时初始化
-document.addEventListener('DOMContentLoaded', initApp);
-```
-
-## 🛡️ 安全最佳实践
-
-### 1. HTTPS 必需
-```
-生产环境必须使用HTTPS
-重定向URI必须使用https://(本地开发可用http://localhost)
-```
-
-### 2. 状态参数验证
-```javascript
-// 发起授权时
-const state = crypto.randomUUID();
-localStorage.setItem('oauth_state', state);
-
-// 回调时验证
-const returnedState = urlParams.get('state');
-const savedState = localStorage.getItem('oauth_state');
-if (returnedState !== savedState) {
- throw new Error('State mismatch - possible CSRF attack');
-}
-```
-
-### 3. 令牌安全存储
-```javascript
-// 使用HttpOnly Cookie(推荐)
-// 或加密存储在localStorage
-function secureStorage() {
- return {
- setItem: (key, value) => {
- const encrypted = encrypt(value); // 使用加密
- localStorage.setItem(key, encrypted);
- },
- getItem: (key) => {
- const encrypted = localStorage.getItem(key);
- return encrypted ? decrypt(encrypted) : null;
- }
- };
-}
-```
-
-## 📚 完整示例项目
-
-创建一个完整的单页应用示例:
-
-```html
-
-
-
- OAuth2 Demo
-
-
-
-
请登录
-
-
-
-
-
-
-
-
-```
-
-## 🔍 调试和测试
-
-### 验证JWT令牌
-访问 [jwt.io](https://jwt.io) 解析令牌内容:
-```
-Header: {"alg":"RS256","typ":"JWT","kid":"oauth2-key-1"}
-Payload: {"sub":"user_id","aud":"your_client_id","exp":1234567890}
-```
-
-### 查看服务器信息
-```bash
-curl https://your-domain.com/.well-known/oauth-authorization-server
-```
-
-### 获取JWKS公钥
-```bash
-curl https://your-domain.com/.well-known/jwks.json
-```
-
----
-
-这个demo涵盖了OAuth2服务器的完整使用流程,实现了真正的自动登录功能。用户只需要第一次授权,之后应用会自动处理令牌刷新和API认证。
\ No newline at end of file
diff --git a/docs/oauth2_setup.md b/docs/oauth2_setup.md
deleted file mode 100644
index 5cb3dd294..000000000
--- a/docs/oauth2_setup.md
+++ /dev/null
@@ -1,258 +0,0 @@
-# OAuth2 服务器设置指南
-
-## 概述
-
-该 OAuth2 服务器实现基于 RFC 6749 标准,支持以下特性:
-
-- **授权类型**: Client Credentials, Authorization Code + PKCE, Refresh Token
-- **JWT 访问令牌**: 使用 RS256 签名
-- **JWKS 端点**: 公钥自动发布和轮换
-- **兼容性**: 与现有认证系统完全兼容
-
-## 配置
-
-### 1. 环境变量配置
-
-在 `.env` 文件中添加以下配置:
-
-```env
-# OAuth2 基础配置
-OAUTH2_ENABLED=true
-OAUTH2_ISSUER=https://your-domain.com
-OAUTH2_ACCESS_TOKEN_TTL=10
-OAUTH2_REFRESH_TOKEN_TTL=720
-
-# JWT 签名配置
-JWT_SIGNING_ALGORITHM=RS256
-JWT_KEY_ID=oauth2-key-1
-JWT_PRIVATE_KEY_FILE=/path/to/private-key.pem
-
-# 授权类型(逗号分隔)
-OAUTH2_ALLOWED_GRANT_TYPES=client_credentials,authorization_code,refresh_token
-
-# 强制 PKCE
-OAUTH2_REQUIRE_PKCE=true
-
-# 自动创建用户
-OAUTH2_AUTO_CREATE_USER=false
-OAUTH2_DEFAULT_USER_ROLE=1
-OAUTH2_DEFAULT_USER_GROUP=default
-```
-
-### 2. 数据库迁移
-
-重启应用程序将自动创建 `oauth_clients` 表。
-
-### 3. 创建第一个 OAuth2 客户端
-
-通过管理员界面或 API 创建客户端:
-
-```bash
-curl -X POST http://localhost:8080/api/oauth_clients \
- -H "Authorization: Bearer YOUR_ADMIN_TOKEN" \
- -H "Content-Type: application/json" \
- -d '{
- "name": "测试服务",
- "client_type": "confidential",
- "grant_types": ["client_credentials"],
- "scopes": ["api:read", "api:write"],
- "description": "用于服务对服务认证的测试客户端"
- }'
-```
-
-## OAuth2 端点
-
-### 标准端点
-
-- **令牌端点**: `POST /api/oauth/token`
-- **授权端点**: `GET /api/oauth/authorize`
-- **JWKS 端点**: `GET /.well-known/jwks.json`
-- **服务器信息**: `GET /.well-known/oauth-authorization-server`
-
-### 管理端点
-
-- **令牌内省**: `POST /api/oauth/introspect` (需要管理员权限)
-- **令牌撤销**: `POST /api/oauth/revoke`
-
-### 客户端管理端点
-
-- **列出客户端**: `GET /api/oauth_clients`
-- **创建客户端**: `POST /api/oauth_clients`
-- **更新客户端**: `PUT /api/oauth_clients`
-- **删除客户端**: `DELETE /api/oauth_clients/{id}`
-- **重新生成密钥**: `POST /api/oauth_clients/{id}/regenerate_secret`
-
-## 使用示例
-
-### 1. Client Credentials 流程
-
-```go
-package main
-
-import (
- "context"
- "golang.org/x/oauth2/clientcredentials"
-)
-
-func main() {
- cfg := clientcredentials.Config{
- ClientID: "your_client_id",
- ClientSecret: "your_client_secret",
- TokenURL: "https://your-domain.com/api/oauth/token",
- Scopes: []string{"api:read"},
- }
-
- client := cfg.Client(context.Background())
- resp, _ := client.Get("https://your-domain.com/api/protected")
- // 处理响应...
-}
-```
-
-### 2. Authorization Code + PKCE 流程
-
-```go
-package main
-
-import (
- "context"
- "golang.org/x/oauth2"
-)
-
-func main() {
- conf := oauth2.Config{
- ClientID: "your_web_client_id",
- ClientSecret: "your_web_client_secret",
- RedirectURL: "https://your-app.com/callback",
- Scopes: []string{"api:read"},
- Endpoint: oauth2.Endpoint{
- AuthURL: "https://your-domain.com/api/oauth/authorize",
- TokenURL: "https://your-domain.com/api/oauth/token",
- },
- }
-
- // 生成 PKCE 参数
- verifier := oauth2.GenerateVerifier()
-
- // 构建授权 URL
- url := conf.AuthCodeURL("state", oauth2.S256ChallengeOption(verifier))
-
- // 用户授权后,使用授权码交换令牌
- token, _ := conf.Exchange(context.Background(), code, oauth2.VerifierOption(verifier))
-
- // 使用令牌调用 API
- client := conf.Client(context.Background(), token)
- resp, _ := client.Get("https://your-domain.com/api/protected")
-}
-```
-
-### 3. cURL 示例
-
-```bash
-# 获取访问令牌
-curl -X POST https://your-domain.com/api/oauth/token \
- -H "Content-Type: application/x-www-form-urlencoded" \
- -u "client_id:client_secret" \
- -d "grant_type=client_credentials&scope=api:read"
-
-# 使用访问令牌调用 API
-curl -H "Authorization: Bearer ACCESS_TOKEN" \
- https://your-domain.com/api/status
-```
-
-## 安全建议
-
-### 1. 密钥管理
-
-- 使用强随机密钥生成器
-- 定期轮换 RSA 密钥对
-- 将私钥存储在安全位置
-- 考虑使用 HSM 或密钥管理服务
-
-### 2. 网络安全
-
-- 强制使用 HTTPS
-- 配置适当的 CORS 策略
-- 实现速率限制
-- 启用请求日志和监控
-
-### 3. 客户端管理
-
-- 定期审查客户端列表
-- 撤销不再使用的客户端
-- 监控客户端使用情况
-- 为不同用途创建不同的客户端
-
-### 4. Scope 和权限
-
-- 实施最小权限原则
-- 定期审查 scope 定义
-- 为敏感操作创建特殊 scope
-- 实现细粒度的权限控制
-
-## 故障排除
-
-### 常见问题
-
-1. **"OAuth2 server is disabled"**
- - 确保 `OAUTH2_ENABLED=true`
- - 检查配置文件是否正确加载
-
-2. **"invalid_client"**
- - 验证 client_id 和 client_secret
- - 确保客户端状态为启用
-
-3. **"invalid_grant"**
- - 检查授权类型是否被允许
- - 验证 PKCE 参数(如果启用)
-
-4. **"invalid_scope"**
- - 确保请求的 scope 在客户端配置中
- - 检查 scope 格式(空格分隔)
-
-### 调试
-
-启用详细日志:
-
-```env
-GIN_MODE=debug
-LOG_LEVEL=debug
-```
-
-检查 JWKS 端点:
-
-```bash
-curl https://your-domain.com/.well-known/jwks.json
-```
-
-验证令牌:
-
-```bash
-# 可以使用 https://jwt.io 解码和验证 JWT 令牌
-```
-
-## 生产部署
-
-### 1. 负载均衡
-
-- OAuth2 服务器是无状态的,支持水平扩展
-- 确保所有实例使用相同的私钥
-- 使用 Redis 作为令牌存储
-
-### 2. 监控
-
-- 监控令牌签发速率
-- 跟踪客户端使用情况
-- 设置异常告警
-
-### 3. 备份
-
-- 备份私钥文件
-- 备份客户端配置数据
-- 制定灾难恢复计划
-
-### 4. 性能优化
-
-- 启用 JWKS 缓存
-- 使用连接池
-- 优化数据库查询
-- 考虑使用 CDN 分发 JWKS
\ No newline at end of file