身份验证

Cliqer 使用 httpOnly 会话 Cookie(session_token)进行身份验证,该 Cookie 由 POST /api/auth/login 签发。浏览器会在每次请求时自动发送它;服务器间调用的工具则将其保存在 Cookie 存储(cookie jar)中。Studio 8K 和 Enterprise 团队还可以签发 API 密钥用于服务器间访问,以 Authorization: Bearer cliqer_<key> 的形式发送,详见下文的 API 密钥身份验证。
会话模型
| 凭据 | 存放位置 | 获取方式 |
|---|---|---|
会话 Cookie(session_token) | 您的 Cliqer 主机上的 httpOnly、Secure Cookie | 由 POST /api/auth/login 设置,并由服务器自动轮换 |
该 Cookie 带有 HttpOnly 属性,因此无法通过 JavaScript 读取。其有效期跟随服务器会话 TTL,每次调用 GET /api/auth/me 时都会透明续期。无需调用单独的刷新端点;要让长期运行的客户端保持会话,只需定期调用 GET /api/auth/me。
会话生命周期
使用会话
浏览器会自动附加该 Cookie。对于服务器间客户端,请保存登录响应所设置的 Cookie,并在后续请求中发送它:
# Save the session cookie on login, then reuse it
curl -X POST "$BASE_URL/api/auth/login" \
-H "Content-Type: application/json" \
-c cookies.txt \
-d '{"email":"user@example.com","password":"your_password"}'
curl -X GET "$BASE_URL/api/auth/me" -b cookies.txt
// Same-origin $fetch sends the httpOnly cookie automatically.
// Cross-origin fetch must opt in with credentials: 'include'.
const me = await $fetch('/api/auth/me')
import requests
session = requests.Session() # persists the session cookie
session.post('$BASE_URL/api/auth/login',
json={'email': 'user@example.com', 'password': 'your_password'})
me = session.get('$BASE_URL/api/auth/me').json()
jar, _ := cookiejar.New(nil)
client := &http.Client{Jar: jar}
// Login stores the session cookie in the jar; later requests reuse it.
client.Post("$BASE_URL/api/auth/login", "application/json", body)
resp, _ := client.Get("$BASE_URL/api/auth/me")
$ch = curl_init('$BASE_URL/api/auth/login');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_COOKIEJAR, 'cookies.txt');
curl_setopt($ch, CURLOPT_COOKIEFILE, 'cookies.txt');
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
'email' => 'user@example.com', 'password' => 'your_password'
]));
curl_exec($ch);
// reqwest with cookie_store persists the session cookie across calls
let client = reqwest::Client::builder()
.cookie_store(true)
.build()?;
client.post("$BASE_URL/api/auth/login")
.json(&serde_json::json!({"email": "user@example.com", "password": "your_password"}))
.send().await?;
let me = client.get("$BASE_URL/api/auth/me").send().await?;
API 密钥身份验证
Studio 8K 和 Enterprise 团队可以签发长期有效的 API 密钥用于服务器间访问,从而无需持有用户的登录凭据。可在控制台的 团队 → API 密钥(/dash/team/api-keys)中创建和吊销密钥;签发密钥需要团队所有者(或被授予 API 密钥权限的自定义角色)。密钥管理仅在 Studio 8K 和 Enterprise 套餐中提供。


密钥仅在创建时显示一次,格式为 cliqer_<prefix>_<secret>。请妥善保存:密钥无法找回,如果丢失,请吊销后重新创建。在任何接受会话的 REST 端点上,均可将其作为 Bearer 令牌发送:
curl -X GET "$BASE_URL/api/licenses/status?license_key=YOUR_LICENSE_KEY" \
-H "Authorization: Bearer cliqer_ab12cd34_your_secret_here"
API 密钥仅适用于 REST 端点。WebSocket(包括幻灯片控制)需要会话令牌。
该密钥以创建者的用户身份进行验证,作用范围限于该用户的团队和套餐。已吊销或未知的密钥会返回 401,与缺少会话时完全相同。仅限管理员的端点始终以 403 拒绝 API 密钥。密钥与会话请求受相同的速率限制约束。
登录
使用用户凭据换取会话。成功后,响应会设置 session_token Cookie。
端点: POST /api/auth/login
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
email | string | 是 | 用户邮箱(也接受 WordPress 用户名) |
password | string | 是 | 用户密码 |
otp | string | 否 | TOTP 验证码,适用于使用身份验证器应用的账户 |
email_otp | string | 否 | 通过邮件发送的 PIN,适用于使用邮件双重验证的账户 |
curl -X POST "$BASE_URL/api/auth/login" \
-H "Content-Type: application/json" \
-c cookies.txt \
-d '{"email":"user@example.com","password":"your_password"}'
interface LoginResponse {
data: {
expires: number | null // session lifetime in ms
}
}
const res: LoginResponse = await $fetch('/api/auth/login', {
method: 'POST',
body: { email: 'user@example.com', password: 'your_password' }
})
// The session_token cookie is set by the response; no token is in the body.
import requests
session = requests.Session()
data = {'email': 'user@example.com', 'password': 'your_password'}
res = session.post('$BASE_URL/api/auth/login', json=data)
# The session cookie now lives in `session`.
jar, _ := cookiejar.New(nil)
client := &http.Client{Jar: jar}
payload := map[string]string{"email": "user@example.com", "password": "your_password"}
body, _ := json.Marshal(payload)
client.Post("$BASE_URL/api/auth/login", "application/json", bytes.NewBuffer(body))
$ch = curl_init('$BASE_URL/api/auth/login');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_COOKIEJAR, 'cookies.txt');
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
'email' => 'user@example.com',
'password' => 'your_password'
]));
$response = curl_exec($ch);
use serde::Serialize;
#[derive(Serialize)]
struct LoginRequest { email: String, password: String }
let client = reqwest::Client::builder().cookie_store(true).build()?;
let response = client
.post("$BASE_URL/api/auth/login")
.json(&LoginRequest { email: "user@example.com".into(), password: "your_password".into() })
.send()
.await?;
响应(成功): 设置 session_token Cookie 并返回会话元数据。
{
"data": {
"expires": 604800000
}
}
响应(需要双重验证): 这是一个 200 控制流信号,而非错误。请使用相同的凭据再次提交 POST /api/auth/login,并附加 otp(身份验证器)或 email_otp(邮件 PIN)。
{
"requires_otp": true,
"method": "totp",
"email_sent": false
}
method 的取值为 totp、email 或 both 之一。若为 email,服务器会通过邮件发送 PIN,email_sent 表示该邮件是否已发出。both 账户使用其身份验证器验证码(otp)登录;对这类账户而言,邮件 PIN 不是登录因素。
401,TOTP 验证码错误或过期时同样如此。邮件 PIN 错误或过期时也会返回 401,且提示信息与密码错误时相同。若组织要求单点登录,其成员会收到 403,必须通过 SSO 登录(参见 SSO 常见问题)。会话轮换
没有单独的刷新端点。服务器会为您轮换 session_token Cookie:当会话临近过期时,GET /api/auth/me 会在返回个人资料的同时附带新的 Set-Cookie。只要客户端保留 Cookie 存储并定期调用 GET /api/auth/me,即可无限期保持登录状态,无需任何额外操作。
当前用户
返回已验证用户的个人资料;若没有有效会话,则返回 { "data": null }(对匿名访问友好,绝不会返回 401)。
端点: GET /api/auth/me
curl -X GET "$BASE_URL/api/auth/me" -b cookies.txt
const { data } = await $fetch('/api/auth/me')
if (data) console.log(data.email, data.is_admin)
me = session.get('$BASE_URL/api/auth/me').json()
resp, _ := client.Get("$BASE_URL/api/auth/me")
$ch = curl_init('$BASE_URL/api/auth/me');
curl_setopt($ch, CURLOPT_COOKIEFILE, 'cookies.txt');
$response = curl_exec($ch);
let me = client.get("$BASE_URL/api/auth/me").send().await?;
响应:
{
"data": {
"id": "uuid",
"email": "user@example.com",
"first_name": "Jane",
"last_name": "Doe",
"role": "uuid",
"tfa_method": "none",
"is_admin": false
}
}
登出
终止当前 Cookie 对应的会话,并将该 Cookie 从浏览器中清除。
端点: POST /api/auth/logout
curl -X POST "$BASE_URL/api/auth/logout" -b cookies.txt -c cookies.txt
await $fetch('/api/auth/logout', { method: 'POST' })
session.post('$BASE_URL/api/auth/logout')
req, _ := http.NewRequest("POST", "$BASE_URL/api/auth/logout", nil)
client.Do(req)
$ch = curl_init('$BASE_URL/api/auth/logout');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_COOKIEFILE, 'cookies.txt');
curl_exec($ch);
client.post("$BASE_URL/api/auth/logout").send().await?;
响应:
{
"success": true
}
安全最佳实践
- 仅使用 HTTPS:会话 Cookie 带有
Secure属性;所有请求都必须使用 TLS 1.2+。 - 切勿通过 JavaScript 读取 Cookie:它在设计上即为
HttpOnly,请保持这一设置。 - 主动刷新:对于长时间打开的标签页,请在会话过期前调用
GET /api/auth/me;服务器会为您轮换 Cookie。 - 登出以吊销会话:
POST /api/auth/logout会立即在服务器端终止会话。 - 存储凭据而非会话:服务器间集成应将登录凭据保存在密钥管理器中并重新建立会话,切勿将 Cookie 持久化到源代码中。