| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124 |
- <?php
- namespace Tests\Integration;
- use App\Http\Middleware\JwtAdminMiddleware;
- use App\Models\Admin;
- use App\Services\AdminJwtSession;
- use App\Services\JwtService;
- use Carbon\Carbon;
- use Firebase\JWT\JWT;
- use Illuminate\Cache\ArrayStore;
- use Illuminate\Cache\Repository as CacheRepository;
- use Illuminate\Config\Repository;
- use Illuminate\Database\Capsule\Manager;
- use Illuminate\Database\Schema\Blueprint;
- use Illuminate\Foundation\Application;
- use Illuminate\Http\Request;
- use Illuminate\Support\Facades\DB;
- use Illuminate\Support\Facades\Facade;
- use Illuminate\Support\Facades\Schema;
- use Illuminate\Translation\ArrayLoader;
- use Illuminate\Translation\Translator;
- use PHPUnit\Framework\TestCase;
- class AdminJwtSessionTest extends TestCase
- {
- private Application $app;
- private AdminJwtSession $session;
- protected function setUp(): void
- {
- Carbon::setTestNow(Carbon::createFromTimestamp(time()));
- Facade::clearResolvedInstances();
- $this->app = new Application(dirname(__DIR__, 2));
- $this->app->instance('config', new Repository(['app' => [
- 'locale' => 'zh', 'jwt_secret' => str_repeat('k', 32), 'jwt_exp' => 86400,
- 'admin_jwt_idle' => 3600, 'admin_jwt_max_lifetime' => 86400 * 30,
- ]]));
- Facade::setFacadeApplication($this->app);
- $this->app->instance('cache', new CacheRepository(new ArrayStore()));
- $this->app->instance('translator', new Translator(new ArrayLoader(), 'zh'));
- $this->app->instance(\Illuminate\Contracts\Routing\ResponseFactory::class, new \Tests\Support\JsonResponseFactory());
- $db = new Manager($this->app); $db->addConnection(['driver' => 'sqlite', 'database' => ':memory:', 'prefix' => 'bot_']);
- $db->setAsGlobal(); $db->bootEloquent(); $this->app->instance('db', $db->getDatabaseManager());
- $this->app->bind('db.schema', fn () => $db->schema());
- Schema::create('admin', function (Blueprint $t) { $t->id(); $t->string('username'); $t->string('password')->default(''); $t->timestamps(); });
- DB::table('admin')->insert(['id' => 1, 'username' => 'root']);
- $this->session = new AdminJwtSession(new JwtService());
- }
- protected function tearDown(): void { Carbon::setTestNow(); DB::disconnect(); Facade::clearResolvedInstances(); parent::tearDown(); }
- private function request(string $token): array
- {
- $request = Request::create('/admin/x', 'GET');
- $request->headers->set('Authorization', 'Bearer ' . $token);
- $passed = false;
- $response = (new JwtAdminMiddleware($this->session))->handle($request, function () use (&$passed) { $passed = true; return 'ok'; });
- return [$passed, $response];
- }
- private function travel(int $seconds): void
- {
- Carbon::setTestNow(Carbon::now()->addSeconds($seconds));
- }
- public function test_activity_keeps_the_session_alive_beyond_the_idle_window(): void
- {
- $token = $this->session->issue(Admin::find(1));
- $payload = $this->session->validate($token);
- $this->assertSame(86400 * 30, $payload->exp - $payload->iat, 'JWT exp 只作为绝对上限');
- // 连续操作 5 小时(空闲窗口只有 1 小时),始终有效
- for ($i = 0; $i < 10; $i++) {
- $this->travel(1800);
- $this->assertTrue($this->request($token)[0], "第 {$i} 次请求应仍在登录态");
- }
- }
- public function test_session_expires_after_idle_window_without_requests(): void
- {
- $token = $this->session->issue(Admin::find(1));
- $this->travel(3599);
- $this->assertTrue($this->request($token)[0]);
- $this->travel(3601);
- [$passed, $response] = $this->request($token);
- $this->assertFalse($passed, '空闲超过 1 小时应失效');
- $this->assertSame(\App\Constants\HttpStatus::AUTHORIZATION_HEADER_NOT_FOUND, $response->getData(true)['code']);
- }
- public function test_revoked_and_forged_tokens_are_rejected(): void
- {
- $token = $this->session->issue(Admin::find(1));
- $this->session->revoke($this->session->validate($token));
- $this->assertFalse($this->request($token)[0]);
- $this->assertFalse($this->request($token . 'x')[0]);
- }
- public function test_legacy_tokens_without_sid_follow_their_own_expiry(): void
- {
- $legacy = JWT::encode(['iat' => time(), 'exp' => time() + 600, 'sub' => 1, 'user_id' => 1], str_repeat('k', 32), 'HS256');
- $this->assertTrue($this->request($legacy)[0], '上线前签发的 token 不受影响');
- $this->assertSame(time() + 600, $this->session->expiresAt($this->session->validate($legacy)));
- }
- public function test_realtime_expiry_is_stable_within_a_connection_and_renews_after_it(): void
- {
- $token = $this->session->issue(Admin::find(1));
- $first = $this->session->realtimeExpiresAt($this->session->validate($token));
- $this->assertSame(Carbon::now()->getTimestamp() + 3600, $first);
- // 期间有操作,活跃时间顺延,但同一连接内推送到期时间保持不变(各频道 channel_data 一致)
- $this->travel(1800);
- $this->request($token);
- $this->assertSame($first, $this->session->realtimeExpiresAt($this->session->validate($token)));
- $this->assertSame(Carbon::now()->getTimestamp() + 3600, $this->session->expiresAt($this->session->validate($token)));
- // 到点后重连拿到新的到期时间
- $this->travel(1801);
- $renewed = $this->session->realtimeExpiresAt($this->session->validate($token));
- $this->assertGreaterThan($first, $renewed);
- $this->assertLessThanOrEqual($this->session->expiresAt($this->session->validate($token)), $renewed);
- }
- }
|