PHP code example of pohoc / sa-token
1. Go to this page and download the library: Download pohoc/sa-token library . Choose the download type require .
2. Extract the ZIP file and open the index.php.
3. Add this code to the index.php.
<?php
require_once('vendor/autoload.php');
/* Start to develop here. Best regards https://php-download.com/ */
pohoc / sa-token example snippets
return [
'tokenName' => 'satoken',
'timeout' => 86400,
'activityTimeout' => -1,
'isReadHeader' => true,
'isReadCookie' => true,
'isReadBody' => false,
'isWriteCookie' => true,
'isWriteHeader' => false,
'concurrent' => true,
'isShare' => true,
'maxLoginCount' => 12,
'cryptoType' => 'intl',
];
use SaToken\SaToken;
SaToken::init();
SaToken::init([
'tokenName' => 'my-token',
'timeout' => 7200,
]);
use SaToken\Config\SaTokenConfigBuilder;
$config = (new SaTokenConfigBuilder())
->tokenName('my-token')
->timeout(7200)
->activityTimeout(1800)
->concurrent(true)
->maxLoginCount(12)
->tokenEncrypt(true)
->cryptoType('intl')
->signAlg('sha256')
->tokenFingerprint(true)
->build();
SaToken::init($config);
use SaToken\Util\SaHealthCheck;
use SaToken\Util\SaMetrics;
// 健康检查
$health = SaHealthCheck::checkAll();
// $health = [
// 'dao_connected' => true,
// 'config_valid' => true,
// 'token_test' => true,
// 'overall' => 'healthy'
// ]
// 性能指标收集
// 在任意操作后
SaMetrics::recordLogin(10001, 1.2); // 登录耗时 ms
SaMetrics::recordCheckLogin(0.05); // 鉴权耗时 ms
SaMetrics::recordQuery(0.1); // 查询耗时 ms
SaMetrics::recordDelete(0.08); // 删除操作耗时 ms
// 获取统计数据
$stats = SaMetrics::getStats();
// $stats = [
// 'login_count' => 100,
// 'login_avg_ms' => 1.1,
// 'check_login_count' => 2000,
// 'check_login_avg_ms' => 0.04,
// ...
// ]
// 重置统计
SaMetrics::reset();
// 配置开启
$config = (new SaTokenConfigBuilder())
->tokenFingerprint(true)
->build();
// 登录时自动记录当前 IP + User-Agent
$result = StpUtil::login(10001);
// 访问时自动验证指纹,不匹配则强制重新登录
StpUtil::checkLogin(); // 指纹不匹配时抛出异常
// 拉黑指定 Token,有效期内无法使用
StpUtil::addToBlacklist($tokenValue, 3600);
// 检查是否在黑名单
$isBlacklisted = StpUtil::isInBlacklist($tokenValue);
// 从黑名单移除
StpUtil::removeFromBlacklist($tokenValue);
// 查询所有黑名单 Token
$tokens = StpUtil::getBlacklistTokens();
use SaToken\StpUtil;
$result = StpUtil::login(10001);
$token = $result->getAccessToken();
$param = new \SaToken\SaLoginParameter();
$param->setDeviceType('PC')->setIsLastingCookie(true)->setTimeout(7200);
$result = StpUtil::login(10001, $param);
$token = $result->getAccessToken();
StpUtil::checkLogin();
$isLogin = StpUtil::isLogin();
$loginId = StpUtil::getLoginId();
$loginId = StpUtil::getLoginIdAsNotNull();
$tokenValue = StpUtil::getTokenValue();
$tokenInfo = StpUtil::getTokenInfo();
StpUtil::logout();
StpUtil::logoutByLoginId(10001);
StpUtil::kickoutByTokenValue($tokenValue);
StpUtil::kickout(10001);
use SaToken\Action\SaTokenActionInterface;
use SaToken\SaToken;
class MyAction implements SaTokenActionInterface
{
public function getPermissionList(mixed $loginId, string $loginType): array
{
return ['user:add', 'user:delete', 'user:update'];
}
public function getRoleList(mixed $loginId, string $loginType): array
{
return ['admin', 'super-admin'];
}
public function generateTokenValue(mixed $loginId, string $loginType): ?string
{
return null;
}
}
SaToken::setAction(new MyAction());
StpUtil::checkPermission('user:add');
StpUtil::checkPermissionAnd(['user:add', 'user:delete']);
StpUtil::checkPermissionOr(['user:add', 'user:delete']);
$has = StpUtil::hasPermission('user:add');
StpUtil::checkRole('admin');
StpUtil::checkRoleAnd(['admin', 'super-admin']);
StpUtil::checkRoleOr(['admin', 'super-admin']);
$has = StpUtil::hasRole('admin');
use SaToken\SaRouter;
use SaToken\StpUtil;
SaRouter::match('/user/**')->check(fn() => StpUtil::checkLogin());
SaRouter::match('/admin/**', '/system/**')->check(fn() => StpUtil::checkRole('admin'));
SaRouter::notMatch('/public/**')->match('**')->check(fn() => StpUtil::checkLogin());
SaRouter::match('/api/**')->check(fn() => StpUtil::checkLogin())->stop();
StpUtil::disable(10001, 'comment');
StpUtil::disable(10001, 'comment', 3, 86400);
$isDisable = StpUtil::isDisable(10001, 'comment');
StpUtil::checkDisable(10001, 'comment');
$level = StpUtil::getDisableLevel(10001, 'comment');
StpUtil::untieDisable(10001, 'comment');
StpUtil::openSafe(120);
StpUtil::openSafe(300, 'payment');
StpUtil::checkSafe();
$isSafe = StpUtil::isSafe();
StpUtil::checkSafe('payment');
StpUtil::closeSafe();
StpUtil::closeSafe('payment');
StpUtil::switchTo(20002);
$isSwitch = StpUtil::isSwitch();
StpUtil::endSwitch();
$session = StpUtil::getSession();
$session = StpUtil::getSessionByLoginId(10001);
$tokenSession = StpUtil::getTokenSession();
$session->set('name', '张三');
$name = $session->get('name');
$name = $session->get('age', 18);
$has = $session->has('name');
$session->delete('name');
$session->update(['key1' => 'v1', 'key2' => 'v2']);
$data = $session->getDataMap();
$session->clear();
$session->destroy();
$timeout = StpUtil::getTokenTimeout();
StpUtil::renewTimeout(3600);
$tempToken = StpUtil::createTempToken(10001, 600);
$device = StpUtil::getLoginDeviceType();
$terminals = StpUtil::getTerminalListByLoginId(10001);
SaToken::init([
'timeout' => 7200, // AccessToken 有效期 2 小时
'refreshToken' => true, // 启用 RefreshToken
'refreshTokenTimeout' => 2592000, // RefreshToken 有效期 30 天
'refreshTokenRotation' => true, // 刷新时轮换 RefreshToken
]);
$result = StpUtil::login(10001);
$accessToken = $result->getAccessToken();
$refreshToken = $result->getRefreshToken(); // refreshToken=true 时有值
$accessExpire = $result->getAccessExpire();
$refreshExpire = $result->getRefreshExpire();
$refreshToken = StpUtil::createRefreshToken($accessToken);
$result = StpUtil::refreshToken($refreshToken);
$newAccessToken = $result->getAccessToken();
$newRefreshToken = $result->getRefreshToken(); // rotation=true 时有值
StpUtil::revokeRefreshToken($refreshToken);
StpUtil::revokeRefreshTokenByAccessToken($accessToken);
$isValid = StpUtil::isRefreshTokenValid($refreshToken);
$refreshToken = StpUtil::getRefreshTokenByAccessToken($accessToken);
use SaToken\StpLogic;
use SaToken\SaToken;
$adminLogic = new StpLogic('admin');
SaToken::registerStpLogic($adminLogic);
$adminLogic = SaToken::getStpLogic('admin');
$adminLogic->login(10001);
$adminLogic->checkLogin();
$adminLogic->logout();
use SaToken\TokenManager;
$tokens = TokenManager::searchTokenValue('keyword', 0, 10);
$sessionIds = TokenManager::searchSessionId('keyword', 0, 10);
$tokenSessionIds = TokenManager::searchTokenSessionId('keyword', 0, 10);
class MyAction implements SaTokenActionInterface
{
public function generateTokenValue(mixed $loginId, string $loginType): ?string
{
return 'custom-' . $loginId . '-' . bin2hex(random_bytes(16));
}
}
SaToken::init([
'tokenEncrypt' => true,
'tokenEncryptKey' => 'your-secret-key-at-least-32-bytes',
]);
use SaToken\Plugin\SaTokenJwt;
$jwt = new SaTokenJwt('your-secret-key');
$token = $jwt->createToken(10001, 'login');
$payload = $jwt->parseToken($token);
$token = $jwt->createToken(10001, 'login', null, [
'role' => 'admin',
'dept' => 'IT',
]);
$extra = $jwt->getExtraClaims($token);
SaToken::init([
'jwtSecretKey' => 'your-secret-key',
'jwtStateless' => true,
]);
$token = StpUtil::loginStateless(10001);
$jwt = new SaTokenJwt('your-secret-key', 'sm');
$token = $jwt->createToken(10001, 'login');
SaToken::init([
'sso' => [
'loginUrl' => 'https://sso.example.com/login',
'authUrl' => 'https://sso.example.com/auth',
'backUrl' => 'https://app.example.com/callback',
'checkTicketUrl' => 'https://sso.example.com/checkTicket',
'sloUrl' => 'https://sso.example.com/logout',
'mode' => 'cross-domain',
'clientId' => 'your-client-id',
'clientSecret' => 'your-client-secret',
'allowDomains' => ['*.example.com', 'app.mycompany.cn'],
'checkState' => true, // 强制校验回调 state 防 CSRF(默认 true,不推荐关闭)
'crossRedis' => false, // 认证中心与客户端是否跨 Redis 部署
'crossRedisCheckUrl' => '', // 跨 Redis 模式的 check-ticket 端点,必须 HTTPS(localhost 豁免)
],
]);
use SaToken\Sso\SaSsoManager;
$sso = SaToken::getSsoManager();
$loginUrl = $sso->buildLoginUrl();
// $ticket、$redirect、$state 均来自回调请求参数;
// $state 会与 buildLoginUrl 写入的 state Cookie 比对校验(checkState 默认开启,缺失/不匹配直接抛异常;
// 确需关闭可在 SSO 配置中设置 'checkState' => false,不推荐)
$loginId = $sso->doLoginCallback($ticket, $redirect, $state);
// buildLoginUrl 只有一个参数,携带当前 URL 请通过模式处理器调用
$loginUrl = $sso->getModeHandler()->buildLoginUrl(null, $currentUrl);
$result = $sso->getModeHandler()->doLoginCallbackWithRedirect($ticket);
$loginId = $result['loginId'];
$redirect = $result['redirect'];
// 认证中心侧:生成带签名的注销回调参数
$params = $sso->buildSloCallbackParams($loginId);
// 客户端侧:处理注销回调
$sso->doSloCallback($loginId, $params);
SaToken::init([
// 签发 id_token(OpenID Connect)必需
'jwtSecretKey' => 'your-jwt-secret-key',
'oauth2' => [
'grantTypes' => ['authorization_code', 'password'],
'codeTimeout' => 60,
'accessTokenTimeout' => 7200,
'refreshTokenTimeout' => 2592000,
'isNewRefreshToken' => false,
'openIdMode' => true,
'issuer' => 'https://auth.example.com',
],
]);
use SaToken\OAuth2\Data\SaOAuth2Client;
use SaToken\OAuth2\SaOAuth2Manager;
$oauth2 = SaToken::getOAuth2Manager();
$oauth2->registerClient(new SaOAuth2Client([
'clientId' => 'your-client-id',
'clientSecret' => 'your-client-secret',
'redirectUris' => ['https://app.example.com/callback'],
'grantTypes' => ['authorization_code', 'refresh_token'],
'scopes' => ['user:read', 'user:write'],
]));
// 注意:换 token 必须传与授权请求一致的 redirect_uri;
// 请求 scope 不得超出注册白名单;grant_type 必须在客户端注册列表中
$code = $oauth2->generateAuthorizationCode($clientId, $loginId, $redirectUri, $scope);
$accessToken = $oauth2->exchangeTokenByCode($code, $clientId, $clientSecret, $redirectUri);
// 第 5/6 参数为 codeChallenge/codeChallengeMethod,仅支持 S256,challenge 长度 43-128 字符
$code = $oauth2->generateAuthorizationCode($clientId, $loginId, $redirectUri, $scope, $codeChallenge, 'S256');
// 第 5 参数为 codeVerifier
$accessToken = $oauth2->exchangeTokenByCode($code, $clientId, $clientSecret, $redirectUri, $codeVerifier);
$accessToken = $oauth2->exchangeTokenByCode($code, $clientId, $clientSecret, $redirectUri);
$idToken = $accessToken->getIdToken();
$oauth2->checkScopeAndThrow($accessTokenValue, 'user:read');
$hasScope = $oauth2->hasScope($accessTokenValue, 'user:write');
use SaToken\SaToken;
$auth = SaToken::getHttpAuth();
$auth->setBasicValidator(function (string $username, string $password): mixed {
if ($username === 'admin' && $password === '123456') {
return 10001;
}
return null;
});
$auth->checkBasic('My Realm');
$auth->setDigestValidator(function (string $username): ?string {
$users = ['admin' => md5('admin:My Realm:123456')];
return $users[$username] ?? null;
});
$auth->checkDigest('My Realm');
use SaToken\SaToken;
$sign = SaToken::getSign();
$params = ['userId' => '10001', 'action' => 'query'];
$signed = $sign->signParams($params);
$isValid = $sign->verifySign($signed);
$signed = $sign->signParams($params, 'POST', '/api/order');
$isValid = $sign->verifySign($signed, 'POST', '/api/order');
$sign->setSignAlg('sha256');
use SaToken\SaToken;
$apiKey = SaToken::getApiKey();
$apiKey->registerKey('ak-123456', 'sk-abcdef', 10001);
$apiKey->registerKey('ak-789012', 'sk-ghijkl', 10002);
$apiKey->checkApiKey();
$apiKey->setValidator(function (string $apiKey, string $apiSecret): mixed {
$user = Db::table('api_keys')->where('api_key', $apiKey)->first();
if ($user && hash_equals($user->api_secret, $apiSecret)) {
return $user->id;
}
return null;
});
use SaToken\SaToken;
$filter = SaToken::getGlobalFilter();
$filter->setCors([
'allowOrigin' => 'https://example.com',
'allowMethods' => 'GET, POST, PUT, DELETE',
'allowHeaders' => 'Content-Type, Authorization',
'allowCredentials' => 'true',
'maxAge' => '3600',
]);
$filter->addBeforeFilter(function () {
SaRouter::match('/api/**')->check(fn() => StpUtil::checkLogin());
});
$filter->addAfterFilter(function () {
// 日志记录等
});
$filter->execute();
if ($filter->isCorsRequest()) {
$filter->handlePreflight();
return;
}
use SaToken\SaToken;
SaToken::setDao(new \SaToken\Dao\SaTokenDaoMemory());
use SaToken\Dao\SaTokenDaoFile;
use SaToken\SaToken;
SaToken::setDao(new SaTokenDaoFile(['path' => __DIR__ . '/runtime/sa-token']));
use SaToken\Dao\SaTokenDaoRedis;
use SaToken\SaToken;
SaToken::setDao(new SaTokenDaoRedis());
$dao = SaTokenDaoRedis::createWithSeparateRedis(
['host' => '127.0.0.1', 'port' => 6379, 'db' => 0],
['host' => '127.0.0.1', 'port' => 6379, 'db' => 1],
);
SaToken::setDao($dao);
use SaToken\Dao\SaTokenDaoPsr16;
use SaToken\SaToken;
$psr16Cache = new SomePsr16Cache();
SaToken::setDao(new SaTokenDaoPsr16($psr16Cache));
return [
// 文件存储(单机)
'storage' => [
'type' => 'file',
'path' => runtime_path() . '/sa-token', // 数据目录,默认 sys_get_temp_dir()/sa-token
'scanLimit'=> 10000, // search 扫描文件数上限
],
// 或 Redis 存储(分布式部署)
// 'storage' => [
// 'type' => 'redis',
// 'host' => '127.0.0.1',
// 'port' => 6379,
// 'password' => '',
// 'database' => 0,
// 'timeout' => 0,
// ],
];
use SaToken\Dao\SaTokenDaoInterface;
class MyDao implements SaTokenDaoInterface
{
public function get(string $key): ?string { /* ... */ }
public function set(string $key, string $value, ?int $timeout = null): void { /* ... */ }
public function update(string $key, string $value): void { /* ... */ }
public function delete(string $key): void { /* ... */ }
public function exists(string $key): bool { /* ... */ }
public function getTimeout(string $key): int { /* ... */ }
public function expire(string $key, int $timeout): void { /* ... */ }
public function getAndExpire(string $key, int $timeout): ?string { /* ... */ }
public function getAndDelete(string $key): ?string { /* ... */ }
public function size(): int { /* ... */ }
public function search(string $prefix, string $keyword, int $start, int $size): array { /* ... */ }
public function deleteMultiple(array $keys): void { /* ... */ } // 批量删除
public function searchKeys(string $prefix, string $keyword, int $start, int $size): array { /* ... */ } // 仅返回键名列表
public function setIfNotExists(string $key, string $value, ?int $timeout = null): bool { /* ... */ } // 仅当 key 不存在时设置(登录锁/签名防重放依赖)
public function increment(string $key, int $amount = 1, ?int $timeout = null): int { /* ... */ } // 原子计数(防爆破/OTP 依赖)
}
use SaToken\Plugin\SaTokenCrypto;
$hash = SaTokenCrypto::md5('password');
$hash = SaTokenCrypto::sha1('password');
$hash = SaTokenCrypto::sha256('password');
$hmac = SaTokenCrypto::hmacSha256('data', 'key');
$hmac = SaTokenCrypto::hmacSha1('data', 'key');
$bcrypt = SaTokenCrypto::bcryptHash('password', 12);
$valid = SaTokenCrypto::bcryptVerify('password', $bcrypt);
StpUtil::checkAntiBrute('[email protected] ');
StpUtil::recordAntiBruteFailure('[email protected] ');
$isLocked = StpUtil::isAccountLocked('[email protected] ');
$remaining = StpUtil::getRemainingLockTime('[email protected] ');
StpUtil::unlockAccount('[email protected] ');
$info = StpUtil::getAntiBruteInfo('[email protected] ');
// ['failCount' => 3, 'isLocked' => false, 'remainingLockTime' => 0, ...]
SaToken::init([
'antiBruteMaxFailures' => 5,
'antiBruteLockDuration' => 600,
]);
$info = StpUtil::getLoginInfo(10001);
// ['currentIp' => '192.168.1.1', 'lastLoginIp' => '10.0.0.1', 'anomalyCount' => 2, ...]
$count = StpUtil::getAnomalyCount(10001);
$history = StpUtil::getIpHistory(10001);
StpUtil::clearLoginHistory(10001);
SaToken::init([
'ipAnomalyDetection' => true,
'ipAnomalySensitivity' => 3,
]);
$devices = StpUtil::getDeviceList(10001);
$count = StpUtil::getDeviceCount(10001);
StpUtil::kickoutDevice(10001, 'device-id-xxx');
$kickedCount = StpUtil::kickoutAllDevices(10001, $currentToken);
$device = StpUtil::findDevice(10001, 'device-id-xxx');
SaToken::init([
'deviceManagement' => true,
]);
$code = StpUtil::generateOtpCode('payment');
$code = StpUtil::sendOtpCode('payment');
StpUtil::verifyOtpCode('payment', '123456');
$isVerified = StpUtil::isSensitiveVerified('payment');
$remaining = StpUtil::getSensitiveVerifyRemainingAttempts('payment');
StpUtil::clearSensitiveVerify('payment');
$token = StpUtil::openSensitiveVerify('payment', 600);
StpUtil::checkSensitiveVerify('payment', $token);
$logs = StpUtil::getAuditLogs(50);
$log = StpUtil::getAuditLog('log-id-xxx');
SaAuditLog::logLogin(10001);
SaAuditLog::logLogout(10001);
SaAuditLog::logKickout(10001);
SaAuditLog::logDisable(10001, 'login', '违规操作');
SaAuditLog::logSwitchTo(10001, 20002);
$recentLogs = SaAuditLog::getRecentLogs('login', 100);
$logsByIp = SaAuditLog::getLogsByIp('192.168.1.1');
$logsByEvent = SaAuditLog::getLogsByEvent('login');
SaAuditLog::clearLogs();
SaToken::init([
'auditLog' => true,
'auditLogMaxEntries' => 1000,
'auditLogTtlDays' => 30,
]);
use SaToken\Rpc\SaRpcContext;
use SaToken\Rpc\SaRpcInterceptor;
// 发送端:将认证信息注入请求头
$headers = SaRpcContext::attachToHeaders(['X-Custom' => 'value']);
$psr7Request = SaRpcContext::attachToRequest($request);
// 接收端:提取并验证
SaRpcContext::extractAndValidate();
$loginId = SaRpcContext::getForwardedLoginId();
$token = SaRpcContext::getForwardedToken();
$loginType = SaRpcContext::getForwardedLoginType();
$isRpc = SaRpcContext::isRpcRequest();
$interceptor = new SaRpcInterceptor();
$interceptor->setValidateToken(true)
->setAutoLogin(true)
->setLoginType('login');
$interceptor->handleIncoming();
$outHeaders = $interceptor->handleOutgoing();
use SaToken\Listener\SaTokenListenerInterface;
use SaToken\SaToken;
class MyListener implements SaTokenListenerInterface
{
public function onLogin(string $loginType, mixed $loginId, string $tokenValue, array $extra = []): void
{
}
public function onLogout(string $loginType, mixed $loginId, string $tokenValue, array $extra = []): void
{
}
public function onKickout(string $loginType, mixed $loginId, string $tokenValue, array $extra = []): void
{
}
public function onReplaced(string $loginType, mixed $loginId, string $tokenValue, array $extra = []): void
{
}
}
SaToken::addListener(new MyListener());
use SaToken\Exception\NotLoginException;
use SaToken\Exception\NotPermissionException;
try {
StpUtil::checkPermission('user:add');
} catch (NotPermissionException $e) {
echo $e->getPermission();
} catch (NotLoginException $e) {
echo $e->getType();
}