From 294e1682319698564ed9f6b4b534a34ec1be246e Mon Sep 17 00:00:00 2001 From: zds <49744633+zds-s@users.noreply.github.com> Date: Sat, 20 Apr 2024 20:44:52 +0800 Subject: [PATCH] Optimal SecurityHttp component (#71) --- publish/security.php | 2 + src/Aspect/CurrentUserAspect.php | 29 +++++ src/Attribute/CurrentUser.php | 7 +- src/Command/GenJwtSecretCommand.php | 75 +++++++++++++ src/CurrentUserProxy.php | 102 ++++++++++++++++++ src/RegisterCurrentUserPropertyHandler.php | 43 ++++++++ .../Cases/Command/GenJwtSecretCommandTest.php | 73 +++++++++++++ 7 files changed, 330 insertions(+), 1 deletion(-) create mode 100644 src/Aspect/CurrentUserAspect.php create mode 100644 src/Command/GenJwtSecretCommand.php create mode 100644 src/CurrentUserProxy.php create mode 100644 src/RegisterCurrentUserPropertyHandler.php create mode 100644 tests/Cases/Command/GenJwtSecretCommandTest.php diff --git a/publish/security.php b/publish/security.php index ca53d9c..1df5f12 100644 --- a/publish/security.php +++ b/publish/security.php @@ -10,6 +10,7 @@ * @license https://github.com/mineadmin/MineAdmin/blob/master/LICENSE */ use Mine\Security\Http\Jwt\Black\CacheBlack; +use Mine\Security\Http\Jwt\Token; use Mine\Security\Http\UserProvider; use Mine\SecurityBundle\Context\Context; @@ -20,6 +21,7 @@ * user provider class */ 'provider' => UserProvider::class, + 'token' => Token::class, /* * entity class */ diff --git a/src/Aspect/CurrentUserAspect.php b/src/Aspect/CurrentUserAspect.php new file mode 100644 index 0000000..a329cb1 --- /dev/null +++ b/src/Aspect/CurrentUserAspect.php @@ -0,0 +1,29 @@ +process(); + } +} diff --git a/src/Attribute/CurrentUser.php b/src/Attribute/CurrentUser.php index 5cbe17f..087d548 100644 --- a/src/Attribute/CurrentUser.php +++ b/src/Attribute/CurrentUser.php @@ -15,4 +15,9 @@ use Hyperf\Di\Annotation\AbstractAnnotation; #[\Attribute(\Attribute::TARGET_PARAMETER)] -class CurrentUser extends AbstractAnnotation {} +class CurrentUser extends AbstractAnnotation +{ + public function __construct( + public string $secret = 'default' + ) {} +} diff --git a/src/Command/GenJwtSecretCommand.php b/src/Command/GenJwtSecretCommand.php new file mode 100644 index 0000000..f272518 --- /dev/null +++ b/src/Command/GenJwtSecretCommand.php @@ -0,0 +1,75 @@ +getSecretName(); + $value = $this->generator(); + $envPath = $this->getEnvPath(); + + if (! file_exists($envPath)) { + $this->error('.env file not is exists!'); + return; + } + if (\Mine\Helper\Str::contains(file_get_contents($envPath), $secretName) === false) { + file_put_contents($envPath, "\n{$secretName}={$value}\n", FILE_APPEND); + } else { + file_put_contents($envPath, preg_replace( + "~{$secretName}\\s*=\\s*[^\n]*~", + "{$secretName}=\"{$value}\"", + file_get_contents($envPath) + )); + } + + $this->info('jwt secret generator successfully:' . $value); + } + + public function getSecretName(): string + { + return Str::upper($this->input->getOption('secret-name')); + } + + public function getEnvPath(): string + { + return BASE_PATH . '/.env'; + } + + public function generator(): string + { + return base64_encode(random_bytes(64)); + } + + protected function configure() + { + $this->setHelp('run "php bin/hyperf.php mine:gen-jwt" create the new jwt secret'); + $this->setDescription('MineAdmin system gen jwt command'); + } + + protected function getOptions(): array + { + return [ + 'secret-name', 'sn', InputOption::VALUE_OPTIONAL, 'The jwt secret name.default:jwt_secret', 'jwt_secret', + ]; + } +} diff --git a/src/CurrentUserProxy.php b/src/CurrentUserProxy.php new file mode 100644 index 0000000..b13b615 --- /dev/null +++ b/src/CurrentUserProxy.php @@ -0,0 +1,102 @@ +security = $this->container->get(Security::class); + } + + public function __get($name) + { + return $this->getAttributes()[$name] ?? null; + } + + public function getEntity(): UserInterface + { + return $this->getSecurity()->getToken()->user($this->scene); + } + + public function getIdentifier(): string + { + return call([$this->getEntity(), __FUNCTION__]); + } + + public function getIdentifierName(): string + { + return call([$this->getEntity(), __FUNCTION__]); + } + + public function getRememberToken(): string + { + return call([$this->getEntity(), __FUNCTION__]); + } + + public function setRememberToken(string $token): void + { + call([$this->getEntity(), __FUNCTION__], func_get_args()); + } + + public function getRememberTokenName(): string + { + return call([$this->getEntity(), __FUNCTION__]); + } + + public function getPassword(): string + { + return call([$this->getEntity(), __FUNCTION__]); + } + + public function setPassword(string $password): void + { + call([$this->getEntity(), __FUNCTION__]); + } + + public function getSecurityBuilder(): Builder + { + return call([$this->getEntity(), __FUNCTION__]); + } + + public function setAttribute(string $key, mixed $value) + { + return call([$this->getEntity(), __FUNCTION__]); + } + + public function getAttributes(): array + { + return call([$this->getEntity(), __FUNCTION__]); + } + + protected function getSecurity(): Security + { + return $this->security; + } +} diff --git a/src/RegisterCurrentUserPropertyHandler.php b/src/RegisterCurrentUserPropertyHandler.php new file mode 100644 index 0000000..2826921 --- /dev/null +++ b/src/RegisterCurrentUserPropertyHandler.php @@ -0,0 +1,43 @@ +setValue(new CurrentUserProxy( + $annotation->secret, + $container, + )); + } + } +} diff --git a/tests/Cases/Command/GenJwtSecretCommandTest.php b/tests/Cases/Command/GenJwtSecretCommandTest.php new file mode 100644 index 0000000..f215a3c --- /dev/null +++ b/tests/Cases/Command/GenJwtSecretCommandTest.php @@ -0,0 +1,73 @@ +getMethod('generator'); + $this->assertIsString($invoke->invoke(\Mockery::mock(GenJwtSecretCommand::class))); + } + + public function testGetSecretName(): void + { + $reflection = new \ReflectionClass(GenJwtSecretCommand::class); + $invoke = $reflection->getMethod('getSecretName'); + $instance = $reflection->newInstanceWithoutConstructor(); + $input = \Mockery::mock(Input::class); + $input->allows('getOption')->andReturn('xxx'); + $instance->setInput($input); + $this->assertSame('XXX', $invoke->invoke($instance)); + } + + public function testGetEnvPath(): void + { + $reflection = new \ReflectionClass(GenJwtSecretCommand::class); + $invoke = $reflection->getMethod('getEnvPath'); + $m = \Mockery::mock(GenJwtSecretCommand::class); + $this->assertSame(BASE_PATH . '/.env', $invoke->invoke($m)); + } + + public function testInvoke(): void + { + $reflection = new \ReflectionClass(GenJwtSecretCommand::class); + $invoke = $reflection->getMethod('__invoke'); + $m = \Mockery::mock(GenJwtSecretCommand::class); + $m->shouldAllowMockingProtectedMethods(); + $env = sys_get_temp_dir() . '/' . Str::random(32) . '.env'; + file_put_contents($env, "xxx=1\n"); + $m->allows('getEnvPath')->andReturn($env); + $input = \Mockery::mock(Input::class); + $input->allows('getOption')->andReturn('Demo'); + $m->allows('setInput'); + $m->allows('getSecretName')->andReturn('DEMO'); + $m->allows('generator')->andReturn(base64_encode(random_bytes(64))); + $m->allows('info')->andReturnUsing(function ($v) { + echo $v; + }); + $m->setInput($input); + $invoke->invoke($m); + $this->assertTrue(str_contains(file_get_contents($env), 'DEMO')); + } +}