跳到主内容

Chrome扩展

casdoor-chrome-extension 仓库展示了如何在Chrome扩展程序中集成Casdoor。 步骤摘要:

步骤1:部署Casdoor

生产模式. 确认http://localhost:8000上的登录页面正常工作(例如,在开发环境中使用admin/123登录)。

步骤2:配置应用程序

  1. 在Casdoor中,前往应用并创建或编辑一个应用。
  2. 为扩展添加重定向URL(例如:https://<0>.chromiumapp.org/ 或开发环境下的 http://localhost:3000/callback)。
  3. 请注意客户端ID客户端密钥,并启用您所需的OAuth选项。

步骤3:设置Chrome扩展程序

1. 创建清单文件

在您的Chrome扩展项目中创建一个包含必要权限的manifest.json文件:

{
"manifest_version": 3,
"name": "Casdoor Chrome Extension",
"version": "1.0.0",
"description": "Chrome extension integrated with Casdoor",
"permissions": [
"identity",
"storage"
],
"host_permissions": [
"http://localhost:8000/*",
"https://door.casdoor.com/*"
],
"action": {
"default_popup": "popup.html",
"default_icon": {
"16": "icons/icon16.png",
"48": "icons/icon48.png",
"128": "icons/icon128.png"
}
},
"background": {
"service_worker": "background.js"
}
}

2. 配置扩展身份

manifest.json中,如果您使用的是Chrome的身份验证API,可能需要添加OAuth2配置:

{
"oauth2": {
"client_id": "your-client-id.apps.googleusercontent.com",
"scopes": ["openid", "profile", "email"]
}
}
注意事项

请将配置值替换为您自己的Casdoor实例,尤其是client_id和主机权限URL。

第4步:实现身份验证流程

1. 创建后台脚本

创建一个 background.js 文件以处理身份验证:

const CASDOOR_ENDPOINT = "http://localhost:8000";
const CLIENT_ID = "your-client-id";
const CLIENT_SECRET = "your-client-secret";
const ORGANIZATION_NAME = "built-in";
const APPLICATION_NAME = "app-built-in";
const REDIRECT_URI = chrome.identity.getRedirectURL();

// Generate the authorization URL
function getAuthUrl() {
const state = Math.random().toString(36).substring(7);
const authUrl = `${CASDOOR_ENDPOINT}/login/oauth/authorize?client_id=${CLIENT_ID}&response_type=code&redirect_uri=${encodeURIComponent(REDIRECT_URI)}&scope=openid%20profile%20email&state=${state}`;

chrome.storage.local.set({ oauthState: state });

return authUrl;
}

// Handle OAuth callback
async function handleOAuthCallback(redirectUrl) {
const url = new URL(redirectUrl);
const code = url.searchParams.get('code');
const state = url.searchParams.get('state');

// Verify state
const { oauthState } = await chrome.storage.local.get('oauthState');
if (state !== oauthState) {
throw new Error('Invalid state parameter');
}

// Exchange code for token
const tokenResponse = await fetch(`${CASDOOR_ENDPOINT}/api/login/oauth/access_token`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
grant_type: 'authorization_code',
client_id: CLIENT_ID,
client_secret: CLIENT_SECRET,
code: code,
redirect_uri: REDIRECT_URI,
}),
});

const tokenData = await tokenResponse.json();
return tokenData;
}

// Listen for messages from popup
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request.action === 'login') {
chrome.identity.launchWebAuthFlow(
{
url: getAuthUrl(),
interactive: true,
},
async (redirectUrl) => {
if (chrome.runtime.lastError || !redirectUrl) {
sendResponse({ error: chrome.runtime.lastError?.message });
return;
}

try {
const tokenData = await handleOAuthCallback(redirectUrl);
await chrome.storage.local.set({ user: tokenData });
sendResponse({ success: true, data: tokenData });
} catch (error) {
sendResponse({ error: error.message });
}
}
);
return true; // Keep the message channel open for async response
}

if (request.action === 'logout') {
chrome.storage.local.remove(['user', 'oauthState'], () => {
sendResponse({ success: true });
});
return true;
}

if (request.action === 'getUser') {
chrome.storage.local.get('user', (result) => {
sendResponse({ user: result.user });
});
return true;
}
});

2. 创建弹出窗口HTML

为扩展程序弹出窗口创建一个popup.html文件:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Casdoor Login</title>
<style>
body {
width: 300px;
padding: 20px;
font-family: Arial, sans-serif;
}
button {
width: 100%;
padding: 10px;
margin: 5px 0;
cursor: pointer;
background-color: #4285f4;
color: white;
border: none;
border-radius: 4px;
}
button:hover {
background-color: #357ae8;
}
#user-info {
margin-top: 20px;
}
</style>
</head>
<body>
<div id="login-section">
<h2>Casdoor Login</h2>
<button id="login-btn">Login with Casdoor</button>
</div>

<div id="user-section" style="display: none;">
<h2>Welcome!</h2>
<div id="user-info"></div>
<button id="logout-btn">Logout</button>
</div>

<script src="popup.js"></script>
</body>
</html>

3. 创建弹出窗口脚本

创建一个popup.js文件以处理用户交互:

document.addEventListener('DOMContentLoaded', () => {
const loginSection = document.getElementById('login-section');
const userSection = document.getElementById('user-section');
const loginBtn = document.getElementById('login-btn');
const logoutBtn = document.getElementById('logout-btn');
const userInfo = document.getElementById('user-info');

// Check if user is already logged in
chrome.runtime.sendMessage({ action: 'getUser' }, (response) => {
if (response.user) {
showUserSection(response.user);
} else {
showLoginSection();
}
});

loginBtn.addEventListener('click', () => {
chrome.runtime.sendMessage({ action: 'login' }, (response) => {
if (response.error) {
alert('Login failed: ' + response.error);
} else if (response.success) {
showUserSection(response.data);
}
});
});

logoutBtn.addEventListener('click', () => {
chrome.runtime.sendMessage({ action: 'logout' }, (response) => {
if (response.success) {
showLoginSection();
}
});
});

function showLoginSection() {
loginSection.style.display = 'block';
userSection.style.display = 'none';
}

function showUserSection(user) {
loginSection.style.display = 'none';
userSection.style.display = 'block';

// Parse JWT token to get user info
if (user.access_token) {
try {
const payload = JSON.parse(atob(user.access_token.split('.')[1]));
userInfo.innerHTML = `
<p><strong>Name:</strong> ${payload.name || 'N/A'}</p>
<p><strong>Email:</strong> ${payload.email || 'N/A'}</p>
`;
} catch (error) {
userInfo.innerHTML = '<p>Logged in successfully</p>';
}
}
}
});

第5步:加载并测试扩展程序第5步:加载并测试扩展程序

  1. 打开Chrome浏览器,访问chrome://extensions/
  2. 在右上角启用开发者模式
  3. 点击加载已解压的扩展程序,并选择您的扩展程序目录。
  4. 单击Chrome工具栏中的扩展程序图标。
  5. 点击使用Casdoor登录按钮以测试身份验证流程。

第6步:处理令牌存储与刷新

为保持用户会话,您应实现令牌刷新逻辑:

async function refreshAccessToken(refreshToken) {
const response = await fetch(`${CASDOOR_ENDPOINT}/api/login/oauth/refresh_token`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
grant_type: 'refresh_token',
refresh_token: refreshToken,
client_id: CLIENT_ID,
client_secret: CLIENT_SECRET,
}),
});

return await response.json();
}

// Check token validity and refresh if needed
async function getValidAccessToken() {
const { user } = await chrome.storage.local.get('user');

if (!user || !user.access_token) {
return null;
}

// Check if token is expired (decode JWT)
try {
const payload = JSON.parse(atob(user.access_token.split('.')[1]));
const expiry = payload.exp * 1000; // Convert to milliseconds

if (Date.now() >= expiry) {
// Token expired, refresh it
if (user.refresh_token) {
const newTokens = await refreshAccessToken(user.refresh_token);
await chrome.storage.local.set({ user: newTokens });
return newTokens.access_token;
}
return null;
}

return user.access_token;
} catch (error) {
console.error('Error checking token validity:', error);
return null;
}
}

另请参阅