常见问题

本文档汇总了使用 user.blym.top OAuth服务时的常见问题及解决方案。

授权流程问题

Q1: 授权后没有跳转到回调地址?

可能原因:

  1. redirect_uri 与注册时不匹配
  2. 客户端被禁用
  3. 用户取消授权

解决方案:

// 检查redirect_uri是否完全匹配
$registeredUris = json_decode($client['redirect_uris'], true);
if (!in_array($redirectUri, $registeredUris)) {
    throw new Exception('redirect_uri不匹配');
}

确保:

  • 协议相同(http/https)
  • 域名相同
  • 端口相同
  • 路径相同

Q2: 提示"invalid_client"错误?

可能原因:

  1. client_id 错误
  2. client_secret 错误
  3. 客户端不存在

解决方案:

# 验证客户端凭据
curl -X POST https://user.blym.top/oauth/token.php \
  -d "grant_type=client_credentials" \
  -d "client_id=YOUR_CLIENT_ID" \
  -d "client_secret=YOUR_CLIENT_SECRET"

如果返回 invalid_client,请检查:

  1. 客户端是否已创建
  2. 客户端是否已启用
  3. client_secret 是否正确

Q3: 授权码(code)使用后提示"invalid_grant"?

可能原因:

  1. 授权码已过期(10分钟有效期)
  2. 授权码已被使用
  3. 授权码不存在

解决方案:

授权码只能使用一次,请确保:

  • 收到授权码后立即换取令牌
  • 不要重复使用同一个授权码
  • 在10分钟内完成令牌交换

Q4: State验证失败?

可能原因:

  1. State参数不匹配
  2. Session丢失
  3. CSRF攻击

解决方案:

// 正确的state处理流程
// 1. 生成并保存state
const state = crypto.randomUUID();
sessionStorage.setItem('oauth_state', state);

// 2. 构建授权URL
const authUrl = `https://user.blym.top/oauth/authorize.php?` +
    `client_id=${clientId}&` +
    `redirect_uri=${encodeURIComponent(redirectUri)}&` +
    `response_type=code&` +
    `state=${state}`;

// 3. 在回调中验证
const returnedState = new URLSearchParams(location.search).get('state');
const savedState = sessionStorage.getItem('oauth_state');

if (returnedState !== savedState) {
    console.error('State验证失败,可能存在CSRF攻击');
    // 不要继续处理
}

令牌问题

Q5: 访问令牌过期后如何处理?

解决方案:

使用刷新令牌获取新的访问令牌:

<?php
function refreshAccessToken($refreshToken, $clientId, $clientSecret) {
    $ch = curl_init('https://user.blym.top/oauth/token.php');
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([
        'grant_type' => 'refresh_token',
        'refresh_token' => $refreshToken,
        'client_id' => $clientId,
        'client_secret' => $clientSecret
    ]));
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    $response = curl_exec($ch);
    curl_close($ch);
    return json_decode($response, true);
}

// 使用示例
if (tokenExpired()) {
    $newTokens = refreshAccessToken(
        $_SESSION['refresh_token'],
        CLIENT_ID,
        CLIENT_SECRET
    );
    $_SESSION['access_token'] = $newTokens['access_token'];
    $_SESSION['refresh_token'] = $newTokens['refresh_token'];
}
?>

Q6: 刷新令牌也过期了怎么办?

解决方案:

刷新令牌过期后,需要用户重新授权:

if (error.error === 'invalid_grant') {
    // 刷新令牌过期,重新授权
    window.location.href = getAuthorizationUrl();
}

Q7: 如何撤销令牌?

管理员撤销:

访问后台 https://user.blym.top/admin/oauth_server.php,在令牌管理中撤销。

用户撤销:

用户可在个人中心解除第三方应用授权。

用户信息问题

Q8: 获取用户信息时返回401错误?

可能原因:

  1. 访问令牌无效或过期
  2. Authorization头格式错误

解决方案:

// 正确的请求方式
$ch = curl_init('https://user.blym.top/api/userinfo.php');
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer ' . $accessToken  // 注意Bearer后有空格
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

if ($httpCode === 401) {
    // 令牌无效,尝试刷新
    $newToken = refreshAccessToken();
    // 重试请求
}

Q9: 用户信息中缺少某些字段?

可能原因: 请求的作用域(scope)不包含该字段。

解决方案:

字段 所需作用域
sub openid
name, preferred_username profile
email, email_verified email

确保授权时请求了正确的作用域:

scope=openid profile email

安全问题

Q10: 如何防止CSRF攻击?

解决方案:

始终使用state参数:

// 生成state
$state = bin2hex(random_bytes(16));
$_SESSION['oauth_state'] = $state;

// 验证state
if ($_GET['state'] !== $_SESSION['oauth_state']) {
    die('CSRF验证失败');
}

Q11: 如何安全存储令牌?

推荐方式:

环境 存储方式
服务器端 数据库(加密)或安全会话
浏览器 HttpOnly Cookie 或 Session Storage
移动端 系统密钥库(Keychain/Keystore)

不推荐:

  • LocalStorage(易受XSS攻击)
  • URL参数(会泄露)
  • 全局变量

Q12: 如何实现PKCE?

完整示例:

// 1. 生成code_verifier
function generateCodeVerifier() {
    const array = new Uint8Array(32);
    crypto.getRandomValues(array);
    return base64UrlEncode(array);
}

// 2. 生成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 base64UrlEncode(new Uint8Array(digest));
}

// 3. 授权请求
const verifier = generateCodeVerifier();
const challenge = await generateCodeChallenge(verifier);
sessionStorage.setItem('code_verifier', verifier);

const authUrl = `https://user.blym.top/oauth/authorize.php?` +
    `client_id=${clientId}&` +
    `redirect_uri=${redirectUri}&` +
    `response_type=code&` +
    `code_challenge=${challenge}&` +
    `code_challenge_method=S256&` +
    `state=${state}`;

// 4. 令牌请求(包含code_verifier)
const tokenResponse = await fetch('https://user.blym.top/oauth/token.php', {
    method: 'POST',
    body: new URLSearchParams({
        grant_type: 'authorization_code',
        code: authCode,
        redirect_uri: redirectUri,
        client_id: clientId,
        code_verifier: verifier  // 使用之前保存的verifier
    })
});

配置问题

Q13: 如何修改令牌有效期?

管理员操作:

  1. 访问 https://user.blym.top/admin/oauth_server.php
  2. 编辑客户端
  3. 修改:
    • 访问令牌有效期(默认3600秒)
    • 刷新令牌有效期(默认2592000秒)

Q14: 如何查看审计日志?

管理员操作:

  1. 访问 https://user.blym.top/admin/oauth_server.php
  2. 点击"审计日志"标签页
  3. 可按时间、用户、客户端、操作类型筛选

Q15: 如何重置客户端密钥?

管理员操作:

  1. 访问客户端管理页面
  2. 点击客户端的"重置密钥"按钮
  3. 确认操作
  4. 保存新密钥

⚠️ 重置后旧密钥立即失效,需要更新所有使用该客户端的应用配置。

联系支持

如果以上解决方案未能解决您的问题,请:

  1. 查看审计日志获取详细错误信息
  2. 联系管理员:访问 user.blym.top
  3. 提供以下信息以便排查:
    • 客户端ID
    • 错误发生时间
    • 错误信息
    • 请求参数