diff --git a/app/admin/controller/user/Admin.php b/app/admin/controller/user/Admin.php index 81f2a48..8a44f5b 100644 --- a/app/admin/controller/user/Admin.php +++ b/app/admin/controller/user/Admin.php @@ -76,6 +76,7 @@ class Admin extends AdminController } $save ? $this->success('保存成功') : $this->error('保存失败'); } + $this->assign('genderArr', $this->model->genderArr); return $this->fetch(); } @@ -103,6 +104,7 @@ class Admin extends AdminController $save ? $this->success('保存成功') : $this->error('保存失败'); } $this->assign('row', $row); + $this->assign('genderArr', $this->model->genderArr); return $this->fetch(); } diff --git a/app/admin/model/User.php b/app/admin/model/User.php index b03b214..e6852bd 100644 --- a/app/admin/model/User.php +++ b/app/admin/model/User.php @@ -11,4 +11,7 @@ class User extends TimeModel protected $defaultSoftDelete = '0'; + public $genderArr = [ + 1 => '男', 2 => '女', 3 => '未知' + ]; } \ No newline at end of file diff --git a/app/admin/view/user/admin/add.html b/app/admin/view/user/admin/add.html index a867f2e..822bfee 100644 --- a/app/admin/view/user/admin/add.html +++ b/app/admin/view/user/admin/add.html @@ -47,9 +47,9 @@
- - - + {foreach $genderArr as $gender => $title} + + {/foreach}
diff --git a/app/admin/view/user/admin/edit.html b/app/admin/view/user/admin/edit.html index 916d2ea..9bb06e0 100644 --- a/app/admin/view/user/admin/edit.html +++ b/app/admin/view/user/admin/edit.html @@ -23,9 +23,9 @@
- - - + {foreach $genderArr as $gender => $title} + + {/foreach}
diff --git a/app/api/controller/ApiController.php b/app/api/controller/ApiController.php index e7c0c81..ba1a05c 100644 --- a/app/api/controller/ApiController.php +++ b/app/api/controller/ApiController.php @@ -1,6 +1,7 @@ post(); // 用户密码 $salt = isset($data['salt'])?$data['salt']:makeSalt(6); - $arr['encpass']=password($data['upass']); + + $arr['encpass']=password($data['upass'].$salt); $arr['salt']=$salt; } else { $arr = ["ver" => "00", "date" => time()]; @@ -59,30 +67,50 @@ class Index extends BaseController // $config = Configuration::forSymmetricSigner($signer,$key); + + //$builder = new Builder(); + // 设置发行时间和过期时间 $now = new DateTimeImmutable(); // 当前时间 + // 设置发行时间和过期时间 + $secondsToAdd = (int) config('jwt.token_ttl'); + $expiresAt = $now->add(new \DateInterval('PT' . $secondsToAdd . 'S')); + + // $token = $builder + // ->issuedAt($now) // iat: 发行时间 + // ->expiresAt($expiresAt) // exp: 过期时间 + // ->withIssuer('iss', 'xtt') // iss: 发行人 + // ->withSubject('sub', 'xtoken') // sub: 主题 + // ->withAudience('aud', 'ttc'); // aud: 受众 + $token = $config->builder() // 签发人 - ->issuedBy('https://douyin.xingtongworld.com/') + ->issuedBy('https://douyin.xingtongworld.com/') // 受众 - ->permittedFor('https://douyin.xingtongworld.com/') + ->permittedFor('https://douyin.xingtongworld.com/') // JWT ID 编号 唯一标识 - ->identifiedBy($claims['id']) + ->identifiedBy($claims['id']) // 签发时间 - ->issuedAt($now) + ->issuedAt($now) // 在1分钟后才可使用 -// ->canOnlyBeUsedAfter($now->modify('+1 minute')) + // ->canOnlyBeUsedAfter($now->modify('+1 minute')) // 过期时间1小时 - ->expiresAt($now->modify('+1 hour')) + ->expiresAt($now->modify('+1 hour')) // 自定义uid 额外参数 - ->withClaim('uid', $claims['id']) - ->withClaim('name',$claims['nick_name']) + ->withClaim('uid', $claims['id']) + ->withClaim('name',$claims['nick_name']) // 自定义header 参数 // ->withHeader('foo', 'bar') // 生成token - ->getToken($config->signer(), $config->signingKey()); + ->getToken($config->signer(), $config->signingKey()); // base64 return $token->toString(); + + // foreach ($claims as $key => $value) { + // $token = $token->withClaim($key, $value); + // } + + // return (string) $token->sign($signer, $key); } } \ No newline at end of file diff --git a/app/api/controller/Passport.php b/app/api/controller/Passport.php index c473e46..c99d2d5 100644 --- a/app/api/controller/Passport.php +++ b/app/api/controller/Passport.php @@ -1,90 +1,79 @@ [ + 'except' => ['login', 'register'] + ] + ]; + /** * 登录 */ - public function login():Json + public function login(): Json { if (!$this->request->isPost()) { return $this->renderError('不支持GET请求'); } $data = $this->postData(); + try { + validate()->rule([ + 'uname|用户名' => 'require', + 'upass|密码' => 'require', + ])->check($data); + } catch (ValidateException $v) { + return $this->renderError($v->getMessage()); + } $model = new UserService; - // - $data = $this->postData(); - if (($userInfo = $model->login($data['uname'],$data['upass'])) === false) { + if (($userInfo = $model->login($data['uname'], $data['upass'])) === false) { return $this->renderError($model->getError() ?: '登录失败'); } return $this->renderSuccess([ 'userId' => $userInfo['uid'], - 'token' => $model->getToken($userInfo['uid'],$userInfo['nick_name']) - ], ''); + 'token' => LcJWTService::createToken($userInfo['uid'], $data['uname']) + ], '登录成功'); } /** * 用户注册 * @return Json */ - public function register():Json + public function register(): Json { if (!$this->request->isPost()) { 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; - if (($userInfo = $model->register($this->postData())) === false) { + if (($userInfo = $model->register($post)) === false) { return $this->renderError($model->getError() ?: '注册失败'); } 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']]); -// } -// } \ No newline at end of file +} \ No newline at end of file diff --git a/app/api/controller/User.php b/app/api/controller/User.php new file mode 100644 index 0000000..e4d852e --- /dev/null +++ b/app/api/controller/User.php @@ -0,0 +1,32 @@ +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()); + } + +} \ No newline at end of file diff --git a/app/api/middleware.php b/app/api/middleware.php index 576c8bd..cc32af2 100644 --- a/app/api/middleware.php +++ b/app/api/middleware.php @@ -8,9 +8,4 @@ return [ // 系统操作日志 // \app\admin\middleware\SystemLog::class, - // Csrf安全校验 -// \app\api\middleware\CsrfMiddleware::class, - // jwt 中间件 - \app\api\middleware\JWTAuth::class, - ]; \ No newline at end of file diff --git a/app/api/middleware/CsrfMiddleware.php b/app/api/middleware/CsrfMiddleware.php deleted file mode 100644 index 2d7e378..0000000 --- a/app/api/middleware/CsrfMiddleware.php +++ /dev/null @@ -1,40 +0,0 @@ -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); - } -} diff --git a/app/api/middleware/JWTAuth.php b/app/api/middleware/JWTAuth.php deleted file mode 100644 index 33be46e..0000000 --- a/app/api/middleware/JWTAuth.php +++ /dev/null @@ -1,36 +0,0 @@ -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); - } -} \ No newline at end of file diff --git a/app/api/middleware/LcJWTAuth.php b/app/api/middleware/LcJWTAuth.php new file mode 100644 index 0000000..dfbfabd --- /dev/null +++ b/app/api/middleware/LcJWTAuth.php @@ -0,0 +1,29 @@ +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); + } +} \ No newline at end of file diff --git a/app/api/service/JWTService.php b/app/api/service/JWTService.php deleted file mode 100644 index d13e54d..0000000 --- a/app/api/service/JWTService.php +++ /dev/null @@ -1,147 +0,0 @@ -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); - } -} \ No newline at end of file diff --git a/app/api/service/LcJWTService.php b/app/api/service/LcJWTService.php new file mode 100644 index 0000000..46dd577 --- /dev/null +++ b/app/api/service/LcJWTService.php @@ -0,0 +1,128 @@ +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(); + } + } +} \ No newline at end of file diff --git a/app/api/service/UserService.php b/app/api/service/UserService.php index 2cb79a9..8fab6cc 100644 --- a/app/api/service/UserService.php +++ b/app/api/service/UserService.php @@ -1,7 +1,11 @@ $uname,'delete_time'=>0])->find(); + $drs = User::where(['nick_name' => $uname, 'delete_time' => 0])->find(); // 异常处理 - if(!isset($drs)){ + if (!isset($drs)) { throwError('用户不存在'); return -1; - }else{ - $fpass = password($pass.$drs['salt']); + } else { + $fpass = password($pass . $drs['salt']); + // var_dump($fpass.'|'.$drs['password']); + // exit; // 对比密码 - if($drs['password'] != $fpass){ + if ($drs['password'] != $fpass) { throwError('密码错误'); return false; } @@ -43,75 +53,27 @@ class UserService { } /** - * @param array $arr + * @param array $data * @return bool */ - public function register(array $arr){ + public function register(array $data): bool + { + $arr = [ + 'nick_name' => $data['uname'], + 'password' => $data['upass'], + 'mobile' => $data['phone'], + ]; $salt = makeSalt(6); // 密码加密 - $arr['password'] = password($arr['password'].$salt); + $arr['password'] = password($arr['password'] . $salt); // 生成salt - $arr['salt'] = $salt; - $dtime =time(); + $arr['salt'] = $salt; + $dtime = time(); $arr['create_time'] = $dtime; $arr['update_time'] = $dtime; // 保存 $model = new User; - $uid = $model->save($arr); - return isset($uid)?true:false; - } - - /** - * 根据uid,nick_name 换取JWT - * @param int $userId - * @param string $uname - * @return string - * @throws \Exception - */ - public function getToken(int $userId,string $uname): string - { - static $token = ''; - if (empty($token)) { - $token = $this->makeToken($userId,$uname); - } - return $token; - } - - /** - * 生成JWT - * @param int $userId - * @param string $uname - * @return string - * @throws \Exception - */ - private function makeToken(int $userId,string $uname): string - { - $signer = new Sha256(); - $key = InMemory::plainText(config('jwt.secret')); - // - $config = Configuration::forSymmetricSigner($signer,$key); - $now = new DateTimeImmutable(); // 当前时间 - // - $token = $config->builder() - // 签发人 - ->issuedBy('https://douyin.xingtongworld.com/') - // 受众 - ->permittedFor('https://douyin.xingtongworld.com/') - // 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(); + $uid = $model->save($arr); + return (bool)$uid; } } \ No newline at end of file diff --git a/composer.json b/composer.json index f1a5d55..451126e 100644 --- a/composer.json +++ b/composer.json @@ -23,13 +23,15 @@ "topthink/think-view": "^1.0", "topthink/think-captcha": "^3.0", "aliyuncs/oss-sdk-php": "^2.3", + "firebase/php-jwt": "6.4", "qcloud/cos-sdk-v5": "^2.0", "qiniu/php-sdk": "^7.2", "alibabacloud/client": "^1.5", "jianyan74/php-excel": "^1.0", "zhongshaofa/easy-admin": "^1.0.2", "ext-json": "*", - "zhongshaofa/thinkphp-log-trace": "^1.0" + "zhongshaofa/thinkphp-log-trace": "^1.0", + "lcobucci/jwt": "3.4" }, "require-dev": { "symfony/var-dumper": "^4.2",