该帖为站主方便配置而写,目前仅支持(lxzpan.cn)此域名使用,暂未开放使用。
1. 申请接入
获取以下信息:
- `site_id`: 网站唯一标识(如:`lxzpan`)
- `site_secret`: 网站密钥(用于API验证)
- 允许的回调域名
2. 配置登录按钮
将旗下网站的登录按钮链接修改为:
https://login.lxzpan.cn/?callback_url={回调地址}&site_id={网站标识}
**参数说明:**
| 参数 | 必填 | 说明 |
|--------------|------|---------------------------------------|
| callback_url | 是 | 登录成功后的回调地址,需URL编码 |
| site_id | 是 | 网站唯一标识 |
示例:
<!-- PHP示例 -->
<a href="https://login.lxzpan.cn/?callback_url=<?php echo urlencode('https://lxzpan.cn/auth/callback'); ?>&site_id=lxzpan">
登录
</a>
<!-- JavaScript示例 -->
<a href="https://login.lxzpan.cn/?callback_url=" + encodeURIComponent('https://lxzpan.cn/auth/callback') + "&site_id=lxzpan">
登录
</a>
3. 处理回调
用户登录成功后,会携带 `ticket` 参数回调到指定地址:
https://your-site.com/auth/callback?ticket=xxxxxxxxxxxxxxxx
4. 验证票据获取用户信息
在回调页面中,使用 `ticket` 换取用户信息:
PHP 示例
<?php
// config.php
define('SSO_SITE_ID', 'lxzpan');
define('SSO_SITE_SECRET', 'your_secret_key');
define('SSO_API_URL', 'https://login.lxzpan.cn/api.php');
// callback.php
$ticket = $_GET['ticket'] ?? '';
if (empty($ticket)) {
die('缺少票据参数');
}
// 调用API验证票据
$data = [
'ticket' => $ticket,
'site_id' => SSO_SITE_ID,
'site_secret' => SSO_SITE_SECRET
];
$ch = curl_init(SSO_API_URL . '?action=verify_ticket');
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
$response = curl_exec($ch);
curl_close($ch);
$result = json_decode($response, true);
if ($result['success']) {
// 登录成功
$user = $result['user'];
$token = $result['token'];
// 存储到Session
$_SESSION['user'] = $user;
$_SESSION['token'] = $token;
// 跳转到首页
header('Location: /');
exit;
} else {
// 登录失败
die('登录失败: ' . $result['message']);
}
?>
JavaScript/Node.js 示例
javascript
// 回调页面处理
async function handleCallback() {
const urlParams = new URLSearchParams(window.location.search);
const ticket = urlParams.get('ticket');
if (!ticket) {
console.error('缺少票据参数');
return;
}
try {
const response = await fetch('https://login.lxzpan.cn/api.php?action=verify_ticket', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
ticket: ticket,
site_id: 'lxzpan',
site_secret: 'your_secret_key'
})
});
const result = await response.json();
if (result.success) {
// 存储用户信息
localStorage.setItem('user', JSON.stringify(result.user));
localStorage.setItem('token', result.token);
// 跳转到首页
window.location.href = '/';
} else {
console.error('登录失败:', result.message);
}
} catch (error) {
console.error('请求失败:', error);
}
}
handleCallback();
Python 示例
import requests
from flask import Flask, request, redirect, session
SSO_SITE_ID = 'lxzpan'
SSO_SITE_SECRET = 'your_secret_key'
SSO_API_URL = 'https://login.lxzpan.cn/api.php'
@app.route('/auth/callback')
def auth_callback():
ticket = request.args.get('ticket')
if not ticket:
return '缺少票据参数', 400
# 验证票据
response = requests.post(
f'{SSO_API_URL}?action=verify_ticket',
json={
'ticket': ticket,
'site_id': SSO_SITE_ID,
'site_secret': SSO_SITE_SECRET
}
)
result = response.json()
if result['success']:
# 登录成功
session['user'] = result['user']
session['token'] = result['token']
return redirect('/')
else:
return f"登录失败: {result['message']}", 400
API 接口文档
1. 用户登录
POST /api.php?action=login
**请求参数:**
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| username | string | 是 | 用户名/邮箱/手机号 |
| password | string | 是 | 密码 |
| callback_url | string | 否 | 回调地址(SSO时使用) |
| site_id | string | 否 | 网站标识(SSO时使用) |
响应示例:
```json
{
"success": true,
"message": "登录成功",
"token": "eyJ0b2tlbiI6...",
"user": {
"id": 1,
"username": "testuser",
"email": "test@example.com"
},
"ticket": "a1b2c3d4e5f6...",
"callback_url": "https://lxzpan.cn/auth/callback"
}
2. 验证票据
POST /api.php?action=verify_ticket
**请求参数:**
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| ticket | string | 是 | SSO票据 |
| site_id | string | 是 | 网站标识 |
| site_secret | string | 是 | 网站密钥 |
响应示例:
```json
{
"success": true,
"user": {
"id": 1,
"username": "testuser",
"email": "test@example.com",
"avatar": "https://...",
"created_at": "2024-01-01 00:00:00"
},
"token": "eyJ0b2tlbiI6..."
}
3. 验证 Token
POST /api.php?action=verify_token
请求头:
Authorization: Bearer {token}
响应示例:
```json
{
"success": true,
"user": {
"id": 1,
"username": "testuser",
"email": "test@example.com",
"avatar": "https://...",
"created_at": "2024-01-01 00:00:00"
}
}
4. 刷新 Token
POST /api.php?action=refresh_token
请求头:
Authorization: Bearer {token}
响应示例:
```json
{
"success": true,
"token": "eyJ0b2tlbiI6..."
}
5. 登出
POST /api.php?action=logout
响应示例:
json
{
"success": true,
"message": "登出成功"
}
完整接入示例
PHP 完整示例
<?php
// sso_config.php
class SSOConfig {
const SITE_ID = 'lxzpan';
const SITE_SECRET = 'your_secret_key';
const LOGIN_URL = 'https://login.lxzpan.cn/';
const API_URL = 'https://login.lxzpan.cn/api.php';
}
// sso_helper.php
class SSOHelper {
// 获取登录链接
public static function getLoginUrl($callbackUrl) {
return SSOConfig::LOGIN_URL . '?callback_url=' . urlencode($callbackUrl)
. '&site_id=' . SSOConfig::SITE_ID;
}
// 验证票据
public static function verifyTicket($ticket) {
$data = [
'ticket' => $ticket,
'site_id' => SSOConfig::SITE_ID,
'site_secret' => SSOConfig::SITE_SECRET
];
$ch = curl_init(SSOConfig::API_URL . '?action=verify_ticket');
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
$response = curl_exec($ch);
curl_close($ch);
return json_decode($response, true);
}
// 验证Token
public static function verifyToken($token) {
$ch = curl_init(SSOConfig::API_URL . '?action=verify_token');
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'Authorization: Bearer ' . $token
]);
$response = curl_exec($ch);
curl_close($ch);
return json_decode($response, true);
}
}
// 使用示例
session_start();
// 1. 在登录页面
$loginUrl = SSOHelper::getLoginUrl('https://lxzpan.cn/auth/callback');
echo '<a href="' . $loginUrl . '">登录</a>';
// 2. 在回调页面
if (isset($_GET['ticket'])) {
$result = SSOHelper::verifyTicket($_GET['ticket']);
if ($result['success']) {
$_SESSION['user'] = $result['user'];
$_SESSION['token'] = $result['token'];
header('Location: /');
exit;
}
}
// 3. 验证当前用户是否登录
if (isset($_SESSION['token'])) {
$result = SSOHelper::verifyToken($_SESSION['token']);
if (!$result['success']) {
// Token过期,需要重新登录
unset($_SESSION['user']);
unset($_SESSION['token']);
}
}
?>
### JavaScript 完整示例
## 安全注意事项
// sso-config.js
const SSO_CONFIG = {
SITE_ID: 'lxzpan',
SITE_SECRET: 'your_secret_key',
LOGIN_URL: 'https://login.lxzpan.cn/',
API_URL: 'https://login.lxzpan.cn/api.php'
};
// sso-helper.js
class SSOHelper {
// 获取登录链接
static getLoginUrl(callbackUrl) {
return `${SSO_CONFIG.LOGIN_URL}?callback_url=${encodeURIComponent(callbackUrl)}&site_id=${SSO_CONFIG.SITE_ID}`;
}
// 验证票据
static async verifyTicket(ticket) {
const response = await fetch(`${SSO_CONFIG.API_URL}?action=verify_ticket`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
ticket: ticket,
site_id: SSO_CONFIG.SITE_ID,
site_secret: SSO_CONFIG.SITE_SECRET
})
});
return await response.json();
}
// 验证Token
static async verifyToken(token) {
const response = await fetch(`${SSO_CONFIG.API_URL}?action=verify_token`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
}
});
return await response.json();
}
// 检查登录状态
static async checkLogin() {
const token = localStorage.getItem('token');
if (!token) return false;
const result = await this.verifyToken(token);
if (result.success) {
localStorage.setItem('user', JSON.stringify(result.user));
return true;
} else {
localStorage.removeItem('token');
localStorage.removeItem('user');
return false;
}
}
// 获取当前用户
static getCurrentUser() {
const user = localStorage.getItem('user');
return user ? JSON.parse(user) : null;
}
// 登出
static logout() {
localStorage.removeItem('token');
localStorage.removeItem('user');
window.location.href = this.getLoginUrl(window.location.href);
}
}
// 使用示例
// 1. 登录按钮
const loginBtn = document.getElementById('login-btn');
loginBtn.href = SSOHelper.getLoginUrl(window.location.origin + '/callback.html');
// 2. 回调页面处理
async function handleCallback() {
const urlParams = new URLSearchParams(window.location.search);
const ticket = urlParams.get('ticket');
if (ticket) {
const result = await SSOHelper.verifyTicket(ticket);
if (result.success) {
localStorage.setItem('token', result.token);
localStorage.setItem('user', JSON.stringify(result.user));
window.location.href = '/';
} else {
alert('登录失败: ' + result.message);
}
}
}
// 3. 页面加载时检查登录状态
document.addEventListener('DOMContentLoaded', async () => {
const isLoggedIn = await SSOHelper.checkLogin();
if (!isLoggedIn && window.location.pathname !== '/login.html') {
// 未登录,跳转到登录页
window.location.href = SSOHelper.getLoginUrl(window.location.href);
}
});
1. 密钥保密: `site_secret` 必须严格保密,不要在前端代码中暴露
2. HTTPS: 所有通信必须使用 HTTPS
3. 票据有效期: SSO票据有效期为5分钟,且只能使用一次
4. Token存储: 建议将Token存储在 httpOnly Cookie 中,避免XSS攻击
5. 回调域名: 确保回调域名已在登录中心白名单中注册
常见问题
Q: 如何获取 site_id 和 site_secret?
A: 联系管理员申请接入,提供你的网站域名和名称。
Q: 票据验证失败怎么办?
A: 检查以下几点:
票据是否已过期(5分钟有效期)
票据是否已被使用(只能使用一次)
site_id 和 site_secret 是否正确
Q: 如何实现自动登录?
A: 在页面加载时调用 `verify_token` 接口验证本地存储的token,如果有效则保持登录状态。
Q: 如何退出登录?
A: 清除本地存储的token和用户信息,并可选择调用 `logout` 接口。
参与讨论