{{wikiTitle}}
添加鉴权说明
复制链接
编辑文档
CRMEB PRO 添加鉴权说明
概述
CRMEB PRO 使用多层权限认证机制,包括 JWT Token 认证、角色权限控制、API 访问权限等。本文档详述权限认证机制的实现和使用方法。
认证体系架构
请求进入
│
▼
┌─────────────────────┐
│ AllowOriginMiddleware │ 跨域处理
└──────────┬──────────┘
│
▼
┌─────────────────────┐
│ AuthTokenMiddleware │ Token验证
└──────────┬──────────┘
│
▼
┌─────────────────────┐
│ CkeckRoleMiddleware │ 权限检查
└──────────┬──────────┘
│
▼
┌─────────────────────┐
│ Controller │ 业务处理
└─────────────────────┘
JWT Token 认证
Token 生成
// app/services/BaseServices.php
public function createToken(int $id, $type, string $pwd = '')
{
/** @var JwtAuth $jwtAuth */
$jwtAuth = app()->make(JwtAuth::class);
return $jwtAuth->createToken($id, $type, ['auth' => md5($pwd)]);
}
Token 类
// crmeb/utils/JwtAuth.php
namespace crmeb\utils;
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
class JwtAuth
{
/**
* 创建Token
* @param int $id 用户ID
* @param string $type 类型(user/admin/kefu等)
* @param array $params 附加参数
* @return array
*/
public function createToken(int $id, string $type, array $params = []): array
{
$host = app()->request->host();
$time = time();
// Token有效期(秒)
$exp = $this->getExpTime($type);
$payload = [
'iss' => $host, // 签发者
'aud' => $host, // 接收者
'iat' => $time, // 签发时间
'nbf' => $time, // 生效时间
'exp' => $time + $exp, // 过期时间
'jti' => [
'id' => $id,
'type' => $type,
],
];
$payload = array_merge($payload, $params);
$token = JWT::encode($payload, $this->getSecretKey(), 'HS256');
return [
'token' => $token,
'exp' => $exp,
];
}
/**
* 解析Token
* @param string $token
* @return object
*/
public function parseToken(string $token): object
{
return JWT::decode($token, new Key($this->getSecretKey(), 'HS256'));
}
/**
* 获取密钥
* @return string
*/
protected function getSecretKey(): string
{
return config('app.app_key') ?: 'crmeb_default_key';
}
}
前端API认证
中间件实现
// app/http/middleware/api/AuthTokenMiddleware.php
namespace app\http\middleware\api;
use app\Request;
use app\services\user\UserAuthServices;
use crmeb\exceptions\AuthException;
use crmeb\interfaces\MiddlewareInterface;
class AuthTokenMiddleware implements MiddlewareInterface
{
/**
* 处理请求
* @param Request $request
* @param \Closure $next
* @param bool $force 是否强制验证
* @return mixed
*/
public function handle(Request $request, \Closure $next, bool $force = true)
{
$authInfo = null;
// 获取Token
$token = trim(ltrim($request->header('Authori-zation'), 'Bearer'));
if (!$token) {
$token = trim(ltrim($request->header('Authorization'), 'Bearer'));
}
try {
/** @var UserAuthServices $service */
$service = app()->make(UserAuthServices::class);
$authInfo = $service->parseToken($token);
} catch (AuthException $e) {
// 强制验证时抛出异常
if ($force) {
return app('json')->make($e->getCode(), $e->getMessage());
}
}
// 设置用户信息到请求对象
if (!is_null($authInfo)) {
$request->user = function (string $key = null) use (&$authInfo) {
if ($key) {
return $authInfo['user'][$key] ?? '';
}
return $authInfo['user'];
};
$request->tokenData = $authInfo['tokenData'];
}
$request->isLogin = !is_null($authInfo);
$request->uid = is_null($authInfo) ? 0 : (int)$authInfo['user']->uid;
return $next($request);
}
}
Token解析服务
// app/services/user/UserAuthServices.php
namespace app\services\user;
use app\services\BaseServices;
use crmeb\exceptions\AuthException;
use crmeb\utils\JwtAuth;
class UserAuthServices extends BaseServices
{
/**
* 解析Token
* @param string $token
* @return array
* @throws AuthException
*/
public function parseToken(string $token): array
{
if (!$token) {
throw new AuthException('请登录', 410000);
}
/** @var JwtAuth $jwtAuth */
$jwtAuth = app()->make(JwtAuth::class);
try {
$tokenData = $jwtAuth->parseToken($token);
} catch (\Exception $e) {
throw new AuthException('登录已过期,请重新登录', 410001);
}
// 验证Token类型
if ($tokenData->jti->type !== 'user') {
throw new AuthException('Token类型错误', 410002);
}
// 获取用户信息
$uid = $tokenData->jti->id;
$user = $this->getUserInfo($uid);
if (!$user) {
throw new AuthException('用户不存在', 410003);
}
if (!$user['status']) {
throw new AuthException('用户已被禁用', 410004);
}
return [
'user' => $user,
'tokenData' => $tokenData,
];
}
}
后台管理认证
后台Token中间件
// app/http/middleware/admin/AdminAuthTokenMiddleware.php
namespace app\http\middleware\admin;
use app\Request;
use app\services\system\admin\SystemAdminAuthServices;
use crmeb\exceptions\AuthException;
use crmeb\interfaces\MiddlewareInterface;
class AdminAuthTokenMiddleware implements MiddlewareInterface
{
public function handle(Request $request, \Closure $next)
{
$token = trim(ltrim($request->header('Authori-zation'), 'Bearer'));
/** @var SystemAdminAuthServices $authServices */
$authServices = app()->make(SystemAdminAuthServices::class);
try {
$adminInfo = $authServices->parseToken($token);
} catch (AuthException $e) {
return app('json')->make($e->getCode(), $e->getMessage());
}
// 设置管理员信息
$request->adminId = $adminInfo['id'];
$request->adminInfo = $adminInfo;
return $next($request);
}
}
角色权限检查
// app/http/middleware/admin/AdminCkeckRoleMiddleware.php
namespace app\http\middleware\admin;
use app\Request;
use app\services\system\admin\SystemRoleServices;
use crmeb\exceptions\AuthException;
use crmeb\interfaces\MiddlewareInterface;
class AdminCkeckRoleMiddleware implements MiddlewareInterface
{
public function handle(Request $request, \Closure $next)
{
$adminInfo = $request->adminInfo;
// 超级管理员跳过权限检查
if ($adminInfo['level'] == 0) {
return $next($request);
}
// 获取当前请求的路由
$rule = $this->getCurrentRule($request);
// 检查是否有权限
/** @var SystemRoleServices $roleServices */
$roleServices = app()->make(SystemRoleServices::class);
if (!$roleServices->hasPermission($adminInfo['roles'], $rule)) {
throw new AuthException('您没有权限访问该接口', 403);
}
return $next($request);
}
/**
* 获取当前请求的权限标识
* @param Request $request
* @return string
*/
protected function getCurrentRule(Request $request): string
{
$rule = $request->rule()->getOption('unique_auth');
if (!$rule) {
$rule = $request->controller() . '/' . $request->action();
}
return strtolower($rule);
}
}
角色权限管理
角色服务
// app/services/system/admin/SystemRoleServices.php
namespace app\services\system\admin;
use app\services\BaseServices;
use app\dao\system\admin\SystemRoleDao;
class SystemRoleServices extends BaseServices
{
/**
* 检查是否有权限
* @param string $roles 角色ID(逗号分隔)
* @param string $rule 权限标识
* @return bool
*/
public function hasPermission(string $roles, string $rule): bool
{
if (empty($roles)) {
return false;
}
$roleIds = explode(',', $roles);
// 获取角色拥有的所有权限
$permissions = $this->getRolePermissions($roleIds);
return in_array($rule, $permissions);
}
/**
* 获取角色权限列表
* @param array $roleIds
* @return array
*/
public function getRolePermissions(array $roleIds): array
{
$roles = $this->dao->getListByIds($roleIds);
$permissions = [];
foreach ($roles as $role) {
$rules = explode(',', $role['rules']);
$permissions = array_merge($permissions, $rules);
}
return array_unique($permissions);
}
/**
* 保存角色
* @param array $data
* @return bool
*/
public function saveRole(array $data): bool
{
return $this->dao->save([
'role_name' => $data['role_name'],
'rules' => implode(',', $data['rules']),
'level' => $data['level'] ?? 1,
'status' => $data['status'] ?? 1,
]) ? true : false;
}
}
菜单权限服务
// app/services/system/admin/SystemMenusServices.php
namespace app\services\system\admin;
use app\services\BaseServices;
class SystemMenusServices extends BaseServices
{
/**
* 获取用户可访问的菜单
* @param array $adminInfo
* @return array
*/
public function getMenusByAdmin(array $adminInfo): array
{
// 超级管理员获取所有菜单
if ($adminInfo['level'] == 0) {
return $this->getAllMenus();
}
// 根据角色获取菜单
$roleIds = explode(',', $adminInfo['roles']);
return $this->getMenusByRoles($roleIds);
}
/**
* 根据角色获取菜单
* @param array $roleIds
* @return array
*/
protected function getMenusByRoles(array $roleIds): array
{
/** @var SystemRoleServices $roleServices */
$roleServices = app()->make(SystemRoleServices::class);
$menuIds = $roleServices->getRoleMenuIds($roleIds);
return $this->dao->getMenusByIds($menuIds);
}
}
路由权限配置
在路由中配置权限
// route/admin.php
// 方式1:使用 unique_auth 配置权限标识
Route::get('user/list', 'v1.user.User/index')
->option([
'real_name' => '用户列表',
'unique_auth' => 'admin-user-index'
]);
// 方式2:资源路由自动生成权限
Route::resource('user', 'v1.user.User')->option(['real_name' => [
'index' => '获取用户列表',
'read' => '获取用户详情',
'create' => '获取创建用户表单',
'save' => '保存用户',
'edit' => '获取编辑用户表单',
'update' => '更新用户',
'delete' => '删除用户'
]]);
权限配置文件
// config/auth.php
return [
// 不需要登录的接口
'no_login' => [
'adminapi/login',
'adminapi/captcha',
'adminapi/ajcaptcha',
],
// 不需要权限验证的接口
'no_check' => [
'adminapi/home/header',
'adminapi/menus',
'adminapi/logout',
],
// 权限规则
'auth_rule' => [
'admin-user' => [
'admin-user-index',
'admin-user-create',
'admin-user-edit',
'admin-user-delete',
],
// ... 更多权限组
],
];
添加新的权限
Step 1: 添加菜单
在后台管理系统中:
- 进入 设置 → 权限管理 → 菜单管理
- 点击 添加菜单
- 填写菜单信息
Step 2: 配置路由权限
// 在路由中添加权限标识
Route::get('my/action', 'v1.my.MyController/action')
->option([
'real_name' => '我的操作',
'unique_auth' => 'admin-my-action'
]);
Step 3: 分配角色权限
在后台管理系统中:
- 进入 设置 → 权限管理 → 角色管理
- 编辑角色
- 勾选新添加的权限
自定义认证逻辑
创建自定义中间件
<?php
// app/http/middleware/CustomAuthMiddleware.php
namespace app\http\middleware;
use app\Request;
use crmeb\interfaces\MiddlewareInterface;
class CustomAuthMiddleware implements MiddlewareInterface
{
public function handle(Request $request, \Closure $next)
{
// 自定义认证逻辑
$apiKey = $request->header('X-Api-Key');
if (!$this->validateApiKey($apiKey)) {
return app('json')->fail('API Key无效', 401);
}
return $next($request);
}
protected function validateApiKey(string $apiKey): bool
{
// 验证API Key
return !empty($apiKey) && strlen($apiKey) === 32;
}
}
使用自定义中间件
// route/api.php
Route::group('custom', function () {
Route::get('data', 'v1.custom.Custom/getData');
})->middleware(CustomAuthMiddleware::class);
前端Token管理
存储Token
// 登录成功后存储Token
login(params).then(res => {
const { token, expires_time } = res.data
// 存储到本地
uni.setStorageSync('token', token)
uni.setStorageSync('expires_time', expires_time)
})
请求拦截器
// 请求拦截器添加Token
request.interceptors.request.use(config => {
const token = uni.getStorageSync('token')
if (token) {
config.header['Authori-zation'] = 'Bearer ' + token
}
return config
})
// 响应拦截器处理Token过期
request.interceptors.response.use(response => {
if (response.data.code === 410000 || response.data.code === 410001) {
// Token过期,跳转登录
uni.removeStorageSync('token')
uni.navigateTo({ url: '/pages/users/login/index' })
}
return response
})
安全建议
- Token安全: 使用HTTPS传输,定期刷新Token
- 密码安全: 使用bcrypt加密存储密码
- 权限最小化: 只分配必要的权限
- 日志记录: 记录所有敏感操作的日志
- 防重放攻击: 关键操作添加时间戳和签名验证
注意事项
- Token 请求头差异:后台管理使用
Authori-zation(带连字符),前端 API 使用Authorization,开发时注意区分 - Token 有效期:Token 有过期时间限制,前端需要处理 Token 过期后的刷新或重新登录逻辑
- 权限标识唯一性:
unique_auth权限标识必须全局唯一,建议使用模块-功能-操作的命名格式 - 角色权限缓存:修改角色权限后需要清除缓存或要求用户重新登录才能生效
- 多端登录控制:系统支持配置是否允许多端同时登录,配置在后台系统设置中
- 敏感接口保护:涉及资金、密码修改等敏感操作的接口,建议额外验证支付密码或短信验证码
- 跨域认证:前后端分离项目中,确保跨域请求时 Token 能正确传递
- JWT 密钥安全:JWT 签名密钥不要硬编码在代码中,应使用环境变量配置
常见问题
Q: 登录成功后请求接口仍提示未登录?
A: 检查以下几点:1) Token 是否正确存储;2) 请求头字段名是否正确(注意Authori-zation的写法);3) Token 格式是否带Bearer前缀Q: 如何实现 Token 无感刷新?
A: 在响应拦截器中检测 Token 即将过期,调用刷新接口获取新 Token,更新本地存储后重试原请求Q: 角色权限修改后为什么不生效?
A: 权限数据有缓存,需要清除缓存或让用户重新登录。可以在后台调用AdminAuthServices::clearCache()清除Q: 如何获取当前登录用户信息?
A: 在控制器中通过$this->request->uid()获取用户 ID,通过$this->request->user()获取完整用户信息Q: 如何设置某些接口不需要登录即可访问?
A: 将路由放在不带认证中间件的分组中,或在AuthTokenMiddleware中配置白名单路由Q: 用户被禁用后如何立即生效?
A: 禁用用户后应清除该用户的 Token 缓存,可以通过JwtAuth::invalidate()方法使 Token 失效
评论({{cateWiki.comment_num}})
最新
最早
{{cateWiki.page_view_num}}人看过该文档
评论(0)
最新
最早
777人看过
登录/注册
即可发表评论
{{item.user ? item.user.nickname : ''}}
(自评)
{{item.content}}
搜索结果
为您找到{{wikiCount}}条结果
{{item.page_view_num}}
{{item.like ? item.like.like_num : 0}}
{{item.comment ? item.comment.comment_num : 0}}
位置:
{{path.name}}
{{(i+1) == getCataloguePathData(item.catalogue).length ? '':'/'}}
