init
This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
"""
|
||||
LinuxDO OAuth2 服务
|
||||
"""
|
||||
import httpx
|
||||
import secrets
|
||||
from typing import Optional, Dict, Any
|
||||
from app.config import settings
|
||||
|
||||
|
||||
class LinuxDOOAuthService:
|
||||
"""LinuxDO OAuth2 服务类"""
|
||||
|
||||
# LinuxDO OAuth2 端点
|
||||
AUTHORIZE_URL = "https://connect.linux.do/oauth2/authorize"
|
||||
TOKEN_URL = "https://connect.linux.do/oauth2/token"
|
||||
USERINFO_URL = "https://connect.linux.do/api/user" # 修复:使用正确的用户信息端点
|
||||
|
||||
def __init__(self):
|
||||
self.client_id = settings.LINUXDO_CLIENT_ID
|
||||
self.client_secret = settings.LINUXDO_CLIENT_SECRET
|
||||
self.redirect_uri = settings.LINUXDO_REDIRECT_URI
|
||||
|
||||
# 验证redirect_uri配置
|
||||
if not self.redirect_uri:
|
||||
raise ValueError(
|
||||
"LINUXDO_REDIRECT_URI 未配置!\n"
|
||||
"请在 .env 文件中设置正确的回调地址:\n"
|
||||
"本地开发: LINUXDO_REDIRECT_URI=http://localhost:8000/api/auth/callback\n"
|
||||
"Docker部署: LINUXDO_REDIRECT_URI=https://your-domain.com/api/auth/callback"
|
||||
)
|
||||
|
||||
# 警告:检查是否使用了localhost(在非开发环境)
|
||||
if not settings.debug and "localhost" in self.redirect_uri.lower():
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.warning(
|
||||
f"⚠️ 生产环境检测到使用 localhost 作为回调地址: {self.redirect_uri}\n"
|
||||
"这可能导致OAuth回调失败!请使用实际的域名或服务器IP。"
|
||||
)
|
||||
|
||||
def generate_state(self) -> str:
|
||||
"""生成随机 state 参数"""
|
||||
return secrets.token_urlsafe(32)
|
||||
|
||||
def get_authorization_url(self, state: str) -> str:
|
||||
"""
|
||||
获取授权 URL
|
||||
|
||||
Args:
|
||||
state: 随机 state 参数
|
||||
|
||||
Returns:
|
||||
授权 URL
|
||||
"""
|
||||
params = {
|
||||
"client_id": self.client_id,
|
||||
"redirect_uri": self.redirect_uri,
|
||||
"response_type": "code",
|
||||
"scope": "read",
|
||||
"state": state
|
||||
}
|
||||
|
||||
query_string = "&".join([f"{k}={v}" for k, v in params.items()])
|
||||
return f"{self.AUTHORIZE_URL}?{query_string}"
|
||||
|
||||
async def get_access_token(self, code: str) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
使用授权码获取访问令牌
|
||||
|
||||
Args:
|
||||
code: 授权码
|
||||
|
||||
Returns:
|
||||
包含 access_token 的字典,失败返回 None
|
||||
"""
|
||||
data = {
|
||||
"client_id": self.client_id,
|
||||
"client_secret": self.client_secret,
|
||||
"code": code,
|
||||
"grant_type": "authorization_code",
|
||||
"redirect_uri": self.redirect_uri
|
||||
}
|
||||
|
||||
try:
|
||||
async with httpx.AsyncClient() as client:
|
||||
response = await client.post(
|
||||
self.TOKEN_URL,
|
||||
data=data,
|
||||
headers={"Content-Type": "application/x-www-form-urlencoded"}
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
return response.json()
|
||||
else:
|
||||
print(f"获取访问令牌失败: {response.status_code} {response.text}")
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
print(f"获取访问令牌异常: {e}")
|
||||
return None
|
||||
|
||||
async def get_user_info(self, access_token: str) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
使用访问令牌获取用户信息
|
||||
|
||||
Args:
|
||||
access_token: 访问令牌
|
||||
|
||||
Returns:
|
||||
用户信息字典,失败返回 None
|
||||
"""
|
||||
try:
|
||||
# 添加真实浏览器请求头,避免被 Cloudflare 拦截
|
||||
headers = {
|
||||
"Authorization": f"Bearer {access_token}",
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
||||
"Accept": "application/json",
|
||||
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
|
||||
}
|
||||
|
||||
# 不自动处理编码,让 httpx 自动解压
|
||||
async with httpx.AsyncClient(follow_redirects=True, timeout=30.0) as client:
|
||||
response = await client.get(
|
||||
self.USERINFO_URL,
|
||||
headers=headers
|
||||
)
|
||||
|
||||
print(f"获取用户信息响应状态: {response.status_code}")
|
||||
print(f"响应头: {response.headers}")
|
||||
|
||||
if response.status_code == 200:
|
||||
try:
|
||||
user_data = response.json()
|
||||
print(f"用户信息: {user_data}")
|
||||
return user_data
|
||||
except Exception as json_error:
|
||||
print(f"解析 JSON 失败: {json_error}")
|
||||
print(f"响应内容前100字符: {response.text[:100]}")
|
||||
return None
|
||||
else:
|
||||
print(f"获取用户信息失败: {response.status_code}")
|
||||
print(f"响应内容: {response.text[:200]}")
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
print(f"获取用户信息异常: {type(e).__name__}: {str(e)}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return None
|
||||
Reference in New Issue
Block a user