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 = ` -
-

完善用户信息

-

系统将为您自动创建账户,请填写或确认以下信息:

- -
-
- - - 用于登录的用户名 -
- -
- - - 在界面上显示的名称 -
- -
- - - 用于接收通知和找回密码 -
- -
- - - OAuth2自动创建的用户组 -
- -
-

从OAuth2提供商获取的信息:

-
-${JSON.stringify(jwtUserInfo, null, 2)}
-          
-
- - - -
-
- `; - - 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