From b965f90c978057029593b0b88d0b4a732271bbb8 Mon Sep 17 00:00:00 2001 From: Bret Date: Tue, 29 Jul 2025 17:31:14 -0700 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=94=B9README.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 +- debug-oauth-client.js | 1 + models/OAuthClient.js | 5 + package.json | 4 +- src/App.jsx | 9 +- src/contexts/AuthContext.jsx | 3 +- test-oauth-authorize.html | 176 ++++++++++++++++++++++++++++++ test-redirect-uri.js | 204 +++++++++++++++++++++++++++++++++++ 8 files changed, 400 insertions(+), 6 deletions(-) create mode 100644 debug-oauth-client.js create mode 100644 test-oauth-authorize.html create mode 100644 test-redirect-uri.js diff --git a/README.md b/README.md index 596b3b3..2a03bdf 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# OAuth 2.0 提供商服务 +# Pdnode Account -这是一个完整的OAuth 2.0提供商服务,支持Authorization Code Flow,包含用户认证、OAuth客户端管理、令牌管理等功能。 +这是一个支持普通登录注册和OAuth 2.0授权码流程的认证系统,适合自建账号体系和第三方授权。 ## 功能特性 diff --git a/debug-oauth-client.js b/debug-oauth-client.js new file mode 100644 index 0000000..0519ecb --- /dev/null +++ b/debug-oauth-client.js @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/models/OAuthClient.js b/models/OAuthClient.js index 2d0b8c2..af1591b 100644 --- a/models/OAuthClient.js +++ b/models/OAuthClient.js @@ -175,6 +175,11 @@ class OAuthClient { static async validateRedirectUri(clientId, redirectUri) { const client = await this.findByClientId(clientId); if (!client) return false; + // console.log(client.redirect_uris) + console.log("请求 redirect_uri: ", redirectUri); +console.log("允许 redirect_uris: ", client.redirect_uris); +console.log("是否包含: ", client.redirect_uris.includes(redirectUri)); + return client.redirect_uris.includes(redirectUri); } diff --git a/package.json b/package.json index eeb7f05..7a71df5 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { - "name": "auth-api", + "name": "pdnode-account", "version": "1.0.0", - "description": "登录和注册API系统", + "description": "Pdnode Account:支持普通登录注册和OAuth 2.0授权码流程的认证系统", "main": "index.js", "scripts": { "start": "node index.js", diff --git a/src/App.jsx b/src/App.jsx index d325194..8861f46 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -15,7 +15,14 @@ function App() { } /> } /> - } /> + + + + } + /> { const checkAuthStatus = async () => { try { const response = await axios.get('/api/auth/profile') - setUser(response.data.data) + setUser(response.data.data.user) + } catch (error) { localStorage.removeItem('token') delete axios.defaults.headers.common['Authorization'] diff --git a/test-oauth-authorize.html b/test-oauth-authorize.html new file mode 100644 index 0000000..3e5f914 --- /dev/null +++ b/test-oauth-authorize.html @@ -0,0 +1,176 @@ + + + + + + OAuth授权页面测试 + + + +

OAuth授权页面测试

+ +
+

测试说明

+

这个页面用于测试OAuth授权页面的修复情况。

+

问题描述:访问OAuth授权页面后直接跳转到login然后又跳转dashboard

+

修复内容:

+
    +
  • 将OAuth授权页面包装在PrivateRoute中
  • +
  • 等待AuthContext的loading状态完成
  • +
  • 简化组件逻辑,移除重复的认证检查
  • +
+
+ +
+

测试步骤

+
    +
  1. 确保后端服务运行在 http://localhost:3000
  2. +
  3. 确保前端服务运行在 http://localhost:3001
  4. +
  5. 先登录用户(访问 http://localhost:3001/login
  6. +
  7. 在个人中心创建一个OAuth客户端
  8. +
  9. 使用下面的测试链接访问OAuth授权页面
  10. +
+
+ +
+

测试链接

+

点击下面的按钮测试OAuth授权页面:

+ + + + + + +
+ +
+

预期结果

+
    +
  • ✅ 已登录用户:应该显示授权页面,不跳转到login
  • +
  • ✅ 未登录用户:应该跳转到login页面
  • +
  • ✅ 无效参数:应该显示错误信息
  • +
  • ✅ 加载状态:应该显示加载动画
  • +
+
+ +
+

测试结果

+
+

点击测试按钮开始测试...

+
+
+ + + + \ No newline at end of file diff --git a/test-redirect-uri.js b/test-redirect-uri.js new file mode 100644 index 0000000..5588a25 --- /dev/null +++ b/test-redirect-uri.js @@ -0,0 +1,204 @@ +const axios = require('axios'); + +// 测试重定向URI匹配逻辑 +function testRedirectUriMatching() { + console.log('🧪 测试重定向URI匹配逻辑...\n'); + + // 模拟数据库中存储的URI + const storedUris = [ + 'http://localhost:3001/third-party-app.html', + 'http://localhost:3001/callback', + 'https://example.com/callback' + ]; + + // 测试用例 + const testCases = [ + { + requested: 'http://127.0.0.1:5500/public/third-party-app.html', + expected: true, + description: '不同主机名和端口,相同路径' + }, + { + requested: 'http://localhost:3001/third-party-app.html', + expected: true, + description: '完全匹配' + }, + { + requested: 'http://localhost:3001/callback', + expected: true, + description: '匹配callback路径' + }, + { + requested: 'http://127.0.0.1:8080/callback', + expected: true, + description: '不同端口,相同路径' + }, + { + requested: 'http://localhost:3001/different-path.html', + expected: false, + description: '路径不匹配' + }, + { + requested: 'https://localhost:3001/third-party-app.html', + expected: false, + description: '协议不匹配' + } + ]; + + // 验证函数(与后端逻辑相同) + const isValidRedirectUri = (storedUris, requestedUri) => { + try { + const requestedUrl = new URL(requestedUri); + + for (const storedUri of storedUris) { + const storedUrl = new URL(storedUri); + + // 检查协议是否匹配 + if (requestedUrl.protocol !== storedUrl.protocol) { + continue; + } + + // 获取文件名(路径的最后一部分) + const requestedFilename = requestedUrl.pathname.split('/').pop(); + const storedFilename = storedUrl.pathname.split('/').pop(); + + // 检查文件名是否匹配,忽略路径前缀 + if (requestedFilename === storedFilename) { + return true; + } + + // 如果文件名不匹配,检查完整路径是否匹配 + if (requestedUrl.pathname === storedUrl.pathname) { + return true; + } + } + return false; + } catch (error) { + return false; + } + }; + + // 运行测试 + let passed = 0; + let total = testCases.length; + + testCases.forEach((testCase, index) => { + const result = isValidRedirectUri(storedUris, testCase.requested); + const status = result === testCase.expected ? '✅' : '❌'; + + console.log(`${status} 测试 ${index + 1}: ${testCase.description}`); + console.log(` 请求URI: ${testCase.requested}`); + console.log(` 期望结果: ${testCase.expected}, 实际结果: ${result}`); + console.log(''); + + if (result === testCase.expected) { + passed++; + } + }); + + console.log(`📊 测试结果: ${passed}/${total} 通过`); + + if (passed === total) { + console.log('🎉 所有测试通过!重定向URI验证逻辑正常工作。'); + } else { + console.log('⚠️ 部分测试失败,需要检查逻辑。'); + } +} + +// 测试实际的OAuth流程 +async function testOAuthFlow() { + console.log('\n🚀 测试实际OAuth流程...\n'); + + try { + // 1. 注册用户 + const testUser = { + username: `testuser_${Date.now()}`, + email: `testuser_${Date.now()}@example.com`, + password: 'TestPassword123' + }; + + console.log('1. 注册测试用户...'); + const registerResponse = await axios.post('http://localhost:3000/api/auth/register', testUser); + if (!registerResponse.data.success) { + console.log('❌ 用户注册失败:', registerResponse.data.message); + return; + } + console.log('✅ 用户注册成功'); + + // 2. 用户登录 + console.log('\n2. 用户登录...'); + const loginResponse = await axios.post('http://localhost:3000/api/auth/login', { + username: testUser.username, + password: testUser.password + }); + + if (!loginResponse.data.success) { + console.log('❌ 用户登录失败:', loginResponse.data.message); + return; + } + + const userToken = loginResponse.data.data.token; + console.log('✅ 用户登录成功'); + + // 3. 创建OAuth客户端 + console.log('\n3. 创建OAuth客户端...'); + const clientData = { + name: '测试应用', + description: '测试重定向URI匹配', + redirect_uris: ['http://localhost:3001/third-party-app.html'] + }; + + const clientResponse = await axios.post('http://localhost:3000/api/oauth/clients', clientData, { + headers: { + 'Authorization': `Bearer ${userToken}` + } + }); + + if (!clientResponse.data.success) { + console.log('❌ OAuth客户端创建失败:', clientResponse.data.message); + return; + } + + const clientId = clientResponse.data.data.client_id; + console.log('✅ OAuth客户端创建成功'); + console.log(` 客户端ID: ${clientId}`); + + // 4. 测试授权请求 + console.log('\n4. 测试授权请求...'); + const authParams = new URLSearchParams({ + response_type: 'code', + client_id: clientId, + redirect_uri: 'http://127.0.0.1:5500/public/third-party-app.html', + scope: 'read write', + state: 'test123' + }); + + const authResponse = await axios.get(`http://localhost:3000/api/oauth/authorize?${authParams}`, { + headers: { + 'Authorization': `Bearer ${userToken}` + } + }); + + if (authResponse.data.success) { + console.log('✅ 授权请求成功!'); + console.log(` 应用名称: ${authResponse.data.data.client.name}`); + console.log(` 请求权限: ${authResponse.data.data.scopes.join(', ')}`); + } else { + console.log('❌ 授权请求失败:', authResponse.data.message); + if (authResponse.data.debug) { + console.log(' 调试信息:', authResponse.data.debug); + } + } + + } catch (error) { + console.error('❌ 测试过程中发生错误:', error.response?.data || error.message); + } +} + +// 运行测试 +if (require.main === module) { + testRedirectUriMatching(); + testOAuthFlow(); +} + +module.exports = { testRedirectUriMatching, testOAuthFlow }; \ No newline at end of file