feat(auth): 添加 Linux DO Connect OAuth 登录支持
- 新增 Linux DO OAuth 配置项和环境变量支持 - 实现 OAuth 授权流程和回调处理 - 前端添加 Linux DO 登录按钮和回调页面 - 支持通过 Linux DO 账号注册/登录 - 添加相关国际化文本 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
368
Linux DO Connect.md
Normal file
368
Linux DO Connect.md
Normal file
@@ -0,0 +1,368 @@
|
||||
# Linux DO Connect
|
||||
|
||||
OAuth(Open Authorization)是一个开放的网络授权标准,目前最新版本为 OAuth 2.0。我们日常使用的第三方登录(如 Google 账号登录)就采用了该标准。OAuth 允许用户授权第三方应用访问存储在其他服务提供商(如 Google)上的信息,无需在不同平台上重复填写注册信息。用户授权后,平台可以直接访问用户的账户信息进行身份验证,而用户无需向第三方应用提供密码。
|
||||
|
||||
目前系统已实现完整的 OAuth2 授权码(code)方式鉴权,但界面等配套功能还在持续完善中。让我们一起打造一个更完善的共享方案。
|
||||
|
||||
## 基本介绍
|
||||
|
||||
这是一套标准的 OAuth2 鉴权系统,可以让开发者共享论坛的用户基本信息。
|
||||
|
||||
- 可获取字段:
|
||||
|
||||
| 参数 | 说明 |
|
||||
| ----------------- | ------------------------------- |
|
||||
| `id` | 用户唯一标识(不可变) |
|
||||
| `username` | 论坛用户名 |
|
||||
| `name` | 论坛用户昵称(可变) |
|
||||
| `avatar_template` | 用户头像模板URL(支持多种尺寸) |
|
||||
| `active` | 账号活跃状态 |
|
||||
| `trust_level` | 信任等级(0-4) |
|
||||
| `silenced` | 禁言状态 |
|
||||
| `external_ids` | 外部ID关联信息 |
|
||||
| `api_key` | API访问密钥 |
|
||||
|
||||
通过这些信息,公益网站/接口可以实现:
|
||||
|
||||
1. 基于 `id` 的服务频率限制
|
||||
2. 基于 `trust_level` 的服务额度分配
|
||||
3. 基于用户信息的滥用举报机制
|
||||
|
||||
## 相关端点
|
||||
|
||||
- Authorize 端点: `https://connect.linux.do/oauth2/authorize`
|
||||
- Token 端点:`https://connect.linux.do/oauth2/token`
|
||||
- 用户信息 端点:`https://connect.linux.do/api/user`
|
||||
|
||||
## 申请使用
|
||||
|
||||
- 访问 [Connect.Linux.Do](https://connect.linux.do/) 申请接入你的应用。
|
||||
|
||||

|
||||
|
||||
- 点击 **`我的应用接入`** - **`申请新接入`**,填写相关信息。其中 **`回调地址`** 是你的应用接收用户信息的地址。
|
||||
|
||||

|
||||
|
||||
- 申请成功后,你将获得 **`Client Id`** 和 **`Client Secret`**,这是你应用的唯一身份凭证。
|
||||
|
||||

|
||||
|
||||
## 接入 Linux Do
|
||||
|
||||
JavaScript
|
||||
```JavaScript
|
||||
// 安装第三方请求库(或使用原生的 Fetch API),本例中使用 axios
|
||||
// npm install axios
|
||||
|
||||
// 通过 OAuth2 获取 Linux Do 用户信息的参考流程
|
||||
const axios = require('axios');
|
||||
const readline = require('readline');
|
||||
|
||||
// 配置信息(建议通过环境变量配置,避免使用硬编码)
|
||||
const CLIENT_ID = '你的 Client ID';
|
||||
const CLIENT_SECRET = '你的 Client Secret';
|
||||
const REDIRECT_URI = '你的回调地址';
|
||||
const AUTH_URL = 'https://connect.linux.do/oauth2/authorize';
|
||||
const TOKEN_URL = 'https://connect.linux.do/oauth2/token';
|
||||
const USER_INFO_URL = 'https://connect.linux.do/api/user';
|
||||
|
||||
// 第一步:生成授权 URL
|
||||
function getAuthUrl() {
|
||||
const params = new URLSearchParams({
|
||||
client_id: CLIENT_ID,
|
||||
redirect_uri: REDIRECT_URI,
|
||||
response_type: 'code',
|
||||
scope: 'user'
|
||||
});
|
||||
|
||||
return `${AUTH_URL}?${params.toString()}`;
|
||||
}
|
||||
|
||||
// 第二步:获取 code 参数
|
||||
function getCode() {
|
||||
return new Promise((resolve) => {
|
||||
// 本例中使用终端输入来模拟流程,仅供本地测试
|
||||
// 请在实际应用中替换为真实的处理逻辑
|
||||
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
||||
rl.question('从回调 URL 中提取出 code,粘贴到此处并按回车:', (answer) => {
|
||||
rl.close();
|
||||
resolve(answer.trim());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 第三步:使用 code 参数获取访问令牌
|
||||
async function getAccessToken(code) {
|
||||
try {
|
||||
const form = new URLSearchParams({
|
||||
client_id: CLIENT_ID,
|
||||
client_secret: CLIENT_SECRET,
|
||||
code: code,
|
||||
redirect_uri: REDIRECT_URI,
|
||||
grant_type: 'authorization_code'
|
||||
}).toString();
|
||||
|
||||
const response = await axios.post(TOKEN_URL, form, {
|
||||
// 提醒:需正确配置请求头,否则无法正常获取访问令牌
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error(`获取访问令牌失败:${error.response ? JSON.stringify(error.response.data) : error.message}`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// 第四步:使用访问令牌获取用户信息
|
||||
async function getUserInfo(accessToken) {
|
||||
try {
|
||||
const response = await axios.get(USER_INFO_URL, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`
|
||||
}
|
||||
});
|
||||
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error(`获取用户信息失败:${error.response ? JSON.stringify(error.response.data) : error.message}`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// 主流程
|
||||
async function main() {
|
||||
// 1. 生成授权 URL,前端引导用户访问授权页
|
||||
const authUrl = getAuthUrl();
|
||||
console.log(`请访问此 URL 授权:${authUrl}
|
||||
`);
|
||||
|
||||
// 2. 用户授权后,从回调 URL 获取 code 参数
|
||||
const code = await getCode();
|
||||
|
||||
try {
|
||||
// 3. 使用 code 参数获取访问令牌
|
||||
const tokenData = await getAccessToken(code);
|
||||
const accessToken = tokenData.access_token;
|
||||
|
||||
// 4. 使用访问令牌获取用户信息
|
||||
if (accessToken) {
|
||||
const userInfo = await getUserInfo(accessToken);
|
||||
console.log(`
|
||||
获取用户信息成功:${JSON.stringify(userInfo, null, 2)}`);
|
||||
} else {
|
||||
console.log(`
|
||||
获取访问令牌失败:${JSON.stringify(tokenData)}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('发生错误:', error);
|
||||
}
|
||||
}
|
||||
```
|
||||
Python
|
||||
```python
|
||||
# 安装第三方请求库,本例中使用 requests
|
||||
# pip install requests
|
||||
|
||||
# 通过 OAuth2 获取 Linux Do 用户信息的参考流程
|
||||
import requests
|
||||
import json
|
||||
|
||||
# 配置信息(建议通过环境变量配置,避免使用硬编码)
|
||||
CLIENT_ID = '你的 Client ID'
|
||||
CLIENT_SECRET = '你的 Client Secret'
|
||||
REDIRECT_URI = '你的回调地址'
|
||||
AUTH_URL = 'https://connect.linux.do/oauth2/authorize'
|
||||
TOKEN_URL = 'https://connect.linux.do/oauth2/token'
|
||||
USER_INFO_URL = 'https://connect.linux.do/api/user'
|
||||
|
||||
# 第一步:生成授权 URL
|
||||
def get_auth_url():
|
||||
params = {
|
||||
'client_id': CLIENT_ID,
|
||||
'redirect_uri': REDIRECT_URI,
|
||||
'response_type': 'code',
|
||||
'scope': 'user'
|
||||
}
|
||||
auth_url = f"{AUTH_URL}?{'&'.join(f'{k}={v}' for k, v in params.items())}"
|
||||
return auth_url
|
||||
|
||||
# 第二步:获取 code 参数
|
||||
def get_code():
|
||||
# 本例中使用终端输入来模拟流程,仅供本地测试
|
||||
# 请在实际应用中替换为真实的处理逻辑
|
||||
return input('从回调 URL 中提取出 code,粘贴到此处并按回车:').strip()
|
||||
|
||||
# 第三步:使用 code 参数获取访问令牌
|
||||
def get_access_token(code):
|
||||
try:
|
||||
data = {
|
||||
'client_id': CLIENT_ID,
|
||||
'client_secret': CLIENT_SECRET,
|
||||
'code': code,
|
||||
'redirect_uri': REDIRECT_URI,
|
||||
'grant_type': 'authorization_code'
|
||||
}
|
||||
# 提醒:需正确配置请求头,否则无法正常获取访问令牌
|
||||
headers = {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
response = requests.post(TOKEN_URL, data=data, headers=headers)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"获取访问令牌失败:{e}")
|
||||
return None
|
||||
|
||||
# 第四步:使用访问令牌获取用户信息
|
||||
def get_user_info(access_token):
|
||||
try:
|
||||
headers = {
|
||||
'Authorization': f'Bearer {access_token}'
|
||||
}
|
||||
response = requests.get(USER_INFO_URL, headers=headers)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"获取用户信息失败:{e}")
|
||||
return None
|
||||
|
||||
# 主流程
|
||||
if __name__ == '__main__':
|
||||
# 1. 生成授权 URL,前端引导用户访问授权页
|
||||
auth_url = get_auth_url()
|
||||
print(f'请访问此 URL 授权:{auth_url}
|
||||
')
|
||||
|
||||
# 2. 用户授权后,从回调 URL 获取 code 参数
|
||||
code = get_code()
|
||||
|
||||
# 3. 使用 code 参数获取访问令牌
|
||||
token_data = get_access_token(code)
|
||||
if token_data:
|
||||
access_token = token_data.get('access_token')
|
||||
|
||||
# 4. 使用访问令牌获取用户信息
|
||||
if access_token:
|
||||
user_info = get_user_info(access_token)
|
||||
if user_info:
|
||||
print(f"
|
||||
获取用户信息成功:{json.dumps(user_info, indent=2)}")
|
||||
else:
|
||||
print("
|
||||
获取用户信息失败")
|
||||
else:
|
||||
print(f"
|
||||
获取访问令牌失败:{json.dumps(token_data, indent=2)}")
|
||||
else:
|
||||
print("
|
||||
获取访问令牌失败")
|
||||
```
|
||||
PHP
|
||||
```php
|
||||
// 通过 OAuth2 获取 Linux Do 用户信息的参考流程
|
||||
|
||||
// 配置信息
|
||||
$CLIENT_ID = '你的 Client ID';
|
||||
$CLIENT_SECRET = '你的 Client Secret';
|
||||
$REDIRECT_URI = '你的回调地址';
|
||||
$AUTH_URL = 'https://connect.linux.do/oauth2/authorize';
|
||||
$TOKEN_URL = 'https://connect.linux.do/oauth2/token';
|
||||
$USER_INFO_URL = 'https://connect.linux.do/api/user';
|
||||
|
||||
// 生成授权 URL
|
||||
function getAuthUrl($clientId, $redirectUri) {
|
||||
global $AUTH_URL;
|
||||
return $AUTH_URL . '?' . http_build_query([
|
||||
'client_id' => $clientId,
|
||||
'redirect_uri' => $redirectUri,
|
||||
'response_type' => 'code',
|
||||
'scope' => 'user'
|
||||
]);
|
||||
}
|
||||
|
||||
// 使用 code 参数获取用户信息(合并获取令牌和获取用户信息的步骤)
|
||||
function getUserInfoWithCode($code, $clientId, $clientSecret, $redirectUri) {
|
||||
global $TOKEN_URL, $USER_INFO_URL;
|
||||
|
||||
// 1. 获取访问令牌
|
||||
$ch = curl_init($TOKEN_URL);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([
|
||||
'client_id' => $clientId,
|
||||
'client_secret' => $clientSecret,
|
||||
'code' => $code,
|
||||
'redirect_uri' => $redirectUri,
|
||||
'grant_type' => 'authorization_code'
|
||||
]));
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||
'Content-Type: application/x-www-form-urlencoded',
|
||||
'Accept: application/json'
|
||||
]);
|
||||
|
||||
$tokenResponse = curl_exec($ch);
|
||||
curl_close($ch);
|
||||
|
||||
$tokenData = json_decode($tokenResponse, true);
|
||||
if (!isset($tokenData['access_token'])) {
|
||||
return ['error' => '获取访问令牌失败', 'details' => $tokenData];
|
||||
}
|
||||
|
||||
// 2. 获取用户信息
|
||||
$ch = curl_init($USER_INFO_URL);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||
'Authorization: Bearer ' . $tokenData['access_token']
|
||||
]);
|
||||
|
||||
$userResponse = curl_exec($ch);
|
||||
curl_close($ch);
|
||||
|
||||
return json_decode($userResponse, true);
|
||||
}
|
||||
|
||||
// 主流程
|
||||
// 1. 生成授权 URL
|
||||
$authUrl = getAuthUrl($CLIENT_ID, $REDIRECT_URI);
|
||||
echo "<a href='$authUrl'>使用 Linux Do 登录</a>";
|
||||
|
||||
// 2. 处理回调并获取用户信息
|
||||
if (isset($_GET['code'])) {
|
||||
$userInfo = getUserInfoWithCode(
|
||||
$_GET['code'],
|
||||
$CLIENT_ID,
|
||||
$CLIENT_SECRET,
|
||||
$REDIRECT_URI
|
||||
);
|
||||
|
||||
if (isset($userInfo['error'])) {
|
||||
echo '错误: ' . $userInfo['error'];
|
||||
} else {
|
||||
echo '欢迎, ' . $userInfo['name'] . '!';
|
||||
// 处理用户登录逻辑...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 使用说明
|
||||
|
||||
### 授权流程
|
||||
|
||||
1. 用户点击应用中的’使用 Linux Do 登录’按钮
|
||||
2. 系统将用户重定向至 Linux Do 的授权页面
|
||||
3. 用户完成授权后,系统自动重定向回应用并携带授权码
|
||||
4. 应用使用授权码获取访问令牌
|
||||
5. 使用访问令牌获取用户信息
|
||||
|
||||
### 安全建议
|
||||
|
||||
- 切勿在前端代码中暴露 Client Secret
|
||||
- 对所有用户输入数据进行严格验证
|
||||
- 确保使用 HTTPS 协议传输数据
|
||||
- 定期更新并妥善保管 Client Secret
|
||||
Reference in New Issue
Block a user