9.用户认证与授权
在 Web 应用开发中,用户认证与授权是保障系统安全的核心环节。无论构建的是简单的博客系统还是复杂的企业级应用,都需要一套可靠的机制来验证用户身份并控制资源访问权限。
Phalcon 提供了完整的安全组件,让我们能够轻松实现从基础登录验证到复杂角色权限管理的全流程解决方案。本章将深入探讨这些安全机制的实现细节和最佳实践。
身份验证实现
身份验证本质上是确认用户声称身份的过程。在 Web 应用中,这通常涉及用户名密码验证、会话管理等机制。Phalcon 的安全组件提供了密码哈希、会话处理等工具,帮助我们构建安全的认证系统。
密码安全存储
将密码以明文形式存储在数据库中是最严重的安全隐患之一。一旦数据库被攻破,所有用户的账号信息将直接泄露。Phalcon 的 Security 组件提供了安全的密码哈希功能,默认使用 bcrypt 算法,这是目前业界推荐的密码加密方案。
<?php
use Phalcon\Encryption\Security;
// 初始化安全组件
$security = new Security();
// 哈希密码
$password = 'user_password_from_form';
$hashedPassword = $security->hash($password);
// 存储$hashedPassword到数据库
// ...
// 验证密码
$userInputPassword = 'password_from_login_form';
$storedHashedPassword = 'password_from_database';
if ($security->checkHash($userInputPassword, $storedHashedPassword)) {
// 密码验证成功
} else {
// 密码验证失败
}
这段代码展示了密码哈希和验证的基本流程。hash() 方法会自动生成随机盐值并应用适当的计算强度,而 checkHash() 方法则负责验证用户输入的密码与存储的哈希值是否匹配。
Phalcon 还支持更先进的 Argon2i 算法,这是密码哈希竞赛的获胜算法,提供了更高的安全性:
<?php
use Phalcon\Encryption\Security;
$security = new Security();
// 设置默认哈希算法为Argon2i
$security->setDefaultHash(Security::CRYPT_ARGON2I);
// 使用Argon2i哈希密码
$hashedPassword = $security->hash(
'user_password',
[
'memory_cost' => 102400, // 内存成本
'time_cost' => 2, // 时间成本
'threads' => 2 // 线程数
]
);
Argon2i 允许我们调整内存成本、时间成本和线程数等参数,以便在安全性和性能之间取得平衡。内存成本越高、时间成本越大,暴力破解的难度就越大,但同时也会增加服务器的计算负担。
登录功能实现
结合密码哈希,我们可以实现完整的用户登录功能。典型的登录流程包括接收用户提交的凭证、验证凭证有效性、创建会话等步骤。
<?php
use Phalcon\Mvc\Controller;
use Phalcon\Http\Request;
use Phalcon\Encryption\Security;
use MyApp\Models\Users;
class SessionController extends Controller
{
/**
* @var Request
*/
protected $request;
/**
* @var Security
*/
protected $security;
public function initialize()
{
// 从依赖注入容器获取服务
$this->request = $this->di->get('request');
$this->security = $this->di->get('security');
}
public function loginAction()
{
// 检查请求是否为POST
if ($this->request->isPost()) {
// 获取用户输入
$username = $this->request->getPost('username', 'string');
$password = $this->request->getPost('password', 'string');
// 查找用户
$user = Users::findFirst([
'conditions' => 'username = :username:',
'bind' => ['username' => $username]
]);
// 防止时序攻击:无论用户是否存在,都执行哈希计算
if (!$user) {
// 使用随机字符串进行哈希计算
$this->security->hash(rand());
$this->flash->error('用户名或密码不正确');
return;
}
// 验证密码
if ($this->security->checkHash($password, $user->password)) {
// 密码验证成功,创建会话
$this->session->set('userId', $user->id);
$this->session->set('username', $user->username);
$this->session->set('role', $user->role);
// 生成新的会话ID以防止会话固定攻击
$this->session->regenerateId();
$this->flash->success('登录成功');
return $this->response->redirect('dashboard');
}
$this->flash->error('用户名或密码不正确');
}
}
public function logoutAction()
{
// 销毁会话
$this->session->destroy();
$this->flash->success('已成功登出');
return $this->response->redirect('login');
}
}
这段代码实现了一个安全的登录系统,包含以下关键安全特性:
- 使用 Phalcon 的安全组件进行密码哈希和验证
- 采用模糊错误消息,不区分"用户不存在"和"密码错误"
- 防止时序攻击,无论用户是否存在都执行相同时间的哈希计算
- 登录成功后生成新的会话 ID,防止会话固定攻击
- 正确处理会话销毁
会话安全配置
会话管理是身份验证系统的重要组成部分。Phalcon 的 Session 组件提供了灵活的会话管理功能,我们需要正确配置以确保会话安全。
<?php
use Phalcon\Session\Manager;
use Phalcon\Session\Adapter\Stream;
// 配置会话
$session = new Manager();
$files = new Stream([
'savePath' => '/tmp/sessions', // 会话文件存储路径
]);
$session->setAdapter($files);
// 启动会话
$session->start();
// 在依赖注入容器中注册会话服务
$di->setShared('session', $session);
为了增强会话安全性,还需要在 PHP 配置中设置以下参数:
// 在php.ini中或通过ini_set()设置
ini_set('session.cookie_secure', 1); // 仅通过HTTPS传输cookie
ini_set('session.cookie_httponly', 1); // 防止JavaScript访问cookie
ini_set('session.cookie_samesite', 'Lax'); // 限制跨站请求
ini_set('session.use_strict_mode', 1); // 启用严格模式
ini_set('session.gc_maxlifetime', 1440); // 会话过期时间(秒)
这些配置可以有效防止会话劫持、XSS 攻击等安全威胁。在生产环境中,还应该考虑使用 Redis 等分布式会话存储方案,而不是默认的文件存储。
ACL 权限控制
认证解决了"你是谁"的问题,而授权则解决了"你能做什么"的问题。访问控制列表(ACL)是实现授权的常用机制,Phalcon 提供了功能完善的 Acl 组件来管理权限。
ACL 基础概念
Phalcon 的 ACL 系统基于两个核心概念:角色(Role)和资源(Resource)。角色代表用户或用户组,资源代表受保护的对象或功能,ACL 定义了角色对资源的访问权限。
Phalcon ACL 采用默认拒绝策略,即除非明确允许,否则所有访问都将被拒绝。这种策略遵循安全原则中的"最小权限"原则,有助于构建更安全的系统。
创建 ACL 组件
下面是创建和配置 ACL 的基本示例:
<?php
use Phalcon\Acl\Adapter\Memory as AclMemory;
use Phalcon\Acl\Role;
use Phalcon\Acl\Component;
use Phalcon\Acl\Enum;
// 创建ACL实例
$acl = new AclMemory();
// 设置默认访问级别为拒绝
$acl->setDefaultAction(Enum::DENY);
// 添加角色
$acl->addRole(new Role('guest'));
$acl->addRole(new Role('user', 'guest')); // 继承guest角色
$acl->addRole(new Role('admin', 'user')); // 继承user角色
// 添加资源
$acl->addComponent(
new Component('index'),
['index', 'about', 'contact']
);
$acl->addComponent(
new Component('user'),
['profile', 'edit', 'password', 'list', 'delete']
);
$acl->addComponent(
new Component('admin'),
['dashboard', 'settings', 'users', 'logs']
);
// 设置访问规则
// 游客可以访问首页、关于和联系页面
$acl->allow('guest', 'index', ['index', 'about', 'contact']);
// 普通用户可以访问自己的资料和修改密码
$acl->allow('user', 'user', ['profile', 'edit', 'password']);
// 管理员可以访问所有用户相关功能和管理后台
$acl->allow('admin', 'user', '*'); // 允许所有操作
$acl->allow('admin', 'admin', '*');
这段代码创建了一个包含三个角色(guest、user、admin)和三个资源(index、user、admin)的 ACL 系统。角色之间可以继承权限,例如 admin 角色继承了 user 角色的所有权限。
检查权限
创建 ACL 后,我们可以在应用中检查用户是否有权限执行某个操作:
<?php
// 获取当前用户角色
$userRole = $this->session->get('role', 'guest');
// 检查权限
$controller = $this->dispatcher->getControllerName();
$action = $this->dispatcher->getActionName();
if (!$acl->isAllowed($userRole, $controller, $action)) {
// 没有权限,重定向到403页面
$this->flash->error('您没有权限访问此页面');
return $this->response->redirect('error/403');
}
通常,我们会在事件管理器中注册一个钩子,在每次请求前自动检查权限:
<?php
use Phalcon\Events\Event;
use Phalcon\Mvc\Dispatcher;
use Phalcon\Mvc\User\Plugin;
class SecurityPlugin extends Plugin
{
public function beforeDispatch(Event $event, Dispatcher $dispatcher)
{
// 获取当前用户角色
$userRole = $this->session->get('role', 'guest');
// 获取当前请求的控制器和动作
$controller = $dispatcher->getControllerName();
$action = $dispatcher->getActionName();
// 检查权限
if (!$this->acl->isAllowed($userRole, $controller, $action)) {
// 根据用户角色重定向
if ($userRole === 'guest') {
// 未登录用户重定向到登录页
$dispatcher->forward([
'controller' => 'session',
'action' => 'login'
]);
} else {
// 已登录用户但无权限,重定向到403页面
$dispatcher->forward([
'controller' => 'error',
'action' => '403'
]);
}
return false;
}
}
}
// 在应用中注册插件
$di->set('eventsManager', function () use ($di) {
$eventsManager = new \Phalcon\Events\Manager();
$securityPlugin = new SecurityPlugin();
$eventsManager->attach('dispatch', $securityPlugin);
return $eventsManager;
});
这种方式可以集中管理权限检查,确保所有请求都经过权限验证。
基于角色的访问控制
在复杂应用中,用户可能属于多个组,每个组有不同的权限。基于角色的访问控制(RBAC)是一种更灵活的权限管理模型,Phalcon 的 ACL 组件可以轻松实现 RBAC。
RBAC 模型设计
典型的 RBAC 模型包含以下实体:
- 用户(User)
- 角色(Role)
- 权限(Permission)
- 资源(Resource)
用户和角色是多对多关系,角色和权限也是多对多关系。这种设计允许一个用户拥有多个角色,一个角色拥有多个权限。
数据库设计
实现 RBAC 需要设计相应的数据库表结构:
erDiagram
USERS ||--o{ USER_ROLES : has
ROLES ||--o{ USER_ROLES : assigned_to
ROLES ||--o{ ROLE_PERMISSIONS : has
PERMISSIONS ||--o{ ROLE_PERMISSIONS : assigned_to
PERMISSIONS }|--|| RESOURCES : belongs_to
USERS {
int id PK
string username
string password
string email
datetime created_at
datetime updated_at
}
ROLES {
int id PK
string name
string description
datetime created_at
datetime updated_at
}
USER_ROLES {
int user_id PK,FK
int role_id PK,FK
}
PERMISSIONS {
int id PK
string name
string description
int resource_id FK
string action
datetime created_at
datetime updated_at
}
RESOURCES {
int id PK
string name
string description
datetime created_at
datetime updated_at
}
ROLE_PERMISSIONS {
int role_id PK,FK
int permission_id PK,FK
}
这个 ER 图展示了 RBAC 模型的数据库结构,包含用户、角色、权限和资源四个核心表,以及它们之间的关联表。
从数据库加载 ACL
在实际应用中,我们需要从数据库动态加载 ACL 规则,而不是在代码中硬编码:
<?php
use Phalcon\Acl\Adapter\Memory as AclMemory;
use Phalcon\Acl\Role;
use Phalcon\Acl\Component;
use Phalcon\Acl\Enum;
use MyApp\Models\Roles;
use MyApp\Models\Permissions;
class AclService
{
protected $acl;
protected $db;
public function __construct($db)
{
$this->db = $db;
$this->acl = new AclMemory();
$this->acl->setDefaultAction(Enum::DENY);
$this->initialize();
}
protected function initialize()
{
// 加载角色
$this->loadRoles();
// 加载资源和权限
$this->loadResourcesAndPermissions();
// 加载角色权限
$this->loadRolePermissions();
}
protected function loadRoles()
{
$roles = Roles::find();
foreach ($roles as $role) {
$this->acl->addRole(new Role($role->name, $role->description));
}
}
protected function loadResourcesAndPermissions()
{
$permissions = Permissions::find([
'columns' => 'DISTINCT resource_id, resource.name as resource_name, action',
'joins' => 'INNER JOIN resources ON permissions.resource_id = resources.id'
]);
$resources = [];
foreach ($permissions as $permission) {
$resourceName = $permission->resource_name;
if (!isset($resources[$resourceName])) {
$resources[$resourceName] = [];
$this->acl->addComponent(new Component($resourceName), []);
}
$resources[$resourceName][] = $permission->action;
}
// 为每个资源添加操作
foreach ($resources as $resourceName => $actions) {
$this->acl->addComponent(new Component($resourceName), $actions);
}
}
protected function loadRolePermissions()
{
$rolePermissions = $this->db->fetchAll(
'SELECT r.name as role_name, p.action, res.name as resource_name
FROM roles r
INNER JOIN role_permissions rp ON r.id = rp.role_id
INNER JOIN permissions p ON rp.permission_id = p.id
INNER JOIN resources res ON p.resource_id = res.id',
\Phalcon\Db\Enum::FETCH_ASSOC
);
foreach ($rolePermissions as $rp) {
$this->acl->allow($rp['role_name'], $rp['resource_name'], $rp['action']);
}
}
public function getAcl()
{
return $this->acl;
}
}
这个服务类从数据库加载角色、资源和权限信息,动态构建 ACL。这样我们可以通过管理界面而不是修改代码来调整权限配置。
在应用中集成 RBAC
为了在应用中使用 RBAC,我们需要在用户登录时加载其角色,并在权限检查时使用这些角色:
<?php
// 用户登录时加载角色
public function loginAction()
{
// ... 前面的登录验证代码 ...
if ($this->security->checkHash($password, $user->password)) {
// 获取用户角色
$roles = $user->getRoles();
$userRoles = [];
foreach ($roles as $role) {
$userRoles[] = $role->name;
}
// 存储用户ID、用户名和角色到会话
$this->session->set('userId', $user->id);
$this->session->set('username', $user->username);
$this->session->set('roles', $userRoles);
// ... 后续登录处理 ...
}
}
然后修改权限检查逻辑,支持多角色:
<?php
// 检查多个角色的权限
public function hasPermission($controller, $action)
{
$userRoles = $this->session->get('roles', ['guest']);
foreach ($userRoles as $role) {
if ($this->acl->isAllowed($role, $controller, $action)) {
return true;
}
}
return false;
}
这种方式允许用户拥有多个角色,只要其中一个角色有权限,就允许访问资源。
权限缓存
在大型应用中,ACL 规则可能非常复杂,每次请求都从数据库加载 ACL 会影响性能。Phalcon 提供了缓存组件,可以将 ACL 对象缓存起来,提高应用性能。
缓存 ACL 对象
我们可以修改前面的 AclService 类,添加缓存功能:
<?php
use Phalcon\Cache\Cache;
use Phalcon\Storage\SerializerFactory;
use Phalcon\Cache\AdapterFactory;
class AclService
{
protected $acl;
protected $db;
protected $cache;
protected $cacheKey = 'app_acl';
protected $cacheTtl = 86400; // 缓存时间,单位:秒
public function __construct($db, $cache)
{
$this->db = $db;
$this->cache = $cache;
$this->acl = $this->loadAclFromCache();
if (!$this->acl) {
$this->acl = new AclMemory();
$this->acl->setDefaultAction(Enum::DENY);
$this->initialize();
$this->saveAclToCache();
}
}
protected function loadAclFromCache()
{
// 尝试从缓存加载ACL
return $this->cache->get($this->cacheKey);
}
protected function saveAclToCache()
{
// 将ACL保存到缓存
$this->cache->set($this->cacheKey, $this->acl, $this->cacheTtl);
}
// ... 其他方法不变 ...
// 当权限发生变化时,清除缓存
public function clearCache()
{
$this->cache->delete($this->cacheKey);
}
}
配置缓存服务
我们需要配置缓存服务,通常使用 Redis 或 Memcached 作为缓存适配器:
<?php
use Phalcon\Cache\AdapterFactory;
use Phalcon\Storage\SerializerFactory;
$serializerFactory = new SerializerFactory();
$adapterFactory = new AdapterFactory($serializerFactory);
$options = [
'defaultSerializer' => 'Php',
'lifetime' => 86400,
'host' => 'localhost',
'port' => 6379,
'index' => 0,
];
$cache = $adapterFactory->newInstance('redis', $options);
// 在依赖注入容器中注册缓存服务
$di->setShared('cache', $cache);
使用 PHP 序列化器可以直接缓存 ACL 对象,但在生产环境中,建议使用更高效的序列化方式如 Msgpack 或 Json。
缓存更新策略
当权限配置发生变化时,需要及时更新缓存。我们可以在权限管理界面添加缓存清除功能:
<?php
class AdminController extends Controller
{
public function updatePermissionAction()
{
// ... 更新权限代码 ...
// 更新成功后清除ACL缓存
$this->aclService->clearCache();
$this->flash->success('权限已更新');
return $this->response->redirect('admin/permissions');
}
}
此外,还可以设置合理的缓存过期时间,确保即使缓存未被主动清除,也会定期更新。对于权限变更不频繁的系统,缓存可以设置较长的过期时间,如 24 小时;对于权限变更频繁的系统,可以缩短缓存时间,如 1 小时。
安全最佳实践
实现认证和授权只是应用安全的开始,我们还需要遵循一系列安全最佳实践,保护应用免受各种攻击。
防止暴力破解
暴力破解是指攻击者尝试大量用户名和密码组合,以获取系统访问权限。我们可以通过以下措施防止暴力破解:
- 实施登录尝试限制
- 使用验证码或其他挑战机制
- 实施账户锁定策略
下面是一个实现登录尝试限制的示例:
<?php
public function loginAction()
{
$username = $this->request->getPost('username', 'string');
$loginAttemptsKey = 'login_attempts_' . $username;
// 获取登录尝试次数
$attempts = $this->cache->get($loginAttemptsKey) ?: 0;
// 如果尝试次数超过5次,显示验证码
if ($attempts >= 5) {
$this->view->enableCaptcha = true;
// 验证验证码
if (!$this->validateCaptcha()) {
$this->flash->error('验证码错误');
return;
}
}
// 验证用户名密码
$user = Users::findFirstByUsername($username);
if ($user && $this->security->checkHash($this->request->getPost('password'), $user->password)) {
// 登录成功,重置尝试次数
$this->cache->delete($loginAttemptsKey);
// ... 后续登录处理 ...
} else {
// 登录失败,增加尝试次数
$attempts++;
$this->cache->set($loginAttemptsKey, $attempts, 3600); // 1小时过期
$remaining = 5 - $attempts;
if ($remaining > 0) {
$this->flash->error('用户名或密码不正确,还有' . $remaining . '次尝试机会');
} else {
$this->flash->error('登录尝试次数过多,请输入验证码');
}
}
}
JWT 认证
对于 API 应用或前后端分离架构,JWT(JSON Web Token)是一种流行的认证方式。Phalcon 提供了 JWT 组件,可以轻松实现基于令牌的认证。
<?php
use Phalcon\Encryption\Security\JWT\Builder;
use Phalcon\Encryption\Security\JWT\Signer\Hmac;
use Phalcon\Encryption\Security\JWT\Token\Parser;
use Phalcon\Encryption\Security\JWT\Validator;
// 生成JWT令牌
public function generateTokenAction()
{
// 验证用户凭据
$username = $this->request->getPost('username');
$password = $this->request->getPost('password');
$user = Users::findFirstByUsername($username);
if (!$user || !$this->security->checkHash($password, $user->password)) {
return $this->response->setJsonContent([
'status' => 'error',
'message' => 'Invalid credentials'
])->setStatusCode(401);
}
// 创建签名器
$signer = new Hmac();
// 创建JWT构建器
$builder = new Builder($signer);
// 设置令牌参数
$now = new DateTimeImmutable();
$passphrase = 'your-secret-key'; // 在生产环境中使用环境变量存储
$tokenObject = $builder
->setIssuer('https://your-app.com')
->setAudience('https://your-app.com')
->setIssuedAt($now->getTimestamp())
->setExpirationTime($now->modify('+1 hour')->getTimestamp())
->setSubject($user->id)
->setPassphrase($passphrase)
->getToken();
// 获取令牌字符串
$token = $tokenObject->getToken();
return $this->response->setJsonContent([
'status' => 'success',
'token' => $token,
'expires_in' => 3600
]);
}
// 验证JWT令牌
public function validateTokenAction()
{
$token = $this->request->getHeader('Authorization');
if (empty($token) || strpos($token, 'Bearer ') !== 0) {
return $this->response->setStatusCode(401, 'Unauthorized');
}
$token = substr($token, 7); // 移除"Bearer "前缀
$signer = new Hmac();
$passphrase = 'your-secret-key';
// 解析令牌
$parser = new Parser();
$tokenObject = $parser->parse($token);
// 验证令牌
$validator = new Validator($tokenObject);
$now = new DateTimeImmutable();
try {
$validator
->validateIssuer('https://your-app.com')
->validateAudience('https://your-app.com')
->validateExpiration($now->getTimestamp())
->validateSignature($signer, $passphrase);
// 令牌验证成功,获取用户ID
$claims = $tokenObject->getClaims()->getPayload();
$userId = $claims['sub'];
// 继续处理请求...
return $this->response->setJsonContent([
'status' => 'success',
'user_id' => $userId
]);
} catch (Exception $e) {
return $this->response->setStatusCode(401, 'Unauthorized');
}
}
使用 JWT 时,需要注意以下几点:
- 密钥安全存储,不要硬编码在代码中
- 设置合理的令牌过期时间
- 使用 HTTPS 传输令牌
- 考虑实现令牌刷新机制
安全 HTTP 头
配置适当的 HTTP 头可以增强应用安全性。Phalcon 的响应对象允许我们轻松设置这些头:
<?php
use Phalcon\Mvc\Controller;
class BaseController extends Controller
{
public function afterExecuteRoute()
{
// 设置安全相关HTTP头
$this->response
->setHeader('X-Content-Type-Options', 'nosniff')
->setHeader('X-Frame-Options', 'DENY')
->setHeader('X-XSS-Protection', '1; mode=block')
->setHeader('Content-Security-Policy', "default-src 'self'")
->setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
return $this->response;
}
}
这些 HTTP 头可以防止 XSS 攻击、点击劫持、MIME 类型嗅探等安全威胁。在生产环境中,应该根据应用需求仔细配置内容安全策略(CSP)。
敏感操作二次验证
对于敏感操作,如修改密码、绑定银行卡等,应该实施二次验证,确保是用户本人操作:
<?php
public function changePasswordAction()
{
// 检查是否已登录
if (!$this->session->has('userId')) {
return $this->response->redirect('session/login');
}
// 检查是否提交了表单
if ($this->request->isPost()) {
$currentPassword = $this->request->getPost('current_password');
$newPassword = $this->request->getPost('new_password');
$confirmPassword = $this->request->getPost('confirm_password');
// 获取当前用户
$user = Users::findFirstById($this->session->get('userId'));
// 验证当前密码
if (!$this->security->checkHash($currentPassword, $user->password)) {
$this->flash->error('当前密码不正确');
return;
}
// 验证新密码
if ($newPassword !== $confirmPassword) {
$this->flash->error('两次输入的新密码不一致');
return;
}
// 更新密码
$user->password = $this->security->hash($newPassword);
if ($user->save()) {
$this->flash->success('密码已成功更新');
// 记录敏感操作日志
$this->logger->info('User ' . $user->username . ' changed password', [
'ip' => $this->request->getClientAddress(),
'user_agent' => $this->request->getUserAgent()
]);
return $this->response->redirect('user/profile');
}
$this->flash->error('密码更新失败: ' . implode(', ', $user->getMessages()));
}
}
对于更敏感的操作,还可以结合短信验证码、邮箱验证等方式进行二次验证,进一步提高安全性。
总结
本章详细介绍了 Phalcon 框架中的用户认证与授权机制,包括身份验证实现、ACL 权限控制、基于角色的访问控制、权限缓存和安全最佳实践。我们学习了如何使用 Phalcon 的安全组件实现密码哈希、会话管理,如何设计和实现灵活的 RBAC 权限模型,以及如何通过缓存提高权限检查性能。
安全是一个持续的过程,而不是一次性的实现。作为开发者,我们需要不断关注最新的安全威胁和防御技术,定期更新和审计应用的安全措施。在实际开发中,应该根据应用的具体需求和安全级别,选择合适的认证和授权方案,并始终遵循安全最佳实践。
下一章我们将学习 Phalcon 的会话与 Cookie 管理,深入了解如何在应用中安全地存储和管理用户状态信息。