{"id":4541,"date":"2020-05-10T21:17:55","date_gmt":"2020-05-10T13:17:55","guid":{"rendered":"https:\/\/wp.chunhsin.idv.tw\/?p=4541"},"modified":"2020-05-10T21:23:37","modified_gmt":"2020-05-10T13:23:37","slug":"%e7%94%a8yii2%e5%bb%ba%e7%ab%8boauth2-server","status":"publish","type":"post","link":"https:\/\/wp.chunhsin.idv.tw\/?p=4541","title":{"rendered":"\u7528Yii2\u5efa\u7acbOauth2 server"},"content":{"rendered":"\n<p>\u5148<a rel=\"noreferrer noopener\" href=\"https:\/\/www.yiiframework.com\/doc\/guide\/2.0\/zh-cn\/start-installation\" target=\"_blank\">\u5efa\u7acb\u4e00\u500byii2\u5c08\u6848<\/a>\uff0c\u7136\u5f8c\u5b89\u88dd\u9019\u500b<a rel=\"noreferrer noopener\" href=\"https:\/\/www.yiiframework.com\/extension\/hosannahighertech\/yii2-oauth2-server\" target=\"_blank\">extension<\/a>\u3002<\/p>\n\n\n\n<p>\u5e95\u4e0b\u70baYii2\u8a2d\u5b9a\u7684\u90e8\u4efd\uff0c\u662f\u8a2d\u5b9a\u4e00\u500b module\u4ee5\u53ca\u5728bootstrap\u968e\u6bb5\u5c31\u57f7\u884coauth2 module\uff0c\u548cextension\u7684\u8aaa\u660e\u6709\u4e9b\u4e0d\u592a\u4e00\u6a23\u3002<\/p>\n\n\n\n<!--more-->\n\n\n\n<pre class=\"wp-block-code\"><code>'bootstrap' => &#91;'oauth2'],\n'components'=>&#91;\n     'urlManager' => &#91;\n          'enablePrettyUrl' => true,\n          'showScriptName' => false,\n          'rules' => &#91;\n              'POST oauth2\/&lt;action:(.+)>' => 'oauth2\/rest\/&lt;action>',\n          ],\n      ],\n],\n'modules' => &#91;\n    'oauth2' => &#91;\n        'class' => 'filsh\\yii2\\oauth2server\\Module',\n        'tokenParamName' => 'access_token',\n        'tokenAccessLifetime' => 3600 * 24,\n        'storageMap' => &#91;\n            'user_credentials' => 'app\\models\\OauthUsers',\n            'user_claims'=>  'app\\models\\OauthUsers',\n        ],\n        'grantTypes' => &#91;\n            'user_credentials' => &#91;\n                'class' => 'OAuth2\\GrantType\\UserCredentials',\n            ],\n            'authorization_code' => &#91;\n                'class' => 'OAuth2\\GrantType\\AuthorizationCode',\n            ],\n            'refresh_token' => &#91;\n                'class' => 'OAuth2\\GrantType\\RefreshToken',\n                'always_issue_new_refresh_token' => true\n            ]\n        ]\n    ]\n]<\/code><\/pre>\n\n\n\n<p>storageMap\u88e1\u7684user_credentials\u548cuser_claims\u90fd\u662f\u6307\u5411 app\\models\\OauthUsers \uff0capp\\models\\OauthUsers\u662f\u5c0d\u61c9\u5c08\u6848\u88e1\u4f7f\u7528\u8005\u8cc7\u6599\u8868\u7684ActiveRecord\uff0cuser_credentials\u662f\u7528\u4f86\u8a8d\u8b49\u4f7f\u7528\u8005\u8eab\u4efd\uff0c\u9700\u5728app\\models\\OauthUsers\u5be6\u4f5cOAuth2\\Storage\\UserCredentialsInterface\u754c\u9762\uff1buser_claims\u662f\u7528\u4f86\u53d6\u5f97\u4f7f\u7528\u8005\u8cc7\u6599\uff0c\u9700\u5728app\\models\\OauthUsers\u5be6\u4f5c OAuth2\\OpenID\\Storage\\UserClaimsInterface\u754c\u9762\u3002<\/p>\n\n\n\n<p>grantTypes\u88e1\u7684 authorization_code\uff0c\u662f\u53d6\u5f97code\u4e4b\u5f8c\u8981\u4ea4\u63dbaccess_token\u7528\u7684\uff0c\u539f\u59cb\u7684extension\u8aaa\u660e\u88e1\u6c92\u6709\u63d0\u5230\uff0c\u9700\u52a0\u4e0a\u624d\u80fd\u6b63\u78ba\u904b\u4f5c\u3002<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>       'authorization_code' => &#91;\n            'class' => 'OAuth2\\GrantType\\AuthorizationCode',\n        ],<\/code><\/pre>\n\n\n\n<p>\u8a2d\u5b9a\u5b8c\u6210\u4e4b\u5f8c\uff0c<a rel=\"noreferrer noopener\" href=\"https:\/\/www.yiiframework.com\/extension\/hosannahighertech\/yii2-oauth2-server\" target=\"_blank\">\u9019\u500b\u5957\u4ef6<\/a>\u5df2\u7d93\u63d0\u4f9b\u8cc7\u6599\u8868\u7d50\u69cb\u53ef\u4f9b\u532f\u5165\uff0c\u8acb\u5728\u547d\u4ee4\u5217\u57f7\u884c  <\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>yii migrate --migrationPath=@vendor\/hosannahighertech\/yii2-oauth2-server\/migrations<\/code><\/pre>\n\n\n\n<p>\u532f\u5165\u7684\u8cc7\u6599\u8868\u6709\u9019\u4e9b:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>oauth_access_tokens - \u5132\u5b58access token\noauth_authorization_codes \u5132\u5b58authorize code\t\noauth_clients - \u5132\u5b58client\noauth_jwt - jwt\u8a8d\u8b49\u8cc7\u6599\noauth_public_keys - \noauth_refresh_tokens - \noauth_scopes - \u5132\u5b58 scope\noauth_users - \u5132\u5b58\u4f7f\u7528\u8005\u8cc7\u6599<\/code><\/pre>\n\n\n\n<p>\u5728\u4e32\u63a5facebook\u767b\u5165\u6216\u662fgoogle\u767b\u5165\u6642\uff0c\u6211\u5011\u90fd\u9700\u8981\u5148\u7533\u8acbappid (\u6216client_id) \u548csecret\uff0c\u540c\u6642\u8981\u8a2d\u5b9aredirect_uri\uff0c \u9019\u500b\u8cc7\u6599\u5c31\u5b58\u653e\u5728  oauth_clients\uff0c\u8a2d\u5b9a\u7684\u754c\u9762\u6211\u5011\u9700\u8981\u81ea\u5df1\u5b8c\u6210\uff0c\u4f46\u6e2c\u8a66\u6642\u53ef\u4ee5\u76f4\u63a5\u5f9e\u8cc7\u6599\u5eab\u4fee\u6539\u8cc7\u6599\u5373\u53ef\u3002\u53ef\u4ee5\u76f4\u63a5\u5229\u7528\u9810\u8a2d\u63d0\u4f9b\u7684testclient, testpass\u4f5c\u70baclient id\u53caclient secret\uff0c\u4e5f\u53ef\u4ee5\u53e6\u5916\u65b0\u589e\u8a18\u9304\uff0cgrant_types\u662f\u8a8d\u8b49\u7684\u65b9\u5f0f\uff0c\u4f60\u679c\u4f60\u4e0d\u78ba\u5b9a\u9019\u662f\u4ec0\u9ebc\uff0c\u65b0\u589e\u8a18\u9304\u6642\u6700\u597d\u8b93\u5b83\u8ddf\u7b2c\u4e00\u7b46\u8a18\u9304\u88e1\u7684\u5167\u5bb9\u4e00\u6a23\uff0c\u4ee5\u514d\u5f71\u97ff\u4f60\u5be6\u4f5c\u3002<\/p>\n\n\n\n<p>oauth_users\u662f\u4f7f\u7528\u8005\u8cc7\u6599\u8868\uff0c\u767b\u5165\u7684\u4f7f\u7528\u8005\u5e33\u5bc6\u5c31\u662f\u653e\u5728\u9019\u500b\u8cc7\u6599\u8868\uff0c\u5728\u5be6\u969b\u767b\u5165\u524d\uff0c\u8acb\u5148\u589e\u52a0\u4e00\u7b46\u4f60\u8981\u767b\u5165\u4f7f\u7528\u7684\u5e33\u865f\u548c\u5bc6\u78bc\u3002\u9810\u8a2d\u7684\u4e3b\u9375\u662fusername\uff0c\u6211\u81ea\u5df1\u518d\u65b0\u589e\u4e00\u500b\u6b04\u4f4d\u53ebid\uff0c\u6539\u4e3b\u9375\u70baid\uff0c\u5982\u679c\u6c92 \u6539\uff0c\u5f8c\u9762\u5229\u7528access token\u53d6\u5f97\u4f7f\u7528\u8005\u8cc7\u6599\u7684\u6b04\u4f4d\u5c31\u8981\u6539\u3002<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">\u5be6\u4f5cOauth2 Server<\/h2>\n\n\n\n<p>\u63a5\u4e0b\u4f86Service\u7aef\u7684\u5be6\u4f5c\u6709\u4e09\u500b\u9805\u76ee\u8981\u505a\uff0c\u5206\u5225\u662f\u8a8d\u8b49\u3001\u4ea4\u63dbaccess_token\uff0c\u4ee5\u53ca\u53d6\u5f97\u4f7f\u7528\u8005\u8cc7\u6599\uff0c\u8a8d\u8b49\u548c\u4ea4\u63dbaccess_token\u53ef\u4ee5\u898f\u5283\u5728\u540c\u4e00\u500bcontroller\u5be6\u4f5c\uff0c\u800c\u53d6\u5f97\u4f7f\u7528\u8005\u8cc7\u6599\u56e0\u70ba\u8981\u5229\u7528\u53d6\u5f97\u7684access_token\u4f86\u8a8d\u8b49\u8eab\u4efd\uff0c\u6703\u5229\u7528yii\\filters\\auth\\CompositeAuth\u9019\u500bcontroller filter\u4f86\u8a8d\u8b49\u8eab\u4efd\uff0c\u5efa\u8b70\u662f\u53e6\u8a2dcontroller\u4f86\u5be6\u4f5c\u3002<\/p>\n\n\n\n<p>\u8a8d\u8b49\u548c\u4ea4\u63dbaccess token\u7684\u5be6\u4f5c\u5982\u4e0b\uff1a<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>use Yii;\nuse yii\\helpers\\ArrayHelper;\nuse yii\\helpers\\VarDumper;\nuse yii\\web\\Controller;\nuse yii\\web\\Response;\nuse app\\models\\LoginForm;\n\nclass SiteController extends Controller\n{\n    public $enableCsrfValidation = false;\n\n    \/**\n     * Login action.\n     *\n     * @return Response|string\n     *\/\n    public function actionLogin()\n    {\n        if (!Yii::$app->user->isGuest) {\n            return $this->goHome();\n        }\n        $model = new LoginForm();\n        if ($model->load(Yii::$app->request->post()) &amp;&amp; $model->login()) {\n            return $this->redirect(ArrayHelper::merge(&#91;'site\/authorize'],Yii::$app->request->queryParams));   \/\/\u767b\u5165\u5b8c\u6210\u5f8c\u8981\u91cd\u65b0\u5c0e\u5411authorize\u9801\u9762\uff0c\u7db2\u5740\u53c3\u6578\u4e5f\u4e00\u4f75\u5e36\u904e\u53bb\u3002\n        }\n\n        $model->password = '';\n        return $this->render('login', &#91;\n            'model' => $model,\n        ]);\n    }\n\n    public function actionAuthorize()\n    {\n        if (Yii::$app->getUser()->getIsGuest())\n            return $this->redirect(ArrayHelper::merge(&#91;'login'],Yii::$app->request->queryParams));  \/\/\u672a\u767b\u5165\u4f7f\u7528\u8005\u9700\u5148\u767b\u5165\uff0c\u5e36\u904e\u4f86\u7684\u7db2\u5740\u53c3\u6578\u4e5f\u8981\u4e00\u4f75\u5e36\u904e\u53bb\u3002\n        \/** @var $module \\filsh\\yii2\\oauth2server\\Module *\/\n        $module = Yii::$app->getModule('oauth2');\n        \/** @var \\OAuth2\\Response $response  *\/\n        $response = $module->getServer()->handleAuthorizeRequest(null,null,!Yii::$app->getUser()->getIsGuest(), Yii::$app->getUser()->getId());\n        $headers = $response->getHttpHeaders();\n        return $this->redirect($headers&#91;'Location']);\n    }\n\n    public function actionToken()\n    {\n        \/** @var $module \\filsh\\yii2\\oauth2server\\Module *\/\n        $module = Yii::$app->getModule('oauth2');\n        \/** @var \\OAuth2\\Response $response  *\/\n        $response = $module->getServer()->handleTokenRequest(null,null,!Yii::$app->getUser()->getIsGuest(), Yii::$app->getUser()->getId());\n        Yii::$app->response->format = \\yii\\web\\Response::FORMAT_JSON;\n        Yii::$app->response->content = $response->getResponseBody();\n        return Yii::$app->response;\n    }\n}\n<\/code><\/pre>\n\n\n\n<p>\u63a5\u4e0b\u4f86\u8981\u505a\u53ef\u53d6\u5f97\u4f7f\u7528\u8005\u8cc7\u6599\u7684Api  Controller<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>use yii\\filters\\auth\\HttpBearerAuth;\nuse yii\\filters\\auth\\QueryParamAuth;\n\nclass ApiController extends \\yii\\rest\\Controller {\n\n    public function behaviors()\n    {\n        $behaviors = parent::behaviors();\n        $behaviors&#91;'authenticator'] = ArrayHelper::merge($behaviors&#91;'authenticator'], &#91;\n            'authMethods' => &#91;\n                &#91;'class' => HttpBearerAuth::className()],\n                &#91;'class' => QueryParamAuth::className(), 'tokenParam' => 'access_token'],\n            ]]);\n        \\Yii::debug('behaviors:'.VarDumper::export($behaviors));\n        return $behaviors;\n    }\n\n    public function actionUserInfo(){\n        $access_token = \\Yii::$app->request->get(\"access_token\");\/\/\u5982\u679c\u7528Yii\u9810\u8a2d\u7684\u50b3\u9001token\u65b9\u5f0f\uff0c\u662f\u5229\u7528get\u7684\u65b9\u5f0f\u3002\n        $tokenModel = \\app\\models\\OauthAccessTokens::find()->where(&#91;'access_token'=>$access_token])->one();\n        $user = \\app\\models\\OauthUsers::find()->where(&#91;'id'=>$tokenModel->user_id])->one();\n        return $user->toArray();\/\/\u5be6\u969b\u4e0a\u9019\u88e1\u53ef\u80fd\u8981\u7a0d\u5fae\u8655\u7406\u4e00\u4e0b\uff0c\u56e0\u70ba\u6574\u500buser\u88e1\u9762\u7684\u8cc7\u6599\u53ef\u80fd\u6709\u4e00\u4e9b\u654f\u611f\u4e0d\u61c9\u8f38\u51fa\u7684\u5167\u5bb9\uff0c\u50cf\u662f\u5bc6\u78bc\u3002\n    }\n\n}<\/code><\/pre>\n\n\n\n<p>\u5982\u679c\u4f60\u7684urlManager\u6709\u7167\u524d\u8ff0\u7684config\u7684\u8a2d\u5b9a<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>     'urlManager' => &#91;\n          'enablePrettyUrl' => true,\n          'showScriptName' => false,\n      ],<\/code><\/pre>\n\n\n\n<p>\u5047\u8a2d\u4f60\u7684\u7db2\u5740\u662fhttp:\/\/myauth.server<\/p>\n\n\n\n<p>\u8a8d\u8b49\u7684\u7db2\u5740\u662f:  http:\/\/myauth.server\/site\/authorize<\/p>\n\n\n\n<p>\u4ea4\u63dbaccess token\u7684\u7db2\u5740\u662f:  http:\/\/myauth.server\/site\/token<\/p>\n\n\n\n<p>\u53d6\u5f97\u4f7f\u7528\u8005\u8cc7\u6599\u7684\u7db2\u5740\u662f: http:\/\/myauth.server\/api\/user-info<\/p>\n\n\n\n<p>\u5982\u679c\u6c92\u6709\u8a2d\u5b9aurlManager\uff0c\u90a3\u7db2\u5740\u61c9\u8a72\u6703\u8b8a\u6210<\/p>\n\n\n\n<p>\u8a8d\u8b49\u7684\u7db2\u5740\u662f: http:\/\/myauth.server\/index.php?r=site\/authorize<\/p>\n\n\n\n<p>\u4ea4\u63dbaccess token\u7684\u7db2\u5740\u662f: http:\/\/myauth.server\/index.php?r=site\/token<\/p>\n\n\n\n<p>\u53d6\u5f97\u4f7f\u7528\u8005\u8cc7\u6599\u7684\u7db2\u5740\u662f: http:\/\/myauth.server\/index.php?r=api\/user-info<\/p>\n\n\n\n<p>\u4ee5\u4e0a\u662f\u7a0d\u5f8c\u5be6\u4f5cclient\u8a8d\u8b49\u6642\u6703\u7528\u5230\u7684\u7db2\u5740\u3002<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">\u5be6\u4f5cOauth2 client<\/h2>\n\n\n\n<p>client\u7684\u90e8\u4efd\u8acb\u53e6\u5efayii2\u5c08\u6848\u3002<\/p>\n\n\n\n<p>Yii2\u5df2\u7d93\u5c07\u5927\u90e8\u4efd\u7684\u5de5\u4f5c\u90fd\u505a\u4e86\uff0c\u5be6\u4f5c\u6642\u53ea\u8981\u65b0\u589e\u4e00\u500b\u7e7c\u627f  yii\\authclient\\OAuth2   \u7684\u985e\u5225\u5373\u53ef\uff0c\u4f8b\u5982\uff1a<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>use yii\\authclient\\OAuth2;\n\nclass MyAuthClient extends Oauth2\n{\n    public $authUrl = 'http:\/\/myauth.server\/site\/authorize';\n    public $apiBaseUrl = 'http:\/\/myauth.server\/api';\n    public $tokenUrl = 'http:\/\/myauth.server\/site\/token';\n    public $scope = 'openid'; \/\/\u5982\u679c\u8981\u5229\u7528\u5957\u4ef6\u81ea\u5e36\u7684api\u53d6\u5f97\u4f7f\u7528\u8005\u8cc7\u6599\uff0c\u9019\u88e1\u8acb\u4e00\u5b9a\u8981\u586bopenid\n\n    protected function initUserAttributes()\n    {\n        \/\/$token = $this->getAccessToken();\n        \/\/$headers = &#91;'Authorization' => 'Bearer '.$token->token];\n        $userInfo = $this->api('user-info', 'POST', &#91;], $headers);\n        return $userInfo;\n    }\n}<\/code><\/pre>\n\n\n\n<p>\u53e6\u5916client\u7aef\u7684\u8a2d\u5b9a\u5982\u4e0b\uff1a<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>'components'=>&#91;\n      'authClientCollection' => &#91;\n            'class' => 'yii\\authclient\\Collection',\n            'clients' => &#91;\n                'my-auth-client' => &#91;\n                    'class' => 'app\\components\\authclient\\MyAuthClient',\n                    'clientId' => 'testclient',\n                    'clientSecret' => 'testpass',\n                    'returnUrl' => 'http:\/\/myauth.client\/site\/auth?authclient=my-auth-client',\n                ],\n                \/\/ etc.\n            ],\n        ],\n],<\/code><\/pre>\n\n\n\n<p>\u4e0a\u8ff0\u7684 clientId, clientSecret\u548creturnUrl\u8acb\u52d9\u5fc5\u8a2d\u5728service\u7aef\u7684 oauth_clients \u8cc7\u6599\u8868\u88e1\u3002<\/p>\n\n\n\n<p>\u63a5\u4e0b\u4f86\u5728SiteController\u88e1\u8a2d\u5b9aAuth Action<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>class SiteController extends Controller\n{\n    public function actions()\n    {\n        return &#91;\n            'auth' => &#91;\n                'class' => 'yii\\authclient\\AuthAction',\n                'successCallback' => &#91;$this, 'authSuccess'],\n            ],\n        ];\n    }\n\n    public function authSuccess(\\yii\\authclient\\OAuth2 $client){\n        $userAttrs = $client->getUserAttributes();\n        \/\/todo: \u5be6\u4f5c\u767b\u5165\u7d30\u7bc0\n    }\n}<\/code><\/pre>\n\n\n\n<p>\u6700\u5f8c\u5c31\u662f\u5c07\u767b\u5165\u6309\u9215\u505a\u51fa\u4f86\uff0c\u5728\u767b\u5165\u9801\u7684view\u88e1\u547c\u53eb\u4e0b\u5217\u6558\u8ff0\u5373\u53ef\uff1a<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;?= yii\\authclient\\widgets\\AuthChoice::widget(&#91;\n      'baseAuthUrl' => &#91;'site\/auth']\n]); ?><\/code><\/pre>\n","protected":false},"excerpt":{"rendered":"<p>\u5148\u5efa\u7acb\u4e00\u500byii2\u5c08\u6848\uff0c\u7136\u5f8c\u5b89\u88dd\u9019\u500bextension\u3002 \u5e95\u4e0b\u70baYii2\u8a2d\u5b9a\u7684\u90e8\u4efd\uff0c\u662f\u8a2d\u5b9a\u4e00\u500b module\u4ee5 &hellip; <a href=\"https:\/\/wp.chunhsin.idv.tw\/?p=4541\" class=\"more-link\">\u95b1\u8b80\u5168\u6587<span class=\"screen-reader-text\">\u3008\u7528Yii2\u5efa\u7acbOauth2 server\u3009<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[5],"tags":[361,349],"class_list":["post-4541","post","type-post","status-publish","format-standard","hentry","category-php","tag-oauth2","tag-yii2"],"_links":{"self":[{"href":"https:\/\/wp.chunhsin.idv.tw\/index.php?rest_route=\/wp\/v2\/posts\/4541","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/wp.chunhsin.idv.tw\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/wp.chunhsin.idv.tw\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/wp.chunhsin.idv.tw\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/wp.chunhsin.idv.tw\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=4541"}],"version-history":[{"count":16,"href":"https:\/\/wp.chunhsin.idv.tw\/index.php?rest_route=\/wp\/v2\/posts\/4541\/revisions"}],"predecessor-version":[{"id":4557,"href":"https:\/\/wp.chunhsin.idv.tw\/index.php?rest_route=\/wp\/v2\/posts\/4541\/revisions\/4557"}],"wp:attachment":[{"href":"https:\/\/wp.chunhsin.idv.tw\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=4541"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/wp.chunhsin.idv.tw\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=4541"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/wp.chunhsin.idv.tw\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=4541"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}