Skip to content

Commit 294e168

Browse files
authored
Optimal SecurityHttp component (#71)
1 parent ae5e1f3 commit 294e168

7 files changed

+330
-1
lines changed

publish/security.php

+2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
* @license https://github.com/mineadmin/MineAdmin/blob/master/LICENSE
1111
*/
1212
use Mine\Security\Http\Jwt\Black\CacheBlack;
13+
use Mine\Security\Http\Jwt\Token;
1314
use Mine\Security\Http\UserProvider;
1415
use Mine\SecurityBundle\Context\Context;
1516

@@ -20,6 +21,7 @@
2021
* user provider class
2122
*/
2223
'provider' => UserProvider::class,
24+
'token' => Token::class,
2325
/*
2426
* entity class
2527
*/

src/Aspect/CurrentUserAspect.php

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
/**
5+
* This file is part of MineAdmin.
6+
*
7+
* @link https://www.mineadmin.com
8+
* @document https://doc.mineadmin.com
9+
* @contact [email protected]
10+
* @license https://github.com/mineadmin/MineAdmin/blob/master/LICENSE
11+
*/
12+
13+
namespace Mine\Security\Http\Aspect;
14+
15+
use Hyperf\Di\Aop\AbstractAspect;
16+
use Hyperf\Di\Aop\ProceedingJoinPoint;
17+
use Mine\Security\Http\Attribute\CurrentUser;
18+
19+
class CurrentUserAspect extends AbstractAspect
20+
{
21+
public array $annotations = [
22+
CurrentUser::class,
23+
];
24+
25+
public function process(ProceedingJoinPoint $proceedingJoinPoint)
26+
{
27+
return $proceedingJoinPoint->process();
28+
}
29+
}

src/Attribute/CurrentUser.php

+6-1
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,9 @@
1515
use Hyperf\Di\Annotation\AbstractAnnotation;
1616

1717
#[\Attribute(\Attribute::TARGET_PARAMETER)]
18-
class CurrentUser extends AbstractAnnotation {}
18+
class CurrentUser extends AbstractAnnotation
19+
{
20+
public function __construct(
21+
public string $secret = 'default'
22+
) {}
23+
}

src/Command/GenJwtSecretCommand.php

+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
/**
5+
* This file is part of MineAdmin.
6+
*
7+
* @link https://www.mineadmin.com
8+
* @document https://doc.mineadmin.com
9+
* @contact [email protected]
10+
* @license https://github.com/mineadmin/MineAdmin/blob/master/LICENSE
11+
*/
12+
13+
namespace Mine\Security\Http\Command;
14+
15+
use Hyperf\Command\Annotation\Command;
16+
use Hyperf\Command\Command as Base;
17+
use Hyperf\Stringable\Str;
18+
use Symfony\Component\Console\Input\InputOption;
19+
20+
#[Command]
21+
class GenJwtSecretCommand extends Base
22+
{
23+
protected ?string $name = 'mine:gen-jwt-secret';
24+
25+
public function __invoke()
26+
{
27+
$secretName = $this->getSecretName();
28+
$value = $this->generator();
29+
$envPath = $this->getEnvPath();
30+
31+
if (! file_exists($envPath)) {
32+
$this->error('.env file not is exists!');
33+
return;
34+
}
35+
if (\Mine\Helper\Str::contains(file_get_contents($envPath), $secretName) === false) {
36+
file_put_contents($envPath, "\n{$secretName}={$value}\n", FILE_APPEND);
37+
} else {
38+
file_put_contents($envPath, preg_replace(
39+
"~{$secretName}\\s*=\\s*[^\n]*~",
40+
"{$secretName}=\"{$value}\"",
41+
file_get_contents($envPath)
42+
));
43+
}
44+
45+
$this->info('jwt secret generator successfully:' . $value);
46+
}
47+
48+
public function getSecretName(): string
49+
{
50+
return Str::upper($this->input->getOption('secret-name'));
51+
}
52+
53+
public function getEnvPath(): string
54+
{
55+
return BASE_PATH . '/.env';
56+
}
57+
58+
public function generator(): string
59+
{
60+
return base64_encode(random_bytes(64));
61+
}
62+
63+
protected function configure()
64+
{
65+
$this->setHelp('run "php bin/hyperf.php mine:gen-jwt" create the new jwt secret');
66+
$this->setDescription('MineAdmin system gen jwt command');
67+
}
68+
69+
protected function getOptions(): array
70+
{
71+
return [
72+
'secret-name', 'sn', InputOption::VALUE_OPTIONAL, 'The jwt secret name.default:jwt_secret', 'jwt_secret',
73+
];
74+
}
75+
}

src/CurrentUserProxy.php

+102
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
/**
5+
* This file is part of MineAdmin.
6+
*
7+
* @link https://www.mineadmin.com
8+
* @document https://doc.mineadmin.com
9+
* @contact [email protected]
10+
* @license https://github.com/mineadmin/MineAdmin/blob/master/LICENSE
11+
*/
12+
13+
namespace Mine\Security\Http;
14+
15+
use Hyperf\Context\Traits\CoroutineProxy;
16+
use Hyperf\Database\Model\Builder;
17+
use Mine\SecurityBundle\Contract\UserInterface;
18+
use Mine\SecurityBundle\Security;
19+
use Psr\Container\ContainerInterface;
20+
21+
use function Hyperf\Support\call;
22+
23+
class CurrentUserProxy implements UserInterface
24+
{
25+
use CoroutineProxy;
26+
27+
protected string $proxyKey = 'secret.http.proxy';
28+
29+
private Security $security;
30+
31+
public function __construct(
32+
private string $scene,
33+
private ContainerInterface $container
34+
) {
35+
$this->security = $this->container->get(Security::class);
36+
}
37+
38+
public function __get($name)
39+
{
40+
return $this->getAttributes()[$name] ?? null;
41+
}
42+
43+
public function getEntity(): UserInterface
44+
{
45+
return $this->getSecurity()->getToken()->user($this->scene);
46+
}
47+
48+
public function getIdentifier(): string
49+
{
50+
return call([$this->getEntity(), __FUNCTION__]);
51+
}
52+
53+
public function getIdentifierName(): string
54+
{
55+
return call([$this->getEntity(), __FUNCTION__]);
56+
}
57+
58+
public function getRememberToken(): string
59+
{
60+
return call([$this->getEntity(), __FUNCTION__]);
61+
}
62+
63+
public function setRememberToken(string $token): void
64+
{
65+
call([$this->getEntity(), __FUNCTION__], func_get_args());
66+
}
67+
68+
public function getRememberTokenName(): string
69+
{
70+
return call([$this->getEntity(), __FUNCTION__]);
71+
}
72+
73+
public function getPassword(): string
74+
{
75+
return call([$this->getEntity(), __FUNCTION__]);
76+
}
77+
78+
public function setPassword(string $password): void
79+
{
80+
call([$this->getEntity(), __FUNCTION__]);
81+
}
82+
83+
public function getSecurityBuilder(): Builder
84+
{
85+
return call([$this->getEntity(), __FUNCTION__]);
86+
}
87+
88+
public function setAttribute(string $key, mixed $value)
89+
{
90+
return call([$this->getEntity(), __FUNCTION__]);
91+
}
92+
93+
public function getAttributes(): array
94+
{
95+
return call([$this->getEntity(), __FUNCTION__]);
96+
}
97+
98+
protected function getSecurity(): Security
99+
{
100+
return $this->security;
101+
}
102+
}
+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
/**
5+
* This file is part of MineAdmin.
6+
*
7+
* @link https://www.mineadmin.com
8+
* @document https://doc.mineadmin.com
9+
* @contact [email protected]
10+
* @license https://github.com/mineadmin/MineAdmin/blob/master/LICENSE
11+
*/
12+
13+
namespace Mine\Security\Http;
14+
15+
use Hyperf\Context\ApplicationContext;
16+
use Hyperf\Di\Definition\PropertyHandlerManager;
17+
use Hyperf\Di\ReflectionManager;
18+
use Mine\Security\Http\Attribute\CurrentUser;
19+
20+
class RegisterCurrentUserPropertyHandler
21+
{
22+
public static bool $registered = false;
23+
24+
public static function register(): void
25+
{
26+
if (static::$registered) {
27+
return;
28+
}
29+
PropertyHandlerManager::register(CurrentUser::class, [static::class, 'handle']);
30+
}
31+
32+
public static function handle($object, $currentClassName, $targetClassName, $property, $annotation): void
33+
{
34+
if ($annotation instanceof CurrentUser) {
35+
$reflectionProperty = ReflectionManager::reflectProperty($currentClassName, $property);
36+
$container = ApplicationContext::getContainer();
37+
$reflectionProperty->setValue(new CurrentUserProxy(
38+
$annotation->secret,
39+
$container,
40+
));
41+
}
42+
}
43+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
/**
5+
* This file is part of MineAdmin.
6+
*
7+
* @link https://www.mineadmin.com
8+
* @document https://doc.mineadmin.com
9+
* @contact [email protected]
10+
* @license https://github.com/mineadmin/MineAdmin/blob/master/LICENSE
11+
*/
12+
13+
namespace Mine\Security\Http\Tests\Cases\Command;
14+
15+
use Hyperf\Stringable\Str;
16+
use Mine\Security\Http\Command\GenJwtSecretCommand;
17+
use PHPUnit\Framework\TestCase;
18+
use Symfony\Component\Console\Input\Input;
19+
20+
/**
21+
* @internal
22+
* @coversNothing
23+
*/
24+
class GenJwtSecretCommandTest extends TestCase
25+
{
26+
public function testGenerator(): void
27+
{
28+
$reflection = new \ReflectionClass(GenJwtSecretCommand::class);
29+
$invoke = $reflection->getMethod('generator');
30+
$this->assertIsString($invoke->invoke(\Mockery::mock(GenJwtSecretCommand::class)));
31+
}
32+
33+
public function testGetSecretName(): void
34+
{
35+
$reflection = new \ReflectionClass(GenJwtSecretCommand::class);
36+
$invoke = $reflection->getMethod('getSecretName');
37+
$instance = $reflection->newInstanceWithoutConstructor();
38+
$input = \Mockery::mock(Input::class);
39+
$input->allows('getOption')->andReturn('xxx');
40+
$instance->setInput($input);
41+
$this->assertSame('XXX', $invoke->invoke($instance));
42+
}
43+
44+
public function testGetEnvPath(): void
45+
{
46+
$reflection = new \ReflectionClass(GenJwtSecretCommand::class);
47+
$invoke = $reflection->getMethod('getEnvPath');
48+
$m = \Mockery::mock(GenJwtSecretCommand::class);
49+
$this->assertSame(BASE_PATH . '/.env', $invoke->invoke($m));
50+
}
51+
52+
public function testInvoke(): void
53+
{
54+
$reflection = new \ReflectionClass(GenJwtSecretCommand::class);
55+
$invoke = $reflection->getMethod('__invoke');
56+
$m = \Mockery::mock(GenJwtSecretCommand::class);
57+
$m->shouldAllowMockingProtectedMethods();
58+
$env = sys_get_temp_dir() . '/' . Str::random(32) . '.env';
59+
file_put_contents($env, "xxx=1\n");
60+
$m->allows('getEnvPath')->andReturn($env);
61+
$input = \Mockery::mock(Input::class);
62+
$input->allows('getOption')->andReturn('Demo');
63+
$m->allows('setInput');
64+
$m->allows('getSecretName')->andReturn('DEMO');
65+
$m->allows('generator')->andReturn(base64_encode(random_bytes(64)));
66+
$m->allows('info')->andReturnUsing(function ($v) {
67+
echo $v;
68+
});
69+
$m->setInput($input);
70+
$invoke->invoke($m);
71+
$this->assertTrue(str_contains(file_get_contents($env), 'DEMO'));
72+
}
73+
}

0 commit comments

Comments
 (0)