├── .github
├── FUNDING.yml
└── workflows
│ └── ci.yml
├── src
├── Exceptions
│ ├── HttpException.php
│ ├── Exception.php
│ ├── InvalidArgumentException.php
│ └── InvalidConfigException.php
├── Events
│ └── TokenCreated.php
├── FederationToken.php
├── QcloudFederationTokenServiceProvider.php
├── Strategies
│ ├── BuildTokens.php
│ ├── Strategy.php
│ └── StackStrategy.php
├── Contracts
│ └── StrategyInterface.php
├── Credentials.php
├── Token.php
├── Builder.php
├── Manager.php
└── Statement.php
├── psalm.xml
├── .gitignore
├── phpunit.xml.dist
├── tests
├── TestCase.php
├── CredentialsTest.php
├── StrategyTest.php
├── TokenTest.php
└── FeatureTest.php
├── LICENSE
├── composer.json
├── config
└── federation-token.php
└── README.md
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: [ overtrue ]
2 |
--------------------------------------------------------------------------------
/src/Exceptions/HttpException.php:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 |
3 | /vendor/
4 | node_modules/
5 | npm-debug.log
6 | yarn-error.log
7 |
8 | # Laravel 4 specific
9 | bootstrap/compiled.php
10 | app/storage/
11 |
12 | # Laravel 5 & Lumen specific
13 | public/storage
14 | public/hot
15 |
16 | # Laravel 5 & Lumen specific with changed public path
17 | public_html/storage
18 | public_html/hot
19 |
20 | storage/*.key
21 | .env
22 | Homestead.yaml
23 | Homestead.json
24 | /.vagrant
25 | .phpunit.result.cache
26 |
27 | cghooks.lock
28 | .php_cs.cache
29 | .php-cs-fixer.cache
30 | composer.lock
31 | .phpunit.cache
32 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ./tests/
6 | vendor
7 |
8 |
9 |
10 |
11 | src/
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/QcloudFederationTokenServiceProvider.php:
--------------------------------------------------------------------------------
1 | publishes([
15 | dirname(__DIR__).'/config/federation-token.php' => config_path('federation-token.php'),
16 | ], 'config');
17 |
18 | $this->mergeConfigFrom(dirname(__DIR__).'/config/federation-token.php', 'federation-token');
19 | }
20 |
21 | public function register()
22 | {
23 | $this->app->singleton(Manager::class, function () {
24 | return new Manager(config('federation-token'));
25 | });
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/Strategies/BuildTokens.php:
--------------------------------------------------------------------------------
1 | getBuilder()->build($this->getStatements(), $this->getExpiresIn(), $this->getName());
16 | }
17 |
18 | /**
19 | * @throws \Overtrue\LaravelQcloudFederationToken\Exceptions\InvalidConfigException
20 | */
21 | public function getBuilder(): Builder
22 | {
23 | return new Builder($this->getSecretId(), $this->getSecretKey(), $this->getRegion(), $this->getEndpoint());
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Contracts/StrategyInterface.php:
--------------------------------------------------------------------------------
1 | 'array', 'effect' => 'string', 'action' => 'array', 'resource' => 'array', 'condition' => 'array']])]
19 | public function getStatements(): array;
20 |
21 | public function getExpiresIn(): int;
22 |
23 | /**
24 | * @return array
25 | */
26 | public function getVariables(): array;
27 |
28 | public function createToken(): Token;
29 | }
30 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | branches: [master]
6 | pull_request:
7 | branches: [master]
8 |
9 | jobs:
10 | phpcs:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - uses: actions/checkout@v2
14 | - name: Setup PHP environment
15 | uses: shivammathur/setup-php@v2
16 | - name: Install dependencies
17 | run: composer install
18 | - name: PHPCSFixer check
19 | run: composer check-style
20 | phpunit:
21 | strategy:
22 | matrix:
23 | php_version: [8.1, 8.2]
24 | runs-on: ubuntu-latest
25 | steps:
26 | - uses: actions/checkout@v2
27 | - name: Setup PHP environment
28 | uses: shivammathur/setup-php@v2
29 | with:
30 | php-version: ${{ matrix.php_version }}
31 | coverage: xdebug
32 | - name: Install dependencies
33 | run: composer install
34 | - name: PHPUnit check
35 | run: ./vendor/bin/phpunit --coverage-text
36 |
--------------------------------------------------------------------------------
/tests/TestCase.php:
--------------------------------------------------------------------------------
1 |
15 | */
16 | protected function getPackageProviders($app): array
17 | {
18 | return [QcloudFederationTokenServiceProvider::class];
19 | }
20 |
21 | /**
22 | * Define environment setup.
23 | *
24 | * @param Application $app
25 | */
26 | protected function getEnvironmentSetUp($app)
27 | {
28 | // Setup default database to use sqlite :memory:
29 | $app['config']->set('database.default', 'testing');
30 | $app['config']->set('database.connections.testing', [
31 | 'driver' => 'sqlite',
32 | 'database' => ':memory:',
33 | 'prefix' => '',
34 | ]);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2020 overtrue
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/src/Credentials.php:
--------------------------------------------------------------------------------
1 | token;
25 | }
26 |
27 | public function getTmpSecretId(): string
28 | {
29 | return $this->tmpSecretId;
30 | }
31 |
32 | public function getTmpSecretKey(): string
33 | {
34 | return $this->tmpSecretKey;
35 | }
36 |
37 | #[ArrayShape(['token' => 'string', 'tmp_secret_id' => 'string', 'tmp_secret_key' => 'string'])]
38 | public function toArray(): array
39 | {
40 | return [
41 | 'token' => $this->token,
42 | 'tmp_secret_id' => $this->tmpSecretId,
43 | 'tmp_secret_key' => $this->tmpSecretKey,
44 | ];
45 | }
46 |
47 | public function toJson($options = 0): bool|string
48 | {
49 | return json_encode($this->toArray(), $options);
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/Token.php:
--------------------------------------------------------------------------------
1 | credentials;
24 | }
25 |
26 | public function getExpiredAt(): int
27 | {
28 | return $this->expiredAt;
29 | }
30 |
31 | public function getExpiration(): string
32 | {
33 | return $this->expiration;
34 | }
35 |
36 | public function getRequestId(): string
37 | {
38 | return $this->requestId;
39 | }
40 |
41 | public function getStatements(): array
42 | {
43 | return $this->statements;
44 | }
45 |
46 | #[ArrayShape(['credentials' => 'mixed', 'expired_at' => 'int', 'expiration' => 'string'])]
47 | public function toArray(): array
48 | {
49 | return [
50 | 'credentials' => $this->credentials->toArray(),
51 | 'expired_at' => $this->expiredAt,
52 | 'expiration' => $this->expiration,
53 | ];
54 | }
55 |
56 | public function toJson($options = 0): bool|string
57 | {
58 | return json_encode($this->toArray(), $options);
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/tests/CredentialsTest.php:
--------------------------------------------------------------------------------
1 | assertSame('mock-token', $credentials->getToken());
23 | $this->assertSame('mock-tmpSecretId', $credentials->getTmpSecretId());
24 | $this->assertSame('mock-tmpSecretKey', $credentials->getTmpSecretKey());
25 | }
26 |
27 | public function test_to_array()
28 | {
29 | $credentials = new Credentials(
30 | 'mock-token',
31 | 'mock-tmpSecretId',
32 | 'mock-tmpSecretKey'
33 | );
34 |
35 | $this->assertInstanceOf(Arrayable::class, $credentials);
36 |
37 | $this->assertSame([
38 | 'token' => 'mock-token',
39 | 'tmp_secret_id' => 'mock-tmpSecretId',
40 | 'tmp_secret_key' => 'mock-tmpSecretKey',
41 | ], $credentials->toArray());
42 | }
43 |
44 | public function test_to_json()
45 | {
46 | $credentials = new Credentials(
47 | 'mock-token',
48 | 'mock-tmpSecretId',
49 | 'mock-tmpSecretKey'
50 | );
51 |
52 | $this->assertInstanceOf(Jsonable::class, $credentials);
53 |
54 | $this->assertSame(json_encode([
55 | 'token' => 'mock-token',
56 | 'tmp_secret_id' => 'mock-tmpSecretId',
57 | 'tmp_secret_key' => 'mock-tmpSecretKey',
58 | ]), $credentials->toJson());
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "overtrue/laravel-qcloud-federation-token",
3 | "description": "QCloud COS FederationToken generator for Laravel.",
4 | "license": "MIT",
5 | "authors": [
6 | {
7 | "name": "overtrue",
8 | "email": "anzhengchao@gmail.com"
9 | }
10 | ],
11 | "require": {
12 | "laravel/framework": "^12.0",
13 | "tencentcloud/tencentcloud-sdk-php": "^3.0"
14 | },
15 | "autoload": {
16 | "psr-4": {
17 | "Overtrue\\LaravelQcloudFederationToken\\": "src"
18 | }
19 | },
20 | "autoload-dev": {
21 | "psr-4": {
22 | "Tests\\": "tests"
23 | }
24 | },
25 | "require-dev": {
26 | "mockery/mockery": "^1.2",
27 | "phpunit/phpunit": "^10.0",
28 | "orchestra/testbench": "^8.0",
29 | "jetbrains/phpstorm-attributes": "^1.0",
30 | "brainmaestro/composer-git-hooks": "dev-master",
31 | "laravel/pint": "^1.5"
32 | },
33 | "extra": {
34 | "laravel": {
35 | "providers": [
36 | "Overtrue\\LaravelQcloudFederationToken\\QcloudFederationTokenServiceProvider"
37 | ]
38 | },
39 | "hooks": {
40 | "pre-commit": [
41 | "composer check-style",
42 | "composer test"
43 | ],
44 | "pre-push": [
45 | "composer test"
46 | ]
47 | }
48 | },
49 | "scripts": {
50 | "post-update-cmd": [
51 | "cghooks remove",
52 | "cghooks add --ignore-lock",
53 | "cghooks update"
54 | ],
55 | "post-merge": "composer install",
56 | "post-install-cmd": [
57 | "cghooks remove",
58 | "cghooks add --ignore-lock",
59 | "cghooks update"
60 | ],
61 | "cghooks": "vendor/bin/cghooks",
62 | "check-style": "vendor/bin/pint --test",
63 | "fix-style": "vendor/bin/pint",
64 | "test": "phpunit --colors"
65 | },
66 | "scripts-descriptions": {
67 | "test": "Run all tests.",
68 | "check-style": "Run style checks (only dry run - no fixing!).",
69 | "fix-style": "Run style checks and fix violations."
70 | },
71 | "config": {
72 | "allow-plugins": {
73 | "composer/package-versions-deprecated": true
74 | }
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/tests/StrategyTest.php:
--------------------------------------------------------------------------------
1 | 'secretId',
14 | 'secret_key' => 'secretKey',
15 | 'region' => 'ap-guangzhou',
16 | 'statements' => [
17 | [
18 | 'principal' => [
19 | 'qcs' => [
20 | 'qcs::cam::uid/1238423:uin/3232523',
21 | ],
22 | ],
23 | 'effect' => 'deny',
24 | 'action' => [
25 | 'cos:PutObject',
26 | 'cos:GetObject',
27 | ],
28 | 'resource' => [
29 | 'qcs::cos:ap-beijing:uid/1238423:bucketA-1238423/*',
30 | 'qcs::cos::uid/1238423:bucketB-1238423/object2',
31 | ],
32 | 'condition' => [
33 | 'ip_equal' => [
34 | 'qcs:ip' => '10.121.2.10/24',
35 | ],
36 | ],
37 | ],
38 | ],
39 | ]);
40 |
41 | $statement = $strategy->getStatements()[0];
42 |
43 | $this->assertSame('secretId', $strategy->getSecretId());
44 | $this->assertSame('secretKey', $strategy->getSecretKey());
45 | $this->assertSame('ap-guangzhou', $strategy->getRegion());
46 | $this->assertSame('deny', $statement['effect']);
47 | $this->assertSame([
48 | 'cos:PutObject',
49 | 'cos:GetObject',
50 | ], $statement['action']);
51 | $this->assertSame([
52 | 'qcs::cos:ap-beijing:uid/1238423:bucketA-1238423/*',
53 | 'qcs::cos:ap-guangzhou:uid/1238423:bucketB-1238423/object2',
54 | ], $statement['resource']);
55 | $this->assertSame([
56 | 'ip_equal' => [
57 | 'qcs:ip' => '10.121.2.10/24',
58 | ],
59 | ], $statement['condition']);
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/tests/TokenTest.php:
--------------------------------------------------------------------------------
1 | assertEquals($credentials, $token->getCredentials());
28 | $this->assertSame(1547696355, $token->getExpiredAt());
29 | $this->assertSame('2022-05-05 05:05:05', $token->getExpiration());
30 | $this->assertSame('mock-request-id', $token->getRequestId());
31 | $this->assertSame(['mock-statement'], $token->getStatements());
32 | }
33 |
34 | public function test_to_array()
35 | {
36 | $credentials = new Credentials(
37 | 'mock-token',
38 | 'mock-tmpSecretId',
39 | 'mock-tmpSecretKey'
40 | );
41 |
42 | $token = new Token($credentials, 1547696355, '2022-05-05 05:05:05', 'mock-request-id', [
43 | 'mock-statement',
44 | ]);
45 |
46 | $this->assertInstanceOf(Arrayable::class, $token);
47 |
48 | $this->assertSame([
49 | 'credentials' => $credentials->toArray(),
50 | 'expired_at' => 1547696355,
51 | 'expiration' => '2022-05-05 05:05:05',
52 | ], $token->toArray());
53 | }
54 |
55 | public function test_to_json()
56 | {
57 | $credentials = new Credentials(
58 | 'mock-token',
59 | 'mock-tmpSecretId',
60 | 'mock-tmpSecretKey'
61 | );
62 |
63 | $token = new Token($credentials, 1547696355, '2022-05-05 05:05:05', 'mock-request-id');
64 |
65 | $this->assertInstanceOf(Jsonable::class, $token);
66 |
67 | $this->assertSame(json_encode([
68 | 'credentials' => $credentials->toArray(),
69 | 'expired_at' => 1547696355,
70 | 'expiration' => '2022-05-05 05:05:05',
71 | ]), $token->toJson());
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/config/federation-token.php:
--------------------------------------------------------------------------------
1 | [
6 | 'secret_id' => env('QCLOUD_COS_SECRET_ID'),
7 | 'secret_key' => env('QCLOUD_COS_SECRET_KEY'),
8 | 'region' => env('QCLOUD_COS_REGION', 'ap-guangzhou'),
9 | 'effect' => 'allow',
10 | 'action' => [
11 | 'cos:ListParts',
12 | 'cos:PutObject',
13 | 'cos:PostObject',
14 | 'cos:InitiateMultipartUpload',
15 | 'cos:UploadPart',
16 | 'cos:CompleteMultipartUpload',
17 | 'cos:AbortMultipartUpload',
18 | 'cos:ListMultipartUploads',
19 | ],
20 | 'variables' => [
21 | 'bucket' => env('QCLOUD_COS_BUCKET'),
22 | 'appid' => env('QCLOUD_COS_APP_ID'),
23 | ],
24 | ],
25 | // 请注意:
26 | // 官方限制了一个策略规则的总字符长度为 1000,所以一个规则尽量别写太复杂,否则可能会报错
27 | 'strategies' => [
28 | // 多个策略生成一个 token
29 | // 'all' => [
30 | // 'strategies' => ['images'],
31 | // ],
32 | // 'images' => [
33 | // // 策略名称,可选
34 | // 'name' => 'cos-put',
35 | // // 临时凭证过期时间
36 | // 'expires_in' => 1800,
37 | //
38 | // // 将与默认配置合并
39 | // 'variables' => [
40 | // 'appid' => env('QCLOUD_APP_ID'),
41 | // 'bucket' => env('QCLOUD_COS_BUCKET', ''),
42 | // //...
43 | // ],
44 | //
45 | // //Statement 请参考:https://cloud.tencent.com/document/product/598/10603
46 | // "statements" => [
47 | // [
48 | // "action" => [
49 | // 'cos:ListParts',
50 | // 'cos:PutObject',
51 | // 'cos:PostObject',
52 | // 'cos:InitiateMultipartUpload',
53 | // 'cos:UploadPart',
54 | // 'cos:CompleteMultipartUpload',
55 | // 'cos:AbortMultipartUpload',
56 | // 'cos:ListMultipartUploads',
57 | // ],
58 | // "resource" => [
59 | // "qcs::cos:ap-beijing:uid/{uid}:{bucket}-{appid}/{date}/{uuid}/*",
60 | // ],
61 | // ]
62 | // ],
63 | // ],
64 | ],
65 | ];
66 |
--------------------------------------------------------------------------------
/src/Strategies/Strategy.php:
--------------------------------------------------------------------------------
1 | config = new Repository($config);
25 | }
26 |
27 | public function get(string $key)
28 | {
29 | return $this->config->get($key);
30 | }
31 |
32 | public function getHeaders()
33 | {
34 | return $this->config->get('headers', []);
35 | }
36 |
37 | public function getName()
38 | {
39 | return $this->config->get('name', $this->name);
40 | }
41 |
42 | public function getSecretId(): string
43 | {
44 | return $this->config->get('secret_id');
45 | }
46 |
47 | public function getSecretKey(): string
48 | {
49 | return $this->config->get('secret_key');
50 | }
51 |
52 | public function getEndpoint(): ?string
53 | {
54 | return $this->config->get('endpoint');
55 | }
56 |
57 | public function getRegion(): ?string
58 | {
59 | return $this->config->get('region');
60 | }
61 |
62 | public function getResources(): array
63 | {
64 | return $this->config->get('resource', []);
65 | }
66 |
67 | public function getExpiresIn(): int
68 | {
69 | return $this->config->get('expires_in', $this->config->get('duration_seconds', 1800));
70 | }
71 |
72 | public function getVariables(): array
73 | {
74 | return $this->config->get('variables', []);
75 | }
76 |
77 | /**
78 | * @throws \Overtrue\LaravelQcloudFederationToken\Exceptions\InvalidConfigException
79 | * @throws \Overtrue\LaravelQcloudFederationToken\Exceptions\InvalidArgumentException
80 | */
81 | #[ArrayShape([Statement::class])]
82 | public function getStatements(): array
83 | {
84 | $statements = $this->config->get('statements');
85 |
86 | if (empty($statements)) {
87 | throw new InvalidConfigException('No statements found.');
88 | }
89 |
90 | $variables = array_merge([
91 | 'region' => $this->getRegion(),
92 | ], $this->getVariables());
93 |
94 | return array_map(fn ($config) => (new Statement($config))->setVariables($variables)->toArray(), $statements);
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/src/Builder.php:
--------------------------------------------------------------------------------
1 | secretId, $this->secretKey);
27 | $httpProfile = new HttpProfile;
28 | $httpProfile->setEndpoint($this->endpoint ?: 'sts.tencentcloudapi.com');
29 |
30 | $clientProfile = new ClientProfile;
31 | $clientProfile->setHttpProfile($httpProfile);
32 | $client = new StsClient($credential, $this->region ?: 'ap-guangzhou', $clientProfile);
33 |
34 | $request = new GetFederationTokenRequest;
35 |
36 | $request->fromJsonString(
37 | json_encode([
38 | 'Name' => $name ?? config('app.name').'.'.uniqid(),
39 | 'Policy' => json_encode([
40 | 'version' => '2.0',
41 | 'statement' => $statements,
42 | ]),
43 | 'DurationSeconds' => $this->expiresIn($expiresIn),
44 | ])
45 | );
46 |
47 | $response = $client->GetFederationToken($request);
48 |
49 | $credentials = new Credentials($response->getCredentials()->getToken(), $response->getCredentials()->getTmpSecretId(), $response->getCredentials()->getTmpSecretKey());
50 |
51 | return tap(new Token(
52 | $credentials,
53 | $response->getExpiredTime(),
54 | $response->getExpiration(),
55 | $response->getRequestId(),
56 | $statements
57 | ), function ($token) {
58 | event(new TokenCreated($token));
59 | });
60 | }
61 |
62 | protected function expiresIn(int|string|Carbon $expiredAt): int
63 | {
64 | $format = fn ($expiresIn) => (int) (min($expiresIn, static::MAX_EXPIRES_IN));
65 |
66 | return match (true) {
67 | is_int($expiredAt) => $format($expiredAt),
68 | is_object($expiredAt) => $format((int) $expiredAt->timestamp - time()),
69 | is_string($expiredAt) => $format((int) Carbon::parse($expiredAt)->timestamp - time()),
70 | };
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/Strategies/StackStrategy.php:
--------------------------------------------------------------------------------
1 | config = new Repository($config);
20 | }
21 |
22 | public function getName()
23 | {
24 | return $this->config->get('name', $this->name);
25 | }
26 |
27 | /**
28 | * @throws \Overtrue\LaravelQcloudFederationToken\Exceptions\InvalidConfigException
29 | */
30 | public function getSecretId(): string
31 | {
32 | return $this->config->get('secret_id') ?? $this->getDefaultStrategy()->getSecretId();
33 | }
34 |
35 | /**
36 | * @throws \Overtrue\LaravelQcloudFederationToken\Exceptions\InvalidConfigException
37 | */
38 | public function getRegion(): ?string
39 | {
40 | return $this->config->get('region') ?? $this->getDefaultStrategy()->getRegion();
41 | }
42 |
43 | /**
44 | * @throws \Overtrue\LaravelQcloudFederationToken\Exceptions\InvalidConfigException
45 | */
46 | public function getEndpoint(): ?string
47 | {
48 | return $this->config->get('endpoint') ?? $this->getDefaultStrategy()->getEndpoint();
49 | }
50 |
51 | /**
52 | * @throws \Overtrue\LaravelQcloudFederationToken\Exceptions\InvalidConfigException
53 | */
54 | public function getSecretKey(): string
55 | {
56 | return $this->config->get('secret_key') ?? $this->getDefaultStrategy()->getSecretKey();
57 | }
58 |
59 | /**
60 | * @throws \Overtrue\LaravelQcloudFederationToken\Exceptions\InvalidConfigException
61 | */
62 | public function getExpiresIn(): int
63 | {
64 | return $this->config->get('expires_in', $this->config->get('duration_seconds', 1800)) ?? $this->getDefaultStrategy()->getExpiresIn();
65 | }
66 |
67 | /**
68 | * @throws \Overtrue\LaravelQcloudFederationToken\Exceptions\InvalidConfigException
69 | */
70 | public function getVariables(): array
71 | {
72 | return $this->config->get('variables', []) ?? $this->getDefaultStrategy()->getVariables();
73 | }
74 |
75 | /**
76 | * @throws \Overtrue\LaravelQcloudFederationToken\Exceptions\InvalidConfigException
77 | */
78 | public function getDefaultStrategy(): StrategyInterface
79 | {
80 | foreach ($this->strategies as $strategy) {
81 | if ($strategy instanceof StrategyInterface) {
82 | return $strategy;
83 | }
84 | }
85 |
86 | throw new InvalidConfigException('Invalid stack strategy config, no available strategy found.');
87 | }
88 |
89 | #[ArrayShape([Statement::class])]
90 | public function getStatements(): array
91 | {
92 | $statements = [];
93 |
94 | foreach ($this->strategies as $strategy) {
95 | if ($strategy instanceof StrategyInterface) {
96 | $statements = array_merge($statements, $strategy->getStatements());
97 | }
98 | }
99 |
100 | return $statements;
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/src/Manager.php:
--------------------------------------------------------------------------------
1 | config = new Repository($config);
29 | }
30 |
31 | /**
32 | * @throws InvalidArgumentException
33 | */
34 | public function strategy(?string $strategy = null)
35 | {
36 | $strategy = $strategy ?: $this->getDefaultStrategy();
37 |
38 | if (is_null($strategy)) {
39 | throw new InvalidArgumentException(sprintf(
40 | 'Unable to resolve NULL strategy for [%s].',
41 | static::class
42 | ));
43 | }
44 |
45 | if (! isset($this->strategies[$strategy])) {
46 | $this->strategies[$strategy] = $this->createStrategy($strategy);
47 | }
48 |
49 | return $this->strategies[$strategy];
50 | }
51 |
52 | /**
53 | * @throws InvalidArgumentException
54 | */
55 | protected function createStrategy($name): StrategyInterface
56 | {
57 | $strategyConfig = $this->getStrategyConfig($name);
58 |
59 | if (isset($this->customCreators[$name])) {
60 | return $this->callCustomCreator($name);
61 | } elseif (array_key_exists('strategies', $strategyConfig)) {
62 | return $this->createStackStrategy($name, $strategyConfig);
63 | } elseif (\array_key_exists($name, $this->config->get('strategies'))) {
64 | $method = 'create'.Str::studly($name).'Strategy';
65 |
66 | if (method_exists($this, $method)) {
67 | return $this->$method();
68 | }
69 |
70 | return new Strategy($name, $strategyConfig);
71 | }
72 |
73 | throw new InvalidArgumentException("Strategy [$name] not supported.");
74 | }
75 |
76 | /**
77 | * @throws \Overtrue\LaravelQcloudFederationToken\Exceptions\InvalidArgumentException
78 | */
79 | protected function createStackStrategy(string $strategyName, array $config): StrategyInterface
80 | {
81 | $strategies = [];
82 |
83 | foreach ($config['strategies'] ?? [] as $name) {
84 | $strategies[] = $this->strategy($name);
85 | }
86 |
87 | return new StackStrategy($strategies, $strategyName, $config);
88 | }
89 |
90 | protected function getStrategyConfig(string $strategyName): array
91 | {
92 | $defaultConfig = $this->config->get('default');
93 | $strategyConfig = array_merge($defaultConfig, $this->config->get("strategies.$strategyName", []));
94 |
95 | $strategyConfig['variables'] = array_merge(
96 | $this->config->get('default.variables', []),
97 | $this->config->get("strategies.{$strategyName}.variables", [])
98 | );
99 |
100 | $defaultStatement = Arr::only($defaultConfig, ['action', 'effect', 'principal', 'resource', 'condition']);
101 |
102 | $strategyConfig['statements'] = array_map(fn ($statement) => array_merge($defaultStatement, $statement), $strategyConfig['statements'] ?? []);
103 |
104 | return $strategyConfig;
105 | }
106 |
107 | protected function callCustomCreator(string $strategy): StrategyInterface
108 | {
109 | return $this->customCreators[$strategy]($this->config->get("strategies.{$strategy}"));
110 | }
111 |
112 | public function extend($strategy, Closure $callback): static
113 | {
114 | $this->customCreators[$strategy] = $callback;
115 |
116 | return $this;
117 | }
118 |
119 | /**
120 | * @return array
121 | */
122 | public function getStrategies(): array
123 | {
124 | return $this->strategies;
125 | }
126 |
127 | public function forgetStrategies(): static
128 | {
129 | $this->strategies = [];
130 |
131 | return $this;
132 | }
133 |
134 | /**
135 | * @throws InvalidArgumentException
136 | */
137 | public function __call($method, $parameters)
138 | {
139 | return $this->strategy()->$method(...$parameters);
140 | }
141 |
142 | protected function getDefaultStrategy(): ?string
143 | {
144 | return array_key_first($this->config->get('strategies'));
145 | }
146 | }
147 |
--------------------------------------------------------------------------------
/src/Statement.php:
--------------------------------------------------------------------------------
1 | setEffect($config['effect'] ?? 'allow');
33 |
34 | if (! empty($config['principal'])) {
35 | $this->setPrincipal($config['principal']);
36 | }
37 |
38 | if (! empty($config['action'])) {
39 | $this->setAction($config['action']);
40 | }
41 |
42 | if (! empty($config['resource'])) {
43 | $this->setResource($config['resource']);
44 | }
45 |
46 | if (! empty($config['condition'])) {
47 | $this->setCondition($config['condition']);
48 | }
49 | }
50 |
51 | public function setVariables(array $variables): static
52 | {
53 | $this->variables = $variables;
54 |
55 | return $this;
56 | }
57 |
58 | public function setPrincipal(array $principal): static
59 | {
60 | $this->principal = $principal;
61 |
62 | return $this;
63 | }
64 |
65 | /**
66 | * @throws \Overtrue\LaravelQcloudFederationToken\Exceptions\InvalidArgumentException
67 | */
68 | public function setEffect(string $effect): static
69 | {
70 | if (! in_array($effect, ['allow', 'deny'])) {
71 | throw new InvalidArgumentException('Invalid effect value.');
72 | }
73 |
74 | $this->effect = $effect;
75 |
76 | return $this;
77 | }
78 |
79 | public function allow(): static
80 | {
81 | $this->effect = 'allow';
82 |
83 | return $this;
84 | }
85 |
86 | public function deny(): static
87 | {
88 | $this->effect = 'deny';
89 |
90 | return $this;
91 | }
92 |
93 | public function setAction(array $action): static
94 | {
95 | $this->action = $action;
96 |
97 | return $this;
98 | }
99 |
100 | public function setResource(array $resource): static
101 | {
102 | $this->resource = $resource;
103 |
104 | return $this;
105 | }
106 |
107 | public function setCondition(array $condition): static
108 | {
109 | $this->condition = $condition;
110 |
111 | return $this;
112 | }
113 |
114 | public function __toString(): string
115 | {
116 | return json_encode($this->toArray()) ?? '';
117 | }
118 |
119 | #[ArrayShape(['principal' => 'array', 'effect' => 'string', 'action' => 'array', 'resource' => 'array', 'condition' => 'array'])]
120 | public function toArray(): array
121 | {
122 | $principal = $this->principal;
123 |
124 | if (! empty($principal['qcs'])) {
125 | $principal['qcs'] = array_map([$this, 'replaceVariables'], $principal['qcs']);
126 | }
127 |
128 | return [
129 | 'principal' => $principal,
130 | 'effect' => $this->effect,
131 | 'action' => $this->action,
132 | 'resource' => array_map([$this, 'replaceVariables'], $this->resource),
133 | 'condition' => $this->condition,
134 | ];
135 | }
136 |
137 | protected function replaceVariables(string $string): string
138 | {
139 | $variables = array_merge(...array_map(function ($key, $value) {
140 | return ['<'.$key.'>' => trim($value, '<>')];
141 | }, array_keys($this->variables), $this->variables));
142 |
143 | $replacements = array_merge([
144 | '' => Str::uuid()->toString(),
145 | '' => \time(),
146 | '' => Str::random(16),
147 | '' => Str::random(32),
148 | '' => \date('Ymd'),
149 | '' => \date('Ymd'),
150 | '' => \date('YmdHis'),
151 | '' => \date('Y'),
152 | '' => \date('m'),
153 | '' => \date('d'),
154 | '' => \date('H'),
155 | '' => \date('i'),
156 | '' => \date('s'),
157 | ], $variables);
158 |
159 | return str_replace(array_keys($replacements), array_values($replacements), $string);
160 | }
161 |
162 | public function toJson($options = 0): bool|string
163 | {
164 | return json_encode($this->toArray());
165 | }
166 |
167 | #[ArrayShape(['principal' => 'array', 'effect' => 'string', 'action' => 'array', 'resource' => 'array', 'condition' => 'array'])]
168 | public function jsonSerialize()
169 | {
170 | return $this->toArray();
171 | }
172 |
173 | public function offsetExists(mixed $offset): bool
174 | {
175 | return property_exists($this, $offset);
176 | }
177 |
178 | public function offsetGet(mixed $offset)
179 | {
180 | return $this->toArray()[$offset] ?? null;
181 | }
182 |
183 | public function offsetSet(mixed $offset, mixed $value)
184 | {
185 | $method = sprintf('set%s', ucfirst($offset));
186 |
187 | if (method_exists($this, $method)) {
188 | $this->$method($value);
189 | }
190 | }
191 |
192 | public function offsetUnset(mixed $offset) {}
193 | }
194 |
--------------------------------------------------------------------------------
/tests/FeatureTest.php:
--------------------------------------------------------------------------------
1 | [
23 | 'qcs' => [
24 | 'qcs::cam::uid/1238423:uin/3232523',
25 | ],
26 | ],
27 | 'effect' => 'allow',
28 | 'action' => [
29 | 'cos:PutObject',
30 | 'cos:GetObject',
31 | 'cos:HeadObject',
32 | 'cos:OptionsObject',
33 | 'cos:ListParts',
34 | 'cos:GetObjectTagging',
35 | ],
36 | 'resource' => [
37 | 'qcs::cos:ap-beijing:uid/1238423:bucketA-1238423/*',
38 | 'qcs::cos:ap-guangzhou:uid/1238423:bucketB-1238423/object2',
39 | ],
40 | 'condition' => [
41 | 'ip_equal' => [
42 | 'qcs:ip' => '10.121.2.10/24',
43 | ],
44 | ],
45 | ];
46 |
47 | config([
48 | 'federation-token' => [
49 | 'default' => [
50 | 'secret_id' => 'default-secret-id',
51 | 'expired_at' => '+1 hour',
52 | ],
53 | 'strategies' => [
54 | 'cvm' => [
55 | 'secret_id' => 'secret-id',
56 | 'secret_key' => 'secret-key',
57 | 'region' => 'ap-tokyo',
58 | 'statements' => [$statement],
59 | ],
60 |
61 | 'cos' => [
62 | 'secret_key' => 'secret-key',
63 | 'region' => 'ap-tokyo',
64 | 'statements' => [$statement],
65 | ],
66 | ],
67 | ],
68 | ]);
69 |
70 | $this->assertSame('secret-id', FederationToken::getSecretId());
71 | $this->assertSame('default-secret-id', FederationToken::strategy('cos')->getSecretId());
72 |
73 | $this->assertSame([$statement], FederationToken::getStatements());
74 | }
75 |
76 | public function test_it_can_replace_vars()
77 | {
78 | $statement = [
79 | 'principal' => [
80 | 'qcs' => [
81 | 'qcs::cam::uid/:uin/',
82 | ],
83 | ],
84 | 'effect' => 'allow',
85 | 'action' => [
86 | 'cos:PutObject',
87 | 'cos:GetObject',
88 | 'cos:HeadObject',
89 | 'cos:OptionsObject',
90 | 'cos:ListParts',
91 | 'cos:GetObjectTagging',
92 | ],
93 | 'resource' => [
94 | 'qcs::cos::uid/:bucketA-/*',
95 | 'qcs::cos::uid/:bucketA-///*',
96 | 'qcs::cos::uid/1238423:bucketB-/////object2',
97 | ],
98 | 'condition' => [
99 | 'ip_equal' => [
100 | 'qcs:ip' => '10.121.2.10/24',
101 | ],
102 | ],
103 | ];
104 |
105 | config([
106 | 'federation-token' => [
107 | 'default' => [
108 | 'secret_id' => 'default-secret-id',
109 | 'expired_at' => '+1 hour',
110 | 'variables' => [
111 | 'uid' => 'mock-uid',
112 | 'uin' => 'mock-uin',
113 | 'appid' => 'mock-appid',
114 | 'region' => 'ap-guangzhou',
115 | ],
116 | ],
117 | 'strategies' => [
118 | 'cos' => [
119 | 'secret_key' => 'secret-key',
120 | 'region' => 'ap-tokyo',
121 | 'variables' => [
122 | 'uid' => 'mock-uid-from-cos',
123 | 'uin' => 'mock-uin',
124 | 'appid' => 'mock-appid',
125 | 'var2' => 'mock-var2',
126 | ],
127 | 'statements' => [$statement],
128 | ],
129 | 'cvm' => [
130 | 'secret_id' => 'secret-id',
131 | 'secret_key' => 'secret-key',
132 | 'region' => 'ap-tokyo',
133 | 'statements' => [$statement],
134 | ],
135 | ],
136 | ],
137 | ]);
138 |
139 | $statement = FederationToken::getStatements();
140 |
141 | // "qcs::cam::uid/:uin/"
142 | $this->assertSame(['qcs' => ['qcs::cam::uid/mock-uid-from-cos:uin/mock-uin']], $statement[0]['principal']);
143 |
144 | // "qcs::cos:{region}:uid/:bucketA-/*",
145 | $this->assertSame(
146 | sprintf(
147 | 'qcs::cos:%s:uid/%s:bucketA-%s/*',
148 | 'ap-guangzhou',
149 | 'mock-uid-from-cos',
150 | 'mock-uid-from-cos',
151 | ),
152 | $statement[0]['resource'][0]
153 | );
154 |
155 | // "qcs::cos::uid/:bucketA-///*",
156 | $this->assertSame(
157 | sprintf(
158 | 'qcs::cos:%s:uid/%s:bucketA-%s/%s/%s/*',
159 | 'ap-guangzhou',
160 | 'mock-uid-from-cos',
161 | 'mock-uid-from-cos',
162 | time(),
163 | 'mock-var2',
164 | ),
165 | $statement[0]['resource'][1]
166 | );
167 |
168 | // "qcs::cos::uid/1238423:bucketB-/////object2"
169 | $this->assertSame(
170 | sprintf(
171 | 'qcs::cos:%s:uid/1238423:bucketB-%s/%s/%s/%s/%s/object2',
172 | 'ap-guangzhou',
173 | 'mock-appid',
174 | date('Ymd'),
175 | date('Y'),
176 | date('m'),
177 | date('d')
178 | ),
179 | $statement[0]['resource'][2]
180 | );
181 | }
182 | }
183 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Laravel 腾讯云联合身份临时访问凭证生成器
2 | ---
3 |
4 | 
5 | 
6 | 
7 | 
8 |
9 | Laravel [腾讯云联合身份临时访问凭证](https://cloud.tencent.com/document/product/1312/48195) 生成器,主要用于下发腾讯云联合身份临时访问凭证,比如前端直传等场景。
10 |
11 | [](https://github.com/sponsors/overtrue)
12 |
13 | 开始之前,请您仔细阅读并理解一下官方文档:
14 |
15 | - [获取联合身份临时访问凭证](https://cloud.tencent.com/document/product/1312/48195)
16 | - [COS API 授权策略使用指引](https://cloud.tencent.com/document/product/436/31923)
17 | - [条件键说明及使用示例](https://cloud.tencent.com/document/product/436/71307)
18 | - [CAM 策略语法](https://cloud.tencent.com/document/product/598/10603)
19 | - [临时证书](https://cloud.tencent.com/document/api/1312/48198#Credentials)
20 | - [API Doctor(使用诊断)](https://console.cloud.tencent.com/api/diagnosis)
21 | - [COS 自助诊断工具](https://console.cloud.tencent.com/cos/diagnose)
22 |
23 | ## 安装
24 |
25 | ```shell
26 | $ composer require overtrue/laravel-qcloud-federation-token -vvv
27 | ```
28 |
29 | ### 配置
30 |
31 | 你可以通过以下命令将配置文件写入 `config/federation-token.php`:
32 |
33 | ```php
34 | $ php artisan vendor:publish --provider="Overtrue\\LaravelQCloudFederationToken\\QCloudFederationTokenServiceProvider"
35 | ```
36 |
37 | **config/federation-token.php**
38 |
39 | ```php
40 | return [
41 | // 默认配置,strategies 下的每一个策略将合并此基础配置
42 | 'default' => [
43 | 'secret_id' => env('QCLOUD_COS_SECRET_ID', ''),
44 | 'secret_key' => env('QCLOUD_COS_SECRET_KEY', ''),
45 | 'region' => env('QCLOUD_COS_REGION', 'ap-guangzhou'),
46 | "effect" => "allow",
47 |
48 | // 全局变量,会被替换到所有策略中
49 | 'variables' => [
50 | 'uid' => env('QCLOUD_COS_APP_ID'),
51 | 'region' => env('QCLOUD_COS_REGION', 'ap-guangzhou'),
52 | //...
53 | ],
54 | ],
55 | // strategies
56 | 'strategies' => [
57 | // 策略名称,比如:image/avatar...
58 | 'avatar' => [
59 | // 策略名称,可选
60 | 'name' => 'avatar',
61 |
62 | // 临时凭证过期时间
63 | 'expires_in' => 1800,
64 |
65 | // 将与默认配置合并
66 | 'variables' => [
67 | 'appid' => env('QCLOUD_COS_APP_ID'),
68 | 'bucket' => env('QCLOUD_COS_BUCKET', ''),
69 | //...
70 | ],
71 |
72 | // Statement 请参考:https://cloud.tencent.com/document/product/598/10603
73 | "statements" => [
74 | [
75 | "action" => [
76 | // 这里建议不要随便修改,分片上传需要用到这些
77 | 'cos:ListParts',
78 | 'cos:PutObject',
79 | 'cos:PostObject',
80 | 'cos:InitiateMultipartUpload',
81 | 'cos:UploadPart',
82 | 'cos:CompleteMultipartUpload',
83 | 'cos:AbortMultipartUpload',
84 | 'cos:ListMultipartUploads',
85 | ],
86 | "resource" => [
87 | "qcs::cos:ap-beijing:uid/:-///*",
88 | ],
89 | 'condition' => [
90 | 'string_equal' => [
91 | 'cos:x-cos-forbid-overwrite' => 'true', // 禁止覆盖
92 | ],
93 | ],
94 | ]
95 | ],
96 | 'headers' => [
97 | // statements 包含 x 类型的 condition 需要告诉客户端传递 header
98 | 'x-cos-forbid-overwrite' => true,
99 | ]
100 | ],
101 | ],
102 | ];
103 | ```
104 |
105 |
106 | 配置语法请参考:https://cloud.tencent.com/document/product/598/10603
107 |
108 | 你可以根据使用场景配置多个策略,然后按策略分发访问凭证。
109 |
110 | > **Warning**
111 | >
112 | > 注意:`condition` 中使用 `cos:content-type` 记得 urlencode, 不然遇到带+号的 MIME 将会失效,比如 `image/svg+xml`。
113 |
114 | ### 安全提醒
115 |
116 | 临时 token 规则配置不合理,可能会造成安全问题,请谨慎配置,请严格检查如下:
117 |
118 | - 配置前请仔细阅读:[《用于前端直传 COS 的临时密钥安全指引》](https://cloud.tencent.com/document/product/436/40265)
119 | - 策略有限制最大长度,不建议把策略配置得太多。
120 | - 凭证生命周期不要设置过长。
121 | - 避免使用主账号来生成凭证,专用的限制登录的 API 子账号。
122 | - 按照[《条件键说明及使用示例》](https://cloud.tencent.com/document/product/436/71307) 添加请求限制如上传大小和类型等。
123 | - 将账号权限控制在尽量小的范围内,避免越权操作。
124 |
125 | ### 变量替换
126 |
127 | 在配置中难免会用到各种上下文变量或者一些动态 resouce 路径等,你可以在配置中指定 `variables` 变量来实现变量替换,例如:
128 |
129 | > 仅 principal 和 resource 中的变量可以替换,其他变量不支持替换。
130 |
131 | ```php
132 | // config/federation-token.php
133 | [
138 | 'secret_id' => env('QCLOUD_COS_SECRET_ID'),
139 | 'secret_key' => env('QCLOUD_COS_SECRET_KEY'),
140 | 'region' => env('QCLOUD_COS_REGION', 'ap-guangzhou'),
141 | "effect" => "allow",
142 |
143 | // 全局变量,会被替换到所有策略中
144 | 'variables' => [
145 | 'region' => env('QCLOUD_COS_REGION', 'ap-guangzhou'),
146 | //...
147 | ],
148 | ],
149 | // strategies
150 | 'strategies' => [
151 | // 请参考:https://cloud.tencent.com/document/product/598/10603
152 | 'avatar' => [
153 | // 将与默认配置合并
154 | 'variables' => [
155 | 'appid' => env('QCLOUD_COS_APP_ID'),
156 | 'bucket' => env('QCLOUD_COS_BUCKET'),
157 | //...
158 | ],
159 | "statements" => [
160 | [
161 | "action" => [
162 | 'cos:ListParts',
163 | 'cos:PutObject',
164 | 'cos:PostObject',
165 | 'cos:InitiateMultipartUpload',
166 | 'cos:UploadPart',
167 | 'cos:CompleteMultipartUpload',
168 | 'cos:AbortMultipartUpload',
169 | 'cos:ListMultipartUploads',
170 | ],
171 | "resource" => [
172 | "qcs::cos:ap-beijing:uid/:-///*",
173 | ],
174 | ]
175 | ],
176 | ],
177 | ],
178 | ];
179 | ```
180 |
181 | 以上配置将会生成如下结果:
182 |
183 | ```json
184 | {
185 | "effect": "allow",
186 | "action": [
187 | "cos:PutObject",
188 | "cos:GetObject",
189 | ],
190 | "resource": [
191 | "qcs::cos:ap-beijing:uid/12278900:example-12278900/20220202/bbeae9bb-d650-46f9-aab3-f4171a1bfdea/*"
192 | ]
193 | }
194 | ```
195 |
196 | ### 内置变量如下
197 |
198 | - `` - UUID 例如:`ca007813-4a49-4d5a-afab-abae18a969a5`
199 | - `` - 当前时间戳,例如:`1654485526`
200 | - `` - 随机字符串,16 位,例如:`Bbq6gkXXIPyCDsEL`
201 | - `` - 随机字符串,32 位,例如:`FykbMqi6GT6JHiyv6E2xqUeo3CZLPjo7`
202 | - `` - 日期,例如:`20220606`
203 | - `` - 日期,例如:`20220606`
204 | - `` - 日期时间(年月日时分秒),例如:`20220606031846`
205 | - `` - 年,例如:`2022`
206 | - `` - 月,例如:`06`
207 | - `` - 日,例如:`06`
208 | - `` - 时,例如:`03`
209 | - `` - 分,例如:`18`
210 | - `` - 秒,例如:`46`
211 |
212 | ## 使用
213 |
214 | ```php
215 | use Overtrue\LaravelQCloudFederationToken\FederationToken;
216 |
217 | // 使用默认策略(配置项 strategies 中第一个)
218 | $token = FederationToken::createToken();
219 |
220 | // 或者指定策略
221 | $token = FederationToken::strategy('avatar')->createToken();
222 |
223 | $token->toArray();
224 |
225 | // 'credentials' => [
226 | // 'token' => 'kTRtHpOSOCUzTVWmzlPKweHffXjT9Izo7b61a142d6b56d31c0a7ace4d22bcff3zpbsXKTIrCo43dRRh7bDIKE1ZOE1KRYHEm0KNLjWG_aSF63YoQWchg',
227 | // 'tmp_secret_id' => 'AKIDw7dwZbmFSup9CnAOraJ7skiPMybaV3WPP5B4oVMCIL5kLyphV_3IyAHFJ5QMCjE6',
228 | // 'tmp_secret_key' => '/lvEo280/AlGt4orjDl9tWLIOMl5nkexS5Pg+xys7ps=',
229 | // ],
230 | // 'expired_at' => 1547696355,
231 | ```
232 |
233 | 如果你需要获取生成 token 的配置信息,你可以使用 `getStatements` 方法:
234 |
235 | ```php
236 | $token->getStatements();
237 | ```
238 |
239 | 格式请参考: https://cloud.tencent.com/document/product/1312/48195
240 |
241 | ### 字符限制问题
242 |
243 | 对于单次策略请求,官方限制策略总字符数为 1000 字符,稍微复杂一点就会超过这个限制,建议做法:
244 |
245 | - 在控制台配置策略,写法一样,也有可视化配置界面,不限制字符数
246 | - 在 API 侧配置简单的基础策略
247 |
248 | 请求时将会产生交集结果,所以二者结合,既保证了安全,也避免了 API 断字符限制问题。
249 |
250 | ### 事件
251 |
252 | | **Event** | **Description** |
253 | |-------------------------------------------------------------|--------------|
254 | | `Overtrue\LaravelQcloudFederationToken\Events\TokenCreated` | token 生成时触发 |
255 |
256 | ## :heart: 赞助我
257 |
258 | [](https://github.com/sponsors/overtrue)
259 |
260 | 如果你喜欢我的项目并想支持它,[点击这里 :heart:](https://github.com/sponsors/overtrue)
261 |
262 | ## 贡献代码
263 |
264 | 你可以通过以下方式参与贡献:
265 |
266 | 1. 通过 [issue tracker](https://github.com/overtrue/laravel-package/issues) 提交 Bug;
267 | 2. 通过 [issue tracker](https://github.com/overtrue/laravel-package/issues) 回答问题或修复 Bug;
268 | 3. 通过 Pull Request 增加新特性或优化文档。
269 |
270 | _代码贡献过程不需要很正式。你只需要确保你遵循 PSR-0、PSR-1 和 PSR-2 的编码准则。任何新的代码贡献都必须附带对应的单元测试。_
271 |
272 | ## Project supported by JetBrains
273 |
274 | Many thanks to Jetbrains for kindly providing a license for me to work on this and other open-source projects.
275 |
276 | [](https://www.jetbrains.com/?from=https://github.com/overtrue)
277 |
278 | ## PHP 扩展包开发
279 |
280 | > 想知道如何从零开始构建 PHP 扩展包?
281 | >
282 | > 请关注我的实战课程,我会在此课程中分享一些扩展开发经验 —— [《PHP 扩展包实战教程 - 从入门到发布》](https://learnku.com/courses/creating-package)
283 |
284 | ## License
285 |
286 | MIT
287 |
--------------------------------------------------------------------------------