16 changed files with 326 additions and 375 deletions
@ -1,90 +1,79 @@ |
|||||
<?php |
<?php |
||||
|
|
||||
namespace app\api\controller; |
namespace app\api\controller; |
||||
|
|
||||
|
use app\api\middleware\LcJWTAuth; |
||||
|
use app\api\service\LcJWTService; |
||||
use app\api\service\UserService; |
use app\api\service\UserService; |
||||
use think\Controller; |
use think\Controller; |
||||
|
use think\exception\ValidateException; |
||||
use think\response\Json; |
use think\response\Json; |
||||
|
|
||||
/** |
/** |
||||
* 用户账户控制器 |
* 用户账户控制器 |
||||
* 功能:MVC 中的C,处理用户输入和反馈给用户 |
* 功能:MVC 中的C,处理用户输入和反馈给用户 |
||||
*/ |
*/ |
||||
class Passport extends ApiController{ |
class Passport extends ApiController |
||||
|
{ |
||||
|
|
||||
|
protected $middleware |
||||
|
= [ |
||||
|
LcJWTAuth::class => [ |
||||
|
'except' => ['login', 'register'] |
||||
|
] |
||||
|
]; |
||||
|
|
||||
/** |
/** |
||||
* 登录 |
* 登录 |
||||
*/ |
*/ |
||||
public function login():Json |
public function login(): Json |
||||
{ |
{ |
||||
if (!$this->request->isPost()) { |
if (!$this->request->isPost()) { |
||||
return $this->renderError('不支持GET请求'); |
return $this->renderError('不支持GET请求'); |
||||
} |
} |
||||
$data = $this->postData(); |
$data = $this->postData(); |
||||
|
try { |
||||
|
validate()->rule([ |
||||
|
'uname|用户名' => 'require', |
||||
|
'upass|密码' => 'require', |
||||
|
])->check($data); |
||||
|
} catch (ValidateException $v) { |
||||
|
return $this->renderError($v->getMessage()); |
||||
|
} |
||||
$model = new UserService; |
$model = new UserService; |
||||
// |
if (($userInfo = $model->login($data['uname'], $data['upass'])) === false) { |
||||
$data = $this->postData(); |
|
||||
if (($userInfo = $model->login($data['uname'],$data['upass'])) === false) { |
|
||||
return $this->renderError($model->getError() ?: '登录失败'); |
return $this->renderError($model->getError() ?: '登录失败'); |
||||
} |
} |
||||
return $this->renderSuccess([ |
return $this->renderSuccess([ |
||||
'userId' => $userInfo['uid'], |
'userId' => $userInfo['uid'], |
||||
'token' => $model->getToken($userInfo['uid'],$userInfo['nick_name']) |
'token' => LcJWTService::createToken($userInfo['uid'], $data['uname']) |
||||
], ''); |
], '登录成功'); |
||||
} |
} |
||||
|
|
||||
/** |
/** |
||||
* 用户注册 |
* 用户注册 |
||||
* @return Json |
* @return Json |
||||
*/ |
*/ |
||||
public function register():Json |
public function register(): Json |
||||
{ |
{ |
||||
if (!$this->request->isPost()) { |
if (!$this->request->isPost()) { |
||||
return $this->renderError('不支持GET请求'); |
return $this->renderError('不支持GET请求'); |
||||
} |
} |
||||
|
$post = $this->postData(); |
||||
|
try { |
||||
|
validate()->rule([ |
||||
|
'uname|用户名' => 'require', |
||||
|
'upass|密码' => 'require', |
||||
|
'qr_upass|确认密码' => 'require|confirm:upass', |
||||
|
'phone|手机号' => 'require|mobile' |
||||
|
])->check($post); |
||||
|
} catch (ValidateException $v) { |
||||
|
return $this->renderError($v->getMessage()); |
||||
|
} |
||||
$model = new UserService; |
$model = new UserService; |
||||
if (($userInfo = $model->register($this->postData())) === false) { |
if (($userInfo = $model->register($post)) === false) { |
||||
return $this->renderError($model->getError() ?: '注册失败'); |
return $this->renderError($model->getError() ?: '注册失败'); |
||||
} |
} |
||||
return $this->renderSuccess("注册成功"); |
return $this->renderSuccess("注册成功"); |
||||
} |
} |
||||
|
} |
||||
} |
|
||||
|
|
||||
// app/controller/Index.php |
|
||||
// namespace app\controller; |
|
||||
|
|
||||
// use app\service\JWTService; |
|
||||
// use think\Request; |
|
||||
|
|
||||
// class IndexController |
|
||||
// { |
|
||||
// protected $jwtService; |
|
||||
|
|
||||
// public function __construct(JWTService $jwtService) |
|
||||
// { |
|
||||
// $this->jwtService = $jwtService; |
|
||||
// } |
|
||||
|
|
||||
// public function login(Request $request) |
|
||||
// { |
|
||||
// $username = $request->param('username'); |
|
||||
// $password = $request->param('password'); |
|
||||
|
|
||||
// // 假设验证用户名和密码成功 |
|
||||
// $claims = ['id' => 1, 'username' => $username]; |
|
||||
|
|
||||
// $token = $this->jwtService->createToken($claims); |
|
||||
|
|
||||
// return json(['token' => $token]); |
|
||||
// } |
|
||||
|
|
||||
// public function protectedRoute(Request $request) |
|
||||
// { |
|
||||
// $claims = $request->attributes->get('claims'); |
|
||||
|
|
||||
// if (empty($claims)) { |
|
||||
// return json(['error' => 'Unauthorized'], 401); |
|
||||
// } |
|
||||
|
|
||||
// return json(['message' => 'Welcome, ' . $claims['username']]); |
|
||||
// } |
|
||||
// } |
|
||||
@ -0,0 +1,32 @@ |
|||||
|
<?php |
||||
|
|
||||
|
namespace app\api\controller; |
||||
|
|
||||
|
use app\admin\model\User as UserModel; |
||||
|
use app\api\middleware\LcJWTAuth; |
||||
|
use think\response\Json; |
||||
|
|
||||
|
class User extends ApiController |
||||
|
{ |
||||
|
|
||||
|
public function getUserData():Json |
||||
|
{ |
||||
|
if (!$this->request->isPost()) { |
||||
|
return $this->renderError('不支持GET请求'); |
||||
|
} |
||||
|
$data = $this->postData(); |
||||
|
$model = new UserModel(); |
||||
|
$field = 'password,salt,last_login_time,create_time,update_time,delete_time'; |
||||
|
$row = $model->where('uid', $data['uid'])->withoutField($field)->find(); |
||||
|
if ($row->isEmpty()) { |
||||
|
return $this->renderError($model->getError() ?: '用户不存在'); |
||||
|
} |
||||
|
if ($row['status'] != 1) return $this->renderError('用户被禁用'); |
||||
|
unset($row['status']); |
||||
|
if ($row['gender']) { |
||||
|
$row['gender'] = $model->genderArr[$row['gender']] ?? ''; |
||||
|
} |
||||
|
return $this->renderSuccess($row->toArray()); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -1,40 +0,0 @@ |
|||||
<?php |
|
||||
namespace app\api\middleware; |
|
||||
|
|
||||
use app\Request; |
|
||||
use CsrfVerify\drive\ThinkphpCache; |
|
||||
use CsrfVerify\entity\CsrfVerifyEntity; |
|
||||
use CsrfVerify\interfaces\CsrfVerifyInterface; |
|
||||
use think\facade\Session; |
|
||||
|
|
||||
class CsrfMiddleware |
|
||||
{ |
|
||||
use \app\common\traits\JumpTrait; |
|
||||
|
|
||||
public function handle(Request $request, \Closure $next) |
|
||||
{ |
|
||||
if (env('EASYADMIN.IS_CSRF', true)) { |
|
||||
if (!in_array($request->method(), ['GET', 'HEAD', 'OPTIONS'])) { |
|
||||
|
|
||||
// 跨域校验 |
|
||||
$refererUrl = $request->header('REFERER', null); |
|
||||
$refererInfo = parse_url($refererUrl); |
|
||||
$host = $request->host(true); |
|
||||
if (!isset($refererInfo['host']) || $refererInfo['host'] != $host) { |
|
||||
$this->error('当前请求不合法!'); |
|
||||
} |
|
||||
|
|
||||
// CSRF校验 |
|
||||
$ckCsrfToken = $request->post('ckCsrfToken', null); |
|
||||
$data = !empty($ckCsrfToken) ? ['__token__' => $ckCsrfToken] : []; |
|
||||
|
|
||||
$check = $request->checkToken('__token__', $data); |
|
||||
if (!$check) { |
|
||||
$this->error('请求验证失败,请重新刷新页面!'); |
|
||||
} |
|
||||
|
|
||||
} |
|
||||
} |
|
||||
return $next($request); |
|
||||
} |
|
||||
} |
|
||||
@ -1,36 +0,0 @@ |
|||||
<?php |
|
||||
namespace app\api\middleware; |
|
||||
|
|
||||
use app\api\service\JWTService; |
|
||||
use think\Exception; |
|
||||
use think\Request; |
|
||||
|
|
||||
class JWTAuth |
|
||||
{ |
|
||||
protected $jwtService; |
|
||||
|
|
||||
public function __construct(JWTService $jwtService) |
|
||||
{ |
|
||||
$this->jwtService = $jwtService; |
|
||||
} |
|
||||
|
|
||||
public function handle(Request $request, \Closure $next) |
|
||||
{ |
|
||||
$token = $request->header('Authorization'); |
|
||||
|
|
||||
if (!$token) { |
|
||||
throw new Exception('Missing token'); |
|
||||
} |
|
||||
|
|
||||
$claims = $this->jwtService->verifyToken($token); |
|
||||
|
|
||||
if (empty($claims)) { |
|
||||
throw new Exception('Invalid token'); |
|
||||
} |
|
||||
|
|
||||
// 将 claims 存储到 request 对象中 |
|
||||
$request->attributes->set('claims', $claims); |
|
||||
|
|
||||
return $next($request); |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,29 @@ |
|||||
|
<?php |
||||
|
|
||||
|
namespace app\api\middleware; |
||||
|
|
||||
|
use app\api\service\LcJWTService; |
||||
|
use app\Request; |
||||
|
|
||||
|
class LcJWTAuth |
||||
|
{ |
||||
|
public function handle(Request $request, \Closure $next) |
||||
|
{ |
||||
|
$token = $request->header('token'); |
||||
|
|
||||
|
if (!$token) { |
||||
|
return json(['code' => 401, 'msg' => 'token cannot be empty']); |
||||
|
} |
||||
|
|
||||
|
$data = LcJWTService::parseToken($token); |
||||
|
$claims = $data['claims']; |
||||
|
|
||||
|
$validateRes = LcJWTService::validationToken($token, $claims->get('uid')); |
||||
|
|
||||
|
if ($validateRes !== true) { |
||||
|
return json(['code' => 401, 'msg' => 'token verification failed']); |
||||
|
} |
||||
|
|
||||
|
return $next($request); |
||||
|
} |
||||
|
} |
||||
@ -1,147 +0,0 @@ |
|||||
<?php |
|
||||
namespace app\api\service; |
|
||||
|
|
||||
|
|
||||
use Lcobucci\JWT\Builder; |
|
||||
use Lcobucci\JWT\Signer\Hmac\Sha256; |
|
||||
use Lcobucci\JWT\Signer\Key\InMemory; |
|
||||
use Lcobucci\JWT\Token; |
|
||||
use DateTimeImmutable; |
|
||||
|
|
||||
class JWTService |
|
||||
{ |
|
||||
private $secret; |
|
||||
|
|
||||
public function __construct() |
|
||||
{ |
|
||||
$this->secret = config('jwt.secret'); |
|
||||
} |
|
||||
|
|
||||
public function createToken(array $claims): string |
|
||||
{ |
|
||||
$signer = new Sha256(); |
|
||||
$key = InMemory::plainText($this->secret); |
|
||||
|
|
||||
$now = new DateTimeImmutable(); // 当前时间 |
|
||||
|
|
||||
$builder = new Builder(); |
|
||||
|
|
||||
// 设置发行时间和过期时间 |
|
||||
$secondsToAdd = (int) config('jwt.token_ttl'); |
|
||||
$expiresAt = $now->add(new \DateInterval('PT' . $secondsToAdd . 'S')); |
|
||||
|
|
||||
$token = $builder |
|
||||
->issuedAt($now) // iat: 发行时间 |
|
||||
->expiresAt($expiresAt) // exp: 过期时间 |
|
||||
|
|
||||
// 使用专用方法设置注册声明 |
|
||||
->withIssuer('your_issuer') // iss: 发行人 |
|
||||
->withSubject('your_subject') // sub: 主题 |
|
||||
->withAudience('your_audience'); // aud: 受众 |
|
||||
|
|
||||
// 添加自定义 Claims |
|
||||
foreach ($claims as $key => $value) { |
|
||||
$token = $token->withClaim($key, $value); // 自定义声明仍然可以使用 withClaim |
|
||||
} |
|
||||
|
|
||||
// 构建并签名 Token |
|
||||
$signedToken = $token->sign($signer, $key); |
|
||||
|
|
||||
return (string) $signedToken; |
|
||||
} |
|
||||
|
|
||||
public function verifyToken(string $token): array |
|
||||
{ |
|
||||
try { |
|
||||
$parser = new \Lcobucci\JWT\Parser(); |
|
||||
$token = $parser->parse($token); |
|
||||
|
|
||||
if ($token->verify(new Sha256(), InMemory::plainText($this->secret))) { |
|
||||
return $token->getClaims(); |
|
||||
} |
|
||||
} catch (\Exception $e) { |
|
||||
// 处理异常 |
|
||||
} |
|
||||
|
|
||||
return []; |
|
||||
} |
|
||||
} |
|
||||
// app/service/JWTService.php |
|
||||
namespace app\service; |
|
||||
|
|
||||
use Lcobucci\JWT\Builder; |
|
||||
use Lcobucci\JWT\Signer\Hmac\Sha256; |
|
||||
use Lcobucci\JWT\Signer\Key\InMemory; |
|
||||
use Lcobucci\JWT\Token; |
|
||||
use DateTimeImmutable; |
|
||||
|
|
||||
class JWTService |
|
||||
{ |
|
||||
private $secret; |
|
||||
|
|
||||
public function __construct() |
|
||||
{ |
|
||||
$this->secret = config('jwt.secret'); |
|
||||
} |
|
||||
|
|
||||
public function createToken(array $claims): string |
|
||||
{ |
|
||||
$signer = new Sha256(); |
|
||||
$key = InMemory::plainText($this->secret); |
|
||||
|
|
||||
$now = new DateTimeImmutable(); // 当前时间 |
|
||||
|
|
||||
$builder = new Builder(); |
|
||||
|
|
||||
// 设置发行时间和过期时间 |
|
||||
$secondsToAdd = (int) config('jwt.token_ttl'); |
|
||||
$expiresAt = $now->add(new \DateInterval('PT' . $secondsToAdd . 'S')); |
|
||||
|
|
||||
$token = $builder |
|
||||
->issuedAt($now) // iat: 发行时间 |
|
||||
->expiresAt($expiresAt) // exp: 过期时间 |
|
||||
|
|
||||
// 使用辅助方法设置注册声明 |
|
||||
->withRegisteredClaim('iss', 'your_issuer') // iss: 发行人 |
|
||||
->withRegisteredClaim('sub', 'your_subject') // sub: 主题 |
|
||||
->withRegisteredClaim('aud', 'your_audience'); // aud: 受众 |
|
||||
|
|
||||
// 添加自定义 Claims |
|
||||
foreach ($claims as $key => $value) { |
|
||||
$token = $token->withClaim($key, $value); // 自定义声明仍然可以使用 withClaim |
|
||||
} |
|
||||
|
|
||||
// 构建并签名 Token |
|
||||
$signedToken = $token->sign($signer, $key); |
|
||||
|
|
||||
return (string) $signedToken; |
|
||||
} |
|
||||
|
|
||||
public function verifyToken(string $token): array |
|
||||
{ |
|
||||
try { |
|
||||
$parser = new \Lcobucci\JWT\Parser(); |
|
||||
$token = $parser->parse($token); |
|
||||
|
|
||||
if ($token->verify(new Sha256(), InMemory::plainText($this->secret))) { |
|
||||
return $token->getClaims(); |
|
||||
} |
|
||||
} catch (\Exception $e) { |
|
||||
// 处理异常 |
|
||||
} |
|
||||
|
|
||||
return []; |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* 辅助方法用于设置注册声明。 |
|
||||
* |
|
||||
* @param string $claimName |
|
||||
* @param mixed $claimValue |
|
||||
* @return \Lcobucci\JWT\Builder |
|
||||
*/ |
|
||||
private function withRegisteredClaim(string $claimName, $claimValue): Builder |
|
||||
{ |
|
||||
return (new Builder())->withClaim($claimName, $claimValue); |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,128 @@ |
|||||
|
<?php |
||||
|
|
||||
|
namespace app\api\service; |
||||
|
|
||||
|
use Lcobucci\JWT\Configuration; |
||||
|
use Lcobucci\JWT\Signer\Hmac\Sha256; |
||||
|
use Lcobucci\JWT\Signer\Key\InMemory; |
||||
|
use DateTimeImmutable; |
||||
|
use Lcobucci\JWT\Token\Plain; |
||||
|
use Lcobucci\JWT\Validation\RequiredConstraintsViolated; |
||||
|
use Lcobucci\JWT\Validation\Constraint\SignedWith; |
||||
|
|
||||
|
class LcJWTService |
||||
|
{ |
||||
|
protected static $url = 'https://douyin.xingtongworld.com/'; |
||||
|
/** |
||||
|
* 配置秘钥加密 |
||||
|
* @return Configuration |
||||
|
*/ |
||||
|
public static function getConfig() |
||||
|
{ |
||||
|
$configuration = Configuration::forSymmetricSigner( |
||||
|
// You may use any HMAC variations (256, 384, and 512) |
||||
|
new Sha256(), |
||||
|
// replace the value below with a key of your own! |
||||
|
InMemory::base64Encoded(config('jwt.secret')) |
||||
|
// You may also override the JOSE encoder/decoder if needed by providing extra arguments here |
||||
|
); |
||||
|
return $configuration; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 签发令牌 |
||||
|
*/ |
||||
|
public static function createToken(int $userId,string $uname) |
||||
|
{ |
||||
|
$config = self::getConfig(); |
||||
|
assert($config instanceof Configuration); |
||||
|
|
||||
|
$now = new DateTimeImmutable(); |
||||
|
|
||||
|
$token = $config->builder() |
||||
|
// 签发人 |
||||
|
->issuedBy(self::$url) |
||||
|
// 受众 |
||||
|
->permittedFor(self::$url) |
||||
|
// JWT ID 编号 唯一标识 |
||||
|
->identifiedBy($userId) |
||||
|
// 签发时间 |
||||
|
->issuedAt($now) |
||||
|
// 在1分钟后才可使用 |
||||
|
// ->canOnlyBeUsedAfter($now->modify('+1 minute')) |
||||
|
// 过期时间1小时 |
||||
|
->expiresAt($now->modify('+1 hour')) |
||||
|
// 自定义uid 额外参数 |
||||
|
->withClaim('uid', $userId) |
||||
|
->withClaim('name', $uname) |
||||
|
// 自定义header 参数 |
||||
|
->withHeader('foo', 'bar') |
||||
|
// 生成token |
||||
|
->getToken($config->signer(), $config->signingKey()); |
||||
|
|
||||
|
return $token->toString(); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 解析令牌 |
||||
|
*/ |
||||
|
public static function parseToken(string $token) |
||||
|
{ |
||||
|
$config = self::getConfig(); |
||||
|
assert($config instanceof Configuration); |
||||
|
|
||||
|
$token = $config->parser()->parse($token); |
||||
|
|
||||
|
assert($token instanceof Plain); |
||||
|
|
||||
|
return [ |
||||
|
'header' => $token->headers(),// Retrieves the token headers |
||||
|
'claims' => $token->claims()// Retrieves the token claims |
||||
|
]; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 验证令牌 |
||||
|
*/ |
||||
|
public static function validationToken(string $token, int $userId) |
||||
|
{ |
||||
|
$config = self::getConfig(); |
||||
|
assert($config instanceof Configuration); |
||||
|
|
||||
|
$token = $config->parser()->parse($token); |
||||
|
assert($token instanceof Plain); |
||||
|
|
||||
|
//Lcobucci\JWT\Validation\Constraint\IdentifiedBy: 验证jwt id是否匹配 |
||||
|
//Lcobucci\JWT\Validation\Constraint\IssuedBy: 验证签发人参数是否匹配 |
||||
|
//Lcobucci\JWT\Validation\Constraint\PermittedFor: 验证受众人参数是否匹配 |
||||
|
//Lcobucci\JWT\Validation\Constraint\RelatedTo: 验证自定义cliam参数是否匹配 |
||||
|
//Lcobucci\JWT\Validation\Constraint\SignedWith: 验证令牌是否已使用预期的签名者和密钥签名 |
||||
|
//Lcobucci\JWT\Validation\Constraint\ValidAt: 验证要求iat,nbf和exp(支持余地配置) |
||||
|
|
||||
|
//验证jwt id是否匹配 |
||||
|
$validate_jwt_id = new \Lcobucci\JWT\Validation\Constraint\IdentifiedBy($userId); |
||||
|
$config->setValidationConstraints($validate_jwt_id); |
||||
|
//验证签发人url是否正确 |
||||
|
$validate_issued = new \Lcobucci\JWT\Validation\Constraint\IssuedBy(self::$url); |
||||
|
$config->setValidationConstraints($validate_issued); |
||||
|
//验证客户端url是否匹配 |
||||
|
$validate_aud = new \Lcobucci\JWT\Validation\Constraint\PermittedFor(self::$url); |
||||
|
$config->setValidationConstraints($validate_aud); |
||||
|
|
||||
|
//验证是否过期 |
||||
|
$timezone = new \DateTimeZone('Asia/Shanghai'); |
||||
|
$now = new \Lcobucci\Clock\SystemClock($timezone); |
||||
|
$validate_jwt_at = new \Lcobucci\JWT\Validation\Constraint\ValidAt($now); |
||||
|
$config->setValidationConstraints($validate_jwt_at); |
||||
|
|
||||
|
$constraints = $config->validationConstraints(); |
||||
|
|
||||
|
try { |
||||
|
$config->validator()->assert($token, ...$constraints); |
||||
|
return true; |
||||
|
} catch (RequiredConstraintsViolated $e) { |
||||
|
// list of constraints violation exceptions: |
||||
|
return $e->getMessage(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue