Files
server-core/app/jwt.py
T
Pine 26fa546f67 feat: 核心服务端基础框架(身份/平台 API)
- 七端口 RBAC、select-identity、JWT、审计
- FastAPI + SQLAlchemy + SQLite,/auth /opc /admin /agents 等路由
2026-08-23 22:35:59 +08:00

74 lines
2.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
"""JWT 访问令牌:签发与验签。
- 无状态验签(HS256),claims 携带角色/组织/数据范围/权限。
- 撤销由 ``sessions`` 表(按 jti+ ``users.token_version`` 兜底,
``get_current_user`` 每请求一次 DB 读校验。
"""
from __future__ import annotations
import uuid
from datetime import datetime, timedelta, timezone
import jwt as pyjwt
from . import config
JTI_TTL_SECONDS = config.JWT_ACCESS_EXPIRY_SECONDS
def _now_ts() -> int:
return int(datetime.now(timezone.utc).timestamp())
def create_access_token(
user: dict,
*,
permissions: list[str],
scope_region_ids: list[str],
scope_level: str | None,
expiry_seconds: int,
identity_id: str | None = None,
) -> tuple[str, str, str]:
"""签发 JWT,返回 (token, jti, expires_at_iso)。
``user`` 为来自 UserRepository 的用户 dict(含 id/username/role/
sub_role/org_id/region_id/token_version)。``identity_id`` 非空时表示
该令牌绑定到指定端口身份(get_current_user 据此解析当前身份)。
"""
jti = str(uuid.uuid4())
now = _now_ts()
exp_ts = now + (expiry_seconds if expiry_seconds else config.JWT_ACCESS_EXPIRY_SECONDS)
payload = {
"sub": user["id"],
"username": user.get("username", ""),
"role": user.get("role", "opc_member"),
"sub_role": user.get("sub_role"),
"org_id": user.get("org_id"),
"region_id": user.get("region_id"),
"identity_id": identity_id,
"scope_level": scope_level,
"scope_region_ids": scope_region_ids,
"perms": permissions,
"ver": user.get("token_version", 0),
"jti": jti,
"iat": now,
"exp": exp_ts,
}
token = pyjwt.encode(payload, config.JWT_SECRET, algorithm=config.JWT_ALGORITHM)
expires_at = datetime.fromtimestamp(exp_ts, tz=timezone.utc).isoformat()
return token, jti, expires_at
def decode_access_token(token: str) -> dict | None:
"""验签并返回 claims;无效/过期返回 None。"""
try:
payload = pyjwt.decode(
token,
config.JWT_SECRET,
algorithms=[config.JWT_ALGORITHM],
)
except pyjwt.PyJWTError:
return None
return payload