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();
}