- 新增 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>
368 lines
12 KiB
Markdown
368 lines
12 KiB
Markdown
# 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 |