You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Chrome扩展MV3中实现并使用SSO登录?

在Chrome扩展Manifest V3中实现SSO登录的可行方案

Chrome MV3对扩展的权限和运行环境做了严格限制,但依然可以通过Chrome官方提供的chrome.identityAPI结合OAuth 2.0授权码流程(带PKCE)实现SSO登录,以下是具体实现方案:

核心思路

MV3中禁止了持久化后台页面,且CSP规则更严格,因此推荐使用弹出式Web授权流(通过chrome.identity.launchWebAuthFlow)结合PKCE(Proof Key for Code Exchange)来完成SSO登录,这种方式既符合MV3的安全要求,又能避免跨域问题。

具体实现步骤

1. 配置Manifest.json

首先在扩展的manifest.json中添加OAuth 2.0相关配置、必要的权限和回调地址:

{
  "manifest_version": 3,
  "name": "SSO Demo Extension",
  "version": "1.0",
  "oauth2": {
    "client_id": "YOUR_CLIENT_ID", // 替换为你的SSO提供商客户端ID
    "scopes": ["openid", "email", "profile"], // 根据需求调整权限范围
    "authorization_endpoint": "https://accounts.google.com/o/oauth2/v2/auth", // 替换为对应SSO的授权端点
    "token_endpoint": "https://oauth2.googleapis.com/token", // 替换为对应SSO的令牌端点
    "redirect_uri": "https://<EXTENSION_ID>.chromiumapp.org/" // 固定格式,替换为你的扩展ID
  },
  "host_permissions": [
    "https://accounts.google.com/*", // 替换为SSO提供商的授权域名
    "https://oauth2.googleapis.com/*" // 替换为SSO提供商的令牌域名
  ],
  "action": {
    "default_popup": "popup.html"
  },
  "background": {
    "service_worker": "background.js"
  },
  "permissions": ["identity", "storage"]
}

注意:redirect_uri必须使用https://<EXTENSION_ID>.chromiumapp.org/格式,这是Chrome扩展专用的回调地址,可通过chrome.identity.getRedirectURL()自动获取。

2. 触发登录流程(Popup/Content Script)

在扩展的弹出页面或内容脚本中,通过chrome.identity.launchWebAuthFlow打开SSO登录窗口,并处理授权码:

// popup.js
document.getElementById('login-btn').addEventListener('click', async () => {
  try {
    const manifest = chrome.runtime.getManifest();
    const redirectUrl = chrome.identity.getRedirectURL();
    
    // 生成PKCE验证所需的挑战码和验证器
    const codeVerifier = generateRandomString(32);
    const codeChallenge = await generatePKCEChallenge(codeVerifier);

    // 构造授权URL
    const authUrl = new URL(manifest.oauth2.authorization_endpoint);
    authUrl.searchParams.set('client_id', manifest.oauth2.client_id);
    authUrl.searchParams.set('response_type', 'code');
    authUrl.searchParams.set('scope', manifest.oauth2.scopes.join(' '));
    authUrl.searchParams.set('redirect_uri', redirectUrl);
    authUrl.searchParams.set('code_challenge', codeChallenge);
    authUrl.searchParams.set('code_challenge_method', 'S256');

    // 启动Web授权流
    const authResponse = await chrome.identity.launchWebAuthFlow({
      url: authUrl.toString(),
      interactive: true
    });

    // 从回调URL中提取授权码
    const code = new URL(authResponse).searchParams.get('code');
    
    // 发送授权码到后台脚本交换令牌
    chrome.runtime.sendMessage(
      { type: 'exchangeToken', code, codeVerifier },
      (response) => {
        if (response.success) {
          console.log('登录成功,令牌已获取');
          chrome.storage.local.set({ accessToken: response.token });
          // 更新UI状态
          document.getElementById('login-btn').style.display = 'none';
          document.getElementById('logout-btn').style.display = 'block';
        } else {
          console.error('登录失败:', response.error);
        }
      }
    );
  } catch (err) {
    console.error('登录流程异常:', err);
  }
});

// 生成随机字符串作为PKCE验证器
function generateRandomString(length) {
  const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
  let result = '';
  const array = new Uint8Array(length);
  crypto.getRandomValues(array);
  for (let i = 0; i < length; i++) {
    result += chars[array[i] % chars.length];
  }
  return result;
}

// 生成PKCE挑战码
async function generatePKCEChallenge(verifier) {
  const encoder = new TextEncoder();
  const data = encoder.encode(verifier);
  const digest = await crypto.subtle.digest('SHA-256', data);
  return btoa(String.fromCharCode(...new Uint8Array(digest)))
    .replace(/\+/g, '-')
    .replace(/\//g, '_')
    .replace(/=+$/, '');
}

3. 后台脚本交换令牌(Service Worker)

在后台Service Worker中,接收授权码并向SSO提供商请求访问令牌:

// background.js
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
  if (request.type === 'exchangeToken') {
    const manifest = chrome.runtime.getManifest();
    const redirectUrl = chrome.identity.getRedirectURL();

    // 构造令牌请求参数
    const formData = new URLSearchParams({
      client_id: manifest.oauth2.client_id,
      code: request.code,
      grant_type: 'authorization_code',
      redirect_uri: redirectUrl,
      code_verifier: request.codeVerifier
    });

    // 发送令牌请求
    fetch(manifest.oauth2.token_endpoint, {
      method: 'POST',
      headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
      body: formData
    })
    .then(res => res.json())
    .then(data => {
      if (data.access_token) {
        sendResponse({ success: true, token: data.access_token });
      } else {
        sendResponse({ success: false, error: data.error_description || '令牌交换失败' });
      }
    })
    .catch(err => {
      sendResponse({ success: false, error: err.message });
    });

    // 标记为异步响应
    return true;
  }
});

4. 验证登录状态与令牌管理

在扩展启动时,从chrome.storage.local读取令牌并验证有效性,处理过期或无效的情况:

// popup.js
document.addEventListener('DOMContentLoaded', async () => {
  const { accessToken } = await chrome.storage.local.get('accessToken');
  if (accessToken) {
    const isValid = await validateToken(accessToken);
    if (isValid) {
      document.getElementById('login-btn').style.display = 'none';
      document.getElementById('logout-btn').style.display = 'block';
    } else {
      chrome.storage.local.remove('accessToken');
    }
  }
});

// 验证令牌有效性(以Google为例)
async function validateToken(token) {
  const res = await fetch('https://www.googleapis.com/oauth2/v3/userinfo', {
    headers: { Authorization: `Bearer ${token}` }
  });
  return res.ok;
}

// 登出逻辑
document.getElementById('logout-btn').addEventListener('click', () => {
  chrome.storage.local.remove('accessToken');
  document.getElementById('login-btn').style.display = 'block';
  document.getElementById('logout-btn').style.display = 'none';
});

关键注意事项

  • PKCE强制使用:MV3中禁止隐式OAuth流程,必须使用授权码流程+PKCE,防止授权码被拦截。
  • 权限配置:确保host_permissions包含SSO提供商的授权、令牌及验证端点域名,否则请求会被拦截。
  • CSP规则:MV3默认CSP不允许unsafe-inline,所有脚本需放在外部文件中,避免违反规则。
  • 令牌存储:使用chrome.storage.local持久化存储令牌,不要存储在内存中,同时注意令牌过期时间,及时实现刷新逻辑。

内容的提问来源于stack exchange,提问作者Siddhartha Choubey

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.21 03:24:23