{"id":532,"date":"2026-06-15T02:00:51","date_gmt":"2026-06-15T02:00:51","guid":{"rendered":"https:\/\/hyc.eshachem.com\/program\/?page_id=532"},"modified":"2026-06-15T09:55:25","modified_gmt":"2026-06-15T09:55:25","slug":"sso-%e5%96%ae%e4%b8%80%e7%99%bb%e5%85%a5%e6%8a%80%e8%a1%93%e6%96%87%e4%bb%b6-springboot","status":"publish","type":"page","link":"https:\/\/hyc.eshachem.com\/program\/birc\/sso-%e5%96%ae%e4%b8%80%e7%99%bb%e5%85%a5%e6%8a%80%e8%a1%93%e6%96%87%e4%bb%b6-springboot\/","title":{"rendered":"SpringBoot &#8211; SSO in BIRC"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">\u672c\u6587\u4ef6\u900f\u904e\u5546\u696d\u667a\u6167\u7814\u7a76\u4e2d\u5fc3 \u2013 \u667a\u6167\u6821\u5712:\u6d3b\u52d5\u7cfb\u7d71\u4f86\u8aaa\u660e<strong>\u5546\u667a\u4e2d\u5fc3\u7684SSO\u67b6\u69cb<\/strong>\u3002<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u5546\u667a\u4e2d\u5fc3\u7684SSO\u70ba\u81ea\u67b6\u4f3a\u670d\u5668\uff0c<strong>\u975e<\/strong>Google SSO\uff0c\u4f46\u539f\u7406\u5927\u540c\u5c0f\u7570\u3002<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u8a72\u6587\u7ae0\u6240\u8ff0\u7a0b\u5f0f\u78bc\u7686\u63d0\u4f9b\u65bc\u6587\u7ae0\u6700\u4e0b\u65b9GitHub\u4e2d\u3002<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\">\u7248\u672c\uff1a0.1.0<br>\u6700\u5f8c\u66f4\u65b0\u6642\u9593\uff1a06\/10\/2026<br>\u6700\u5f8c\u66f4\u65b0\u4eba\u54e1\uff1a\u9673\u6cd3\u6bd3<\/p>\n<\/blockquote>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\">\n\n\n\n<h2 class=\"wp-block-heading\" id=\"%e6%a6%82%e8%bf%b0\">\u6982\u8ff0<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">\u63a1\u7528 <strong>\u6388\u6b0a\u78bc\uff08Authorization Code\uff09\u4ea4\u63db<\/strong> \u6a21\u5f0f\u8655\u7406\u4e26\u9a57\u8b49\u6191\u8b49\uff1a<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>\u89d2\u8272<\/th><th>\u8aaa\u660e<\/th><\/tr><\/thead><tbody><tr><td><strong>SSO \u767b\u5165\u4e2d\u5fc3<\/strong><\/td><td>\u96c6\u4e2d\u5f0f\u8eab\u5206\u9a57\u8b49\u670d\u52d9\uff0c\u8ca0\u8cac Google \u7b49\u5916\u90e8 IdP \u767b\u5165\u3001\u6838\u767c\u4e00\u6b21\u6027\u6388\u6b0a\u78bc<\/td><\/tr><tr><td>\u7cfb\u7d71<strong>\u524d\u7aef<\/strong><\/td><td>\u5c0e\u5411 SSO \u767b\u5165\u3001\u63a5\u6536\u56de\u50b3\u7684 <code>code<\/code>\u3001\u8f49\u4ea4\u5f8c\u7aef\u4ea4\u63db JWT<\/td><\/tr><tr><td>\u7cfb\u7d71<strong>\u5f8c\u7aef<\/strong><\/td><td>\u4ee5 <code>Client Secret<\/code> \u5411 SSO \u9a57\u8b49 <code>code<\/code>\u3001\u5efa\u7acb\/\u66f4\u65b0\u672c\u5730\u4f7f\u7528\u8005\u3001\u6838\u767c\u61c9\u7528\u7a0b\u5f0f JWT<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>\u8a2d\u8a08\u539f\u5247\uff1a<\/strong> <code>Client Secret<\/code> \u50c5\u5b58\u65bc\u5f8c\u7aef\uff0c\u524d\u7aef\u6c38\u9060\u4e0d\u61c9\u76f4\u63a5\u547c\u53eb SSO \u7684 <code>\/sso\/verify-code<\/code>\u3002(\u907f\u514d\u88abXSS)<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\">JWT\u53ef\u53c3\u8003: <a href=\"https:\/\/hyc.eshachem.com\/program\/291-2\/\">https:\/\/hyc.eshachem.com\/program\/291-2\/<\/a><\/p>\n<\/blockquote>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\">\n\n\n\n<h2 class=\"wp-block-heading\" id=\"%e6%95%b4%e9%ab%94%e7%99%bb%e5%85%a5%e6%b5%81%e7%a8%8b\">\u6574\u9ad4\u767b\u5165\u6d41\u7a0b<\/h2>\n\n\n\n<div class=\"wp-block-merpress-mermaidjs diagram-source-mermaid\"><pre class=\"mermaid\">sequenceDiagram\n    participant User as \u4f7f\u7528\u8005\n    participant FE as \u524d\u7aef\u61c9\u7528\n    participant SSO as SSO \u767b\u5165\u4e2d\u5fc3\n    participant BE as Campus Activity \u5f8c\u7aef\n    participant DB as \u8cc7\u6599\u5eab\n\n    User-&gt;&gt;FE: \u9ede\u64ca\u300c\u767b\u5165\u300d\n    FE-&gt;&gt;SSO: GET \/sso\/prelogin?redirect_url=[\u524d\u7aef\u56de\u547c\u7db2\u5740]\n    SSO-&gt;&gt;User: \u986f\u793a\u767b\u5165\u9801\uff08Google \u7b49\uff09\n    User-&gt;&gt;SSO: \u5b8c\u6210\u8eab\u5206\u9a57\u8b49\n    SSO-&gt;&gt;FE: 302 \u8df3\u8f49\u81f3 redirect_url?code=[\u4e00\u6b21\u6027\u6388\u6b0a\u78bc]\n    FE-&gt;&gt;BE: POST \/api\/auth-tokens\/exchange&lt;br\/&gt;(Header: X-Client-Token: code)\n    BE-&gt;&gt;SSO: POST \/sso\/verify-code&lt;br\/&gt;(Header: X-Client-Id, X-Client-Secret | Body: code)\n    SSO--&gt;&gt;BE: \u50b3\u56de\u4f7f\u7528\u8005\u8cc7\u6599 (email, name, picture)\n    BE-&gt;&gt;DB: \u67e5\u8a62\u6216\u5efa\u7acb\u4f7f\u7528\u8005\n    BE--&gt;&gt;FE: 200 OK&lt;br\/&gt;(Header: X-Auth-Token: JWT)\n    FE-&gt;&gt;BE: \u5f8c\u7e8c API \u8acb\u6c42&lt;br\/&gt;(Header: Authorization: Bearer JWT)<\/pre><\/div>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\">\n\n\n\n<h2 class=\"wp-block-heading\" id=\"api%e8%aa%aa%e6%98%8e\">API\u8aaa\u660e<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"sso-%e7%99%bb%e5%85%a5%e5%95%86%e6%99%ba%e4%b8%ad%e5%bf%83\">SSO \u767b\u5165\u5546\u667a\u4e2d\u5fc3<\/h3>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>\u7aef\u9ede<\/th><th>\u65b9\u6cd5<\/th><th>\u8aaa\u660e<\/th><\/tr><\/thead><tbody><tr><td><code>\/sso\/prelogin<\/code><\/td><td>GET<\/td><td>\u767c\u8d77\u767b\u5165\uff0c\u53c3\u6578 <code>redirect_url<\/code> \u70ba\u767b\u5165\u6210\u529f\u5f8c\u7684\u524d\u7aef\u56de\u547c\u7db2\u5740<\/td><\/tr><tr><td><code>\/sso\/verify-code<\/code><\/td><td>POST<\/td><td>\u4ee5\u6388\u6b0a\u78bc\u63db\u53d6\u4f7f\u7528\u8005\u8cc7\u8a0a\uff08\u50c5\u9650\u5f8c\u7aef\u547c\u53eb\uff09<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>verify-code \u8acb\u6c42\u683c\u5f0f\uff1a<\/strong><\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">POST {SSO_BASE_URL}\/sso\/verify-code\nContent-Type: text\/plain\nX-Client-Id: {client-id}\nX-Client-Secret: {client-secret}\n\n{code}<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\"><strong>\u6210\u529f\u56de\u61c9\u7bc4\u4f8b\uff1a<\/strong><\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"JSON \/ JSON with Comments\" data-shcb-language-slug=\"json\"><span><code class=\"hljs language-json\">{\n  <span class=\"hljs-attr\">\"email\"<\/span>: <span class=\"hljs-string\">\"s12345678@ntub.edu.tw\"<\/span>,\n  <span class=\"hljs-attr\">\"name\"<\/span>: <span class=\"hljs-string\">\"\u738b\u5c0f\u660e\"<\/span>,\n  <span class=\"hljs-attr\">\"picture\"<\/span>: <span class=\"hljs-string\">\"https:\/\/...\"<\/span>\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JSON \/ JSON with Comments<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">json<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\"><strong>\u5931\u6557\u56de\u61c9\u7bc4\u4f8b\uff1a<\/strong><\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"JSON \/ JSON with Comments\" data-shcb-language-slug=\"json\"><span><code class=\"hljs language-json\">{\n  <span class=\"hljs-attr\">\"error\"<\/span>: <span class=\"hljs-string\">\"invalid_or_expired_code\"<\/span>\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JSON \/ JSON with Comments<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">json<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h3 class=\"wp-block-heading\" id=\"%e7%b3%bb%e7%b5%b1%e5%be%8c%e7%ab%af%e8%99%95%e7%90%86\">\u7cfb\u7d71\u5f8c\u7aef\u8655\u7406<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>API\uff1a\u4ea4\u63db Token<\/strong><\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">POST \/api\/auth-tokens\/exchange\nX-Client-Token: {SSO \u56de\u50b3\u7684 code}<\/code><\/span><\/pre>\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>\u9805\u76ee<\/th><th>\u8aaa\u660e<\/th><\/tr><\/thead><tbody><tr><td>\u8a8d\u8b49\u9700\u6c42<\/td><td>\u4e0d\u9700\u8981 Bearer Token\uff08\u516c\u958b\u7aef\u9ede\uff09<\/td><\/tr><tr><td>\u8acb\u6c42 Header<\/td><td><code>X-Client-Token<\/code>\uff1aSSO \u56de\u50b3\u7684\u4e00\u6b21\u6027\u6388\u6b0a\u78bc<\/td><\/tr><tr><td>\u6210\u529f\u56de\u61c9 Header<\/td><td><code>X-Auth-Token<\/code>\uff1a\u672c\u7cfb\u7d71\u6838\u767c\u7684 JWT<\/td><\/tr><tr><td>\u6210\u529f\u56de\u61c9 Body<\/td><td><code>{ \"message\": \"\u767b\u5165\u6210\u529f\", ... }<\/code><\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">\u5be6\u4f5c\u4f4d\u7f6e\uff1a<code>AuthController.exchangeToken()<\/code> \u2192 <code>AuthServiceImpl.ssoLogin()<\/code><\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>\u8655\u7406\u908f\u8f2f<\/strong><\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>\u9a57\u8b49 <code>code<\/code> \u4e0d\u70ba\u7a7a<\/li>\n\n\n\n<li>\u5411 SSO <code>\/sso\/verify-code<\/code> \u767c\u9001 POST \u8acb\u6c42<\/li>\n\n\n\n<li>\u89e3\u6790\u56de\u61c9\uff0c\u53d6\u5f97 <code>email<\/code>\u3001<code>name<\/code>\u3001<code>picture<\/code><\/li>\n\n\n\n<li>\u4f9d <code>schoolEmail<\/code> \u67e5\u8a62\u672c\u5730\u4f7f\u7528\u8005\uff1a<\/li>\n<\/ol>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>\u5df2\u5b58\u5728<\/strong>\uff1a\u540c\u6b65\u66f4\u65b0 <code>name<\/code>\u3001<code>picture<\/code>\uff08\u82e5\u6709\u8b8a\u66f4\uff09<\/li>\n\n\n\n<li><strong>\u4e0d\u5b58\u5728<\/strong>\uff1a\u81ea\u52d5\u5efa\u7acb\u65b0\u4f7f\u7528\u8005<\/li>\n<\/ul>\n\n\n\n<ol class=\"wp-block-list\">\n<li>\u4ee5\u672c\u5730\u4f7f\u7528\u8005\u8cc7\u6599\u7522\u751f JWT\uff0c\u56de\u50b3\u7d66\u524d\u7aef<\/li>\n<\/ol>\n\n\n\n<ol class=\"wp-block-list\"><\/ol>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>\u65b0\u4f7f\u7528\u8005\u9810\u8a2d\u503c<\/strong><\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>\u6b04\u4f4d<\/th><th>\u503c<\/th><\/tr><\/thead><tbody><tr><td><code>studentId<\/code><\/td><td>\u53d6\u81ea email <code>@<\/code> \u524d\u7684\u5b57\u4e32<\/td><\/tr><tr><td><code>schoolEmail<\/code><\/td><td>SSO \u56de\u50b3\u7684 email<\/td><\/tr><tr><td><code>identity<\/code><\/td><td><code>TEACHER_STUDENT<\/code>\uff08\u5728\u8077\u5e2b\u751f\uff09<\/td><\/tr><tr><td><code>status<\/code><\/td><td><code>true<\/code>\uff08\u555f\u7528\uff09<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"jwt-%e8%aa%8d%e8%ad%89%e6%a9%9f%e5%88%b6\">JWT \u8a8d\u8b49\u6a5f\u5236<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">SSO \u767b\u5165\u5b8c\u6210\u5f8c\uff0c\u5f8c\u7e8c\u7684 API \u8acb\u6c42\u7686\u4f7f\u7528\u672c\u7cfb\u7d71\u81ea\u884c\u6838\u767c\u7684 JWT\uff0c\u8207 SSO \u7121\u95dc\u3002<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>\u8acb\u6c42\u683c\u5f0f\uff1a<\/strong><\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"HTTP\" data-shcb-language-slug=\"http\"><span><code class=\"hljs language-http\"><span class=\"hljs-attribute\">Authorization<\/span>: Bearer {JWT}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTTP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">http<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\"><strong>JWT Payload \u6b04\u4f4d\uff1a<\/strong><\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Claim<\/th><th>\u8aaa\u660e<\/th><\/tr><\/thead><tbody><tr><td><code>sub<\/code><\/td><td>\u4f7f\u7528\u8005\u5b78\u6821\u4fe1\u7bb1\uff08<code>schoolEmail<\/code>\uff09<\/td><\/tr><tr><td><code>id<\/code><\/td><td>\u672c\u5730\u4f7f\u7528\u8005 ID<\/td><\/tr><tr><td><code>name<\/code><\/td><td>\u986f\u793a\u540d\u7a31<\/td><\/tr><tr><td><code>picture<\/code><\/td><td>\u982d\u50cf URL<\/td><\/tr><tr><td><code>role<\/code><\/td><td>Spring Security \u89d2\u8272\uff0c\u683c\u5f0f\u70ba <code>ROLE_{IdentityRole}<\/code><\/td><\/tr><tr><td><code>host<\/code><\/td><td>\u4f9d\u4fe1\u7bb1\u63a8\u65b7\u7684\u7cfb\u6240\u540d\u7a31<\/td><\/tr><tr><td><code>iat<\/code> \/ <code>exp<\/code><\/td><td>\u7c3d\u767c\u6642\u9593 \/ \u904e\u671f\u6642\u9593\uff08\u9810\u8a2d 24 \u5c0f\u6642\uff09<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>\u76f8\u95dc\u5143\u4ef6\uff1a<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>JwtProvider<\/code>\uff1a\u7522\u751f\u8207\u9a57\u8b49 JWT<\/li>\n\n\n\n<li><code>JwtAuthenticationFilter<\/code>\uff1a\u5f9e <code>Authorization<\/code> Header \u89e3\u6790 JWT\uff0c\u8a2d\u5b9a Spring Security Context<\/li>\n\n\n\n<li><code>SecurityConfig<\/code>\uff1a<code>\/auth-tokens\/**<\/code> \u70ba\u516c\u958b\u7aef\u9ede\uff0c\u5176\u9918\u7aef\u9ede\u7531 Filter \u8655\u7406\u8a8d\u8b49<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\">\n\n\n\n<h2 class=\"wp-block-heading\" id=\"%e7%92%b0%e5%a2%83%e5%8f%83%e6%95%b8\">\u74b0\u5883\u53c3\u6578<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">\u8a2d\u5b9a\u6a94\uff1a<code>src\/main\/resources\/application.yml<\/code><\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"YAML\" data-shcb-language-slug=\"yaml\"><span><code class=\"hljs language-yaml\"><span class=\"hljs-attr\">sso:<\/span>\n  <span class=\"hljs-attr\">base-url:<\/span> <span class=\"hljs-string\">${SSO_BASE_URL:https:\/\/sso.ntubimdbirc.tw}<\/span>\n  <span class=\"hljs-attr\">client-id:<\/span> <span class=\"hljs-string\">${SSO_CLIENT_ID:campus-activity}<\/span>\n  <span class=\"hljs-attr\">client-secret:<\/span> <span class=\"hljs-string\">${SSO_CLIENT_SECRET}<\/span>\n\n<span class=\"hljs-attr\">application:<\/span>\n  <span class=\"hljs-attr\">security:<\/span>\n    <span class=\"hljs-attr\">jwt:<\/span>\n      <span class=\"hljs-attr\">secret-key:<\/span> <span class=\"hljs-string\">${spring.security.jwt.secret}<\/span>\n      <span class=\"hljs-attr\">expiration:<\/span> <span class=\"hljs-number\">86400000<\/span>  <span class=\"hljs-comment\"># 24 \u5c0f\u6642\uff08\u6beb\u79d2\uff09<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">YAML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">yaml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>\u74b0\u5883\u8b8a\u6578<\/th><th>\u8aaa\u660e<\/th><th>\u9810\u8a2d\u503c(\u7bc4\u4f8b)<\/th><\/tr><\/thead><tbody><tr><td><code>SSO_BASE_URL<\/code><\/td><td>SSO \u767b\u5165\u4e2d\u5fc3 Base URL<\/td><td><code>https:\/\/sso.ntubimdbirc.tw<\/code><\/td><\/tr><tr><td><code>SSO_CLIENT_ID<\/code><\/td><td>\u5411 SSO \u8a3b\u518a\u7684 Client ID<\/td><td><code>campus-activity<\/code><\/td><\/tr><tr><td><code>SSO_CLIENT_SECRET<\/code><\/td><td>Client Secret\uff08<strong>\u5fc5\u586b\uff0c\u52ff\u63d0\u4ea4Git<\/strong>\uff09<\/td><td>\u2014<\/td><\/tr><tr><td><code>spring.security.jwt.secret<\/code><\/td><td>JWT \u7c3d\u7ae0\u91d1\u9470\uff08Base64\uff0c\u2265 512 bits\uff09<\/td><td>\u898b application.yml<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\"><strong>\u5b89\u5168\u63d0\u9192\uff1a<\/strong> <code>SSO_CLIENT_SECRET<\/code> \u8207 JWT Secret \u61c9\u900f\u904e\u74b0\u5883\u8b8a\u6578\u6216\u5bc6\u9470\u7ba1\u7406\u670d\u52d9\u6ce8\u5165\uff0c\u4e0d\u53ef\u5beb\u5165\u524d\u7aef\u7a0b\u5f0f\u78bc\u6216\u516c\u958b\u5132\u5b58\u5eab\u3002<\/p>\n<\/blockquote>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\">\n\n\n\n<h2 class=\"wp-block-heading\" id=\"%e9%8c%af%e8%aa%a4%e8%99%95%e7%90%86\">\u932f\u8aa4\u8655\u7406<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">\u6240\u6709 SSO \u76f8\u95dc\u932f\u8aa4\u7531 <code>SsoException<\/code> \u62cb\u51fa\uff0c\u4e26\u7531 <code>ExceptionHandleController<\/code> \u7d71\u4e00\u56de\u61c9\uff1a<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">{\n  <span class=\"hljs-string\">\"errorCode\"<\/span>: <span class=\"hljs-string\">\"SSO_ERROR\"<\/span>,\n  <span class=\"hljs-string\">\"message\"<\/span>: <span class=\"hljs-string\">\"{\u932f\u8aa4\u4ee3\u78bc}\"<\/span>,\n  <span class=\"hljs-string\">\"status\"<\/span>: {HTTP \u72c0\u614b\u78bc}\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>message<\/th><th>HTTP \u72c0\u614b<\/th><th>\u8aaa\u660e<\/th><\/tr><\/thead><tbody><tr><td><code>missing_code<\/code><\/td><td>400<\/td><td>\u672a\u63d0\u4f9b <code>X-Client-Token<\/code><\/td><\/tr><tr><td><code>unauthorized<\/code><\/td><td>401<\/td><td>SSO \u56de\u50b3 <code>invalid_or_expired_code<\/code>\uff08code \u7121\u6548\u6216\u5df2\u904e\u671f\uff09<\/td><\/tr><tr><td><code>bad_request<\/code><\/td><td>400<\/td><td>SSO \u56de\u50b3\u5176\u4ed6\u932f\u8aa4<\/td><\/tr><tr><td><code>sso_empty_response<\/code><\/td><td>502<\/td><td>SSO \u56de\u61c9\u70ba\u7a7a<\/td><\/tr><tr><td><code>sso_missing_info<\/code><\/td><td>502<\/td><td>SSO \u56de\u61c9\u7f3a\u5c11 email<\/td><\/tr><tr><td><code>sso_response_parse_error<\/code><\/td><td>502<\/td><td>SSO \u56de\u61c9 JSON \u89e3\u6790\u5931\u6557<\/td><\/tr><tr><td><code>error_login<\/code><\/td><td>502<\/td><td>SSO \u9023\u7dda\u5931\u6557\u6216\u5176\u4ed6\u672a\u9810\u671f\u932f\u8aa4<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\">\n\n\n\n<h2 class=\"wp-block-heading\" id=\"%e5%89%8d%e7%ab%af%e6%95%b4%e5%90%88%e6%96%87%e4%bb%b6\">\u524d\u7aef\u6574\u5408\u6587\u4ef6<\/h2>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\">\u8a73\u60c5\u53ef\u53c3\u8003: <a href=\"https:\/\/hyc.eshachem.com\/program\/birc\/sso-%e5%96%ae%e4%b8%80%e7%99%bb%e5%85%a5%e6%8a%80%e8%a1%93%e6%96%87%e4%bb%b6-next-js\/\">Next.js \u2013 SSO in BIRC<\/a> \uff0c\u9019\u88e1\u7c21\u55ae\u505a\u500b\u4ecb\u7d39\u3002<\/p>\n<\/blockquote>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"%e7%99%bc%e8%b5%b7%e7%99%bb%e5%85%a5\">\u767c\u8d77\u767b\u5165<\/h3>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">const<\/span> SSO_BASE_URL = <span class=\"hljs-string\">'https:\/\/sso.ntubimdbirc.tw'<\/span>;\n<span class=\"hljs-keyword\">const<\/span> redirectUrl = <span class=\"hljs-built_in\">encodeURIComponent<\/span>(<span class=\"hljs-string\">'https:\/\/your-frontend.example.com\/callback'<\/span>);\n\n<span class=\"hljs-built_in\">window<\/span>.location.href = <span class=\"hljs-string\">`<span class=\"hljs-subst\">${SSO_BASE_URL}<\/span>\/sso\/prelogin?redirect_url=<span class=\"hljs-subst\">${redirectUrl}<\/span>`<\/span>;<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h3 class=\"wp-block-heading\" id=\"%e8%99%95%e7%90%86%e5%9b%9e%e5%91%bc\">\u8655\u7406\u56de\u547c<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">SSO \u767b\u5165\u6210\u529f\u5f8c\uff0c\u6703\u5c07\u4f7f\u7528\u8005\u5c0e\u5411 <code>redirect_url?code={\u6388\u6b0a\u78bc}<\/code>\u3002<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">const<\/span> urlParams = <span class=\"hljs-keyword\">new<\/span> URLSearchParams(<span class=\"hljs-built_in\">window<\/span>.location.search);\n<span class=\"hljs-keyword\">const<\/span> code = urlParams.get(<span class=\"hljs-string\">'code'<\/span>);\n<span class=\"hljs-keyword\">const<\/span> error = urlParams.get(<span class=\"hljs-string\">'error'<\/span>);\n\n<span class=\"hljs-keyword\">if<\/span> (error) {\n  <span class=\"hljs-comment\">\/\/ \u8655\u7406\u767b\u5165\u5931\u6557<\/span>\n} <span class=\"hljs-keyword\">else<\/span> <span class=\"hljs-keyword\">if<\/span> (code) {\n  <span class=\"hljs-keyword\">await<\/span> exchangeToken(code);\n  <span class=\"hljs-comment\">\/\/ \u5efa\u8b70\u6e05\u9664 URL \u4e2d\u7684 code \u53c3\u6578<\/span>\n  <span class=\"hljs-built_in\">window<\/span>.history.replaceState({}, <span class=\"hljs-built_in\">document<\/span>.title, <span class=\"hljs-built_in\">window<\/span>.location.pathname);\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h3 class=\"wp-block-heading\" id=\"%e5%90%91%e5%be%8c%e7%ab%af%e4%ba%a4%e6%8f%9b-jwt\">\u5411\u5f8c\u7aef\u4ea4\u63db JWT<\/h3>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">async<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">exchangeToken<\/span>(<span class=\"hljs-params\">code<\/span>) <\/span>{\n  <span class=\"hljs-keyword\">const<\/span> response = <span class=\"hljs-keyword\">await<\/span> fetch(<span class=\"hljs-string\">'https:\/\/your-api.example.com\/api\/auth-tokens\/exchange'<\/span>, {\n    <span class=\"hljs-attr\">method<\/span>: <span class=\"hljs-string\">'POST'<\/span>,\n    <span class=\"hljs-attr\">headers<\/span>: {\n      <span class=\"hljs-string\">'X-Client-Token'<\/span>: code,\n    },\n  });\n\n  <span class=\"hljs-keyword\">if<\/span> (!response.ok) {\n    <span class=\"hljs-keyword\">throw<\/span> <span class=\"hljs-keyword\">new<\/span> <span class=\"hljs-built_in\">Error<\/span>(<span class=\"hljs-string\">'\u767b\u5165\u5931\u6557'<\/span>);\n  }\n\n  <span class=\"hljs-keyword\">const<\/span> jwt = response.headers.get(<span class=\"hljs-string\">'X-Auth-Token'<\/span>);\n  <span class=\"hljs-comment\">\/\/ \u5132\u5b58 JWT\uff08\u5efa\u8b70\u4f7f\u7528 httpOnly cookie \u6216\u5b89\u5168\u7684 storage \u7b56\u7565\uff09<\/span>\n  localStorage.setItem(<span class=\"hljs-string\">'authToken'<\/span>, jwt);\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h3 class=\"wp-block-heading\" id=\"%e5%91%bc%e5%8f%ab%e5%8f%97%e4%bf%9d%e8%ad%b7-api\">\u547c\u53eb\u53d7\u4fdd\u8b77 API<\/h3>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">const<\/span> response = <span class=\"hljs-keyword\">await<\/span> fetch(<span class=\"hljs-string\">'https:\/\/your-api.example.com\/api\/...'<\/span>, {\n  <span class=\"hljs-attr\">headers<\/span>: {\n    <span class=\"hljs-string\">'Authorization'<\/span>: <span class=\"hljs-string\">`Bearer <span class=\"hljs-subst\">${localStorage.getItem(<span class=\"hljs-string\">'authToken'<\/span>)}<\/span>`<\/span>,\n  },\n});<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h3 class=\"wp-block-heading\" id=\"cors-%e6%b3%a8%e6%84%8f%e4%ba%8b%e9%a0%85\">CORS \u6ce8\u610f\u4e8b\u9805<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">\u5f8c\u7aef\u5df2\u5c07 <code>X-Auth-Token<\/code> \u52a0\u5165 CORS <code>exposedHeaders<\/code>\uff0c\u524d\u7aef\u53ef\u5f9e Response Header \u8b80\u53d6 JWT\u3002\u82e5\u524d\u7aef\u8207\u5f8c\u7aef\u8de8\u57df\uff0c\u8acb\u78ba\u8a8d\u524d\u7aef\u5141\u8a31\u8b80\u53d6\u6b64 Header\u3002<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\">\n\n\n\n<h2 class=\"wp-block-heading\" id=\"%e8%ba%ab%e5%88%86%e6%ac%8a%e9%99%90\">\u8eab\u5206\u6b0a\u9650<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">SSO \u9996\u6b21\u767b\u5165\u5efa\u7acb\u7684\u4f7f\u7528\u8005\u9810\u8a2d\u70ba <code>TEACHER_STUDENT<\/code>\u3002\u7ba1\u7406\u54e1\u89d2\u8272\u9700\u7531\u7cfb\u7d71\u53e6\u884c\u6307\u6d3e\u3002<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>\u679a\u8209\u503c<\/th><th>\u4ee3\u78bc<\/th><th>\u8aaa\u660e<\/th><\/tr><\/thead><tbody><tr><td><code>EXTERNAL<\/code><\/td><td>0<\/td><td>\u8a2a\u5ba2<\/td><\/tr><tr><td><code>TEACHER_STUDENT<\/code><\/td><td>1<\/td><td>\u5728\u8077\u5e2b\u751f\uff08SSO \u65b0\u4f7f\u7528\u8005\u9810\u8a2d\uff09<\/td><\/tr><tr><td><code>ACTIVITY_ADMIN<\/code><\/td><td>2<\/td><td>\u6d3b\u52d5\u7ba1\u7406\u54e1<\/td><\/tr><tr><td><code>VENUE_ADMIN<\/code><\/td><td>3<\/td><td>\u5834\u5730\u7ba1\u7406\u54e1<\/td><\/tr><tr><td><code>ADMIN<\/code><\/td><td>4<\/td><td>\u7cfb\u7d71\u7ba1\u7406\u54e1<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">JWT \u4e2d\u7684 <code>role<\/code> claim \u6703\u5e36\u6709 <code>ROLE_<\/code> \u524d\u7db4\uff0c\u4f9b Spring Security \u6388\u6b0a\u5224\u65b7\u4f7f\u7528\u3002<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\">\n\n\n\n<h2 class=\"wp-block-heading\" id=\"%e6%9c%ac%e5%9c%b0%e6%b8%ac%e8%a9%a6\">\u672c\u5730\u6e2c\u8a66<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">\u5c08\u6848\u5167\u63d0\u4f9b\u6a21\u64ec\u6e2c\u8a66\u9801\u9762(\u53ef\u65bcGitHub\u4e0b\u8f09)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u6b64\u6e2c\u8a66\u9801\u9762\u793a\u7bc4\u5b8c\u6574 SSO \u6d41\u7a0b\u7684\u524d\u7aef\u884c\u70ba\u3002\u5176\u4e2d\u300c\u6b65\u9a5f\u4e09\u300d\u76f4\u63a5\u5728\u524d\u7aef\u547c\u53eb SSO verify-code <strong>\u50c5\u4f9b\u958b\u767c\u9664\u932f<\/strong>\uff0c\u6b63\u5f0f\u74b0\u5883\u61c9\u6539\u70ba\u547c\u53eb\u672c\u5f8c\u7aef\u7684 <code>\/auth-tokens\/exchange<\/code>\u3002<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Swagger UI\uff1a<\/strong> \u555f\u52d5\u5f8c\u53ef\u65bc <code>\/api<\/code> \u627e\u5230 <code>Authentication<\/code> \u7fa4\u7d44\u4e0b\u7684 <code>POST \/auth-tokens\/exchange<\/code> \u7aef\u9ede\u9032\u884c\u6e2c\u8a66\u3002<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"%e5%8e%9f%e5%a7%8b%e7%a2%bc%e7%b4%a2%e5%bc%95\">\u539f\u59cb\u78bc\u7d22\u5f15<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/github.com\/Chen11111112\/SSO-in-BIRC-with-Spring-Boot.git\">https:\/\/github.com\/Chen11111112\/SSO-in-BIRC-with-Spring-Boot.git<\/a><\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>\u6a94\u6848<\/th><th>\u8077\u8cac<\/th><\/tr><\/thead><tbody><tr><td><code>AuthController.java<\/code><\/td><td>SSO code \u4ea4\u63db API \u7aef\u9ede<\/td><\/tr><tr><td><code>AuthServiceImpl.java<\/code><\/td><td>SSO \u9a57\u8b49\u3001\u4f7f\u7528\u8005\u5efa\u7acb\/\u66f4\u65b0\u3001JWT \u6838\u767c<\/td><\/tr><tr><td><code>JwtProvider.java<\/code><\/td><td>JWT \u7522\u751f\u8207\u9a57\u8b49<\/td><\/tr><tr><td><code>JwtAuthenticationFilter.java<\/code><\/td><td>\u8acb\u6c42\u6514\u622a\u8207 JWT \u89e3\u6790<\/td><\/tr><tr><td><code>SecurityConfig.java<\/code><\/td><td>Spring Security \u8207 CORS \u8a2d\u5b9a<\/td><\/tr><tr><td><code>SsoException.java<\/code><\/td><td>SSO \u932f\u8aa4\u4f8b\u5916<\/td><\/tr><tr><td><code>ExceptionHandleController.java<\/code><\/td><td>\u7d71\u4e00\u932f\u8aa4\u56de\u61c9<\/td><\/tr><tr><td><code>application.yml<\/code><\/td><td>SSO \u8207 JWT \u8a2d\u5b9a<\/td><\/tr><\/tbody><\/table><\/figure>\n","protected":false},"excerpt":{"rendered":"<p>\u672c\u6587\u4ef6\u900f\u904e\u5546\u696d\u667a\u6167\u7814\u7a76\u4e2d\u5fc3 \u2013 \u667a\u6167\u6821\u5712:\u6d3b\u52d5\u7cfb\u7d71\u4f86\u8aaa\u660e\u5546\u667a\u4e2d\u5fc3\u7684SSO\u67b6\u69cb\u3002 \u5546\u667a\u4e2d\u5fc3\u7684SSO\u70ba\u81ea\u67b6\u4f3a\u670d\u5668\uff0c\u975eGoogle SSO\uff0c\u4f46\u539f\u7406\u5927\u540c\u5c0f\u7570\u3002 \u8a72\u6587\u7ae0\u6240\u8ff0\u7a0b\u5f0f\u78bc\u7686\u63d0\u4f9b\u65bc\u6587\u7ae0\u6700\u4e0b\u65b9GitHub\u4e2d\u3002 \u7248\u672c\uff1a0.1.0\u6700\u5f8c\u66f4\u65b0\u6642\u9593\uff1a06\/10\/2026\u6700\u5f8c\u66f4\u65b0\u4eba\u54e1\uff1a\u9673\u6cd3\u6bd3 \u6982\u8ff0 \u63a1\u7528 \u6388\u6b0a\u78bc\uff08Authorization Code\uff09\u4ea4\u63db \u6a21\u5f0f\u8655\u7406\u4e26\u9a57\u8b49\u6191\u8b49\uff1a \u89d2\u8272 \u8aaa\u660e SSO \u767b\u5165\u4e2d\u5fc3 \u96c6\u4e2d\u5f0f\u8eab\u5206\u9a57\u8b49\u670d\u52d9\uff0c\u8ca0\u8cac Google \u7b49\u5916\u90e8 IdP \u767b\u5165\u3001\u6838\u767c\u4e00\u6b21\u6027\u6388\u6b0a\u78bc \u7cfb\u7d71\u524d\u7aef \u5c0e\u5411 SSO \u767b\u5165\u3001\u63a5\u6536\u56de\u50b3\u7684 code\u3001\u8f49\u4ea4\u5f8c\u7aef\u4ea4\u63db JWT \u7cfb\u7d71\u5f8c\u7aef \u4ee5 Client Secret \u5411 SSO \u9a57\u8b49 code\u3001\u5efa\u7acb\/\u66f4\u65b0\u672c\u5730\u4f7f\u7528\u8005\u3001\u6838\u767c\u61c9\u7528\u7a0b\u5f0f JWT \u8a2d\u8a08\u539f\u5247\uff1a Client Secret \u50c5\u5b58\u65bc\u5f8c\u7aef\uff0c\u524d\u7aef\u6c38\u9060\u4e0d\u61c9\u76f4\u63a5\u547c\u53eb SSO \u7684 \/sso\/verify-code\u3002(\u907f\u514d\u88abXSS) JWT\u53ef\u53c3\u8003: https:\/\/hyc.eshachem.com\/program\/291-2\/ \u6574\u9ad4\u767b\u5165\u6d41\u7a0b API\u8aaa\u660e SSO \u767b\u5165\u5546\u667a\u4e2d\u5fc3 \u7aef\u9ede \u65b9\u6cd5 \u8aaa\u660e \/sso\/prelogin GET \u767c\u8d77\u767b\u5165\uff0c\u53c3\u6578 [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":0,"parent":579,"menu_order":-1,"comment_status":"closed","ping_status":"closed","template":"","meta":{"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"footnotes":""},"class_list":["post-532","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/hyc.eshachem.com\/program\/wp-json\/wp\/v2\/pages\/532","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/hyc.eshachem.com\/program\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/hyc.eshachem.com\/program\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/hyc.eshachem.com\/program\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/hyc.eshachem.com\/program\/wp-json\/wp\/v2\/comments?post=532"}],"version-history":[{"count":20,"href":"https:\/\/hyc.eshachem.com\/program\/wp-json\/wp\/v2\/pages\/532\/revisions"}],"predecessor-version":[{"id":603,"href":"https:\/\/hyc.eshachem.com\/program\/wp-json\/wp\/v2\/pages\/532\/revisions\/603"}],"up":[{"embeddable":true,"href":"https:\/\/hyc.eshachem.com\/program\/wp-json\/wp\/v2\/pages\/579"}],"wp:attachment":[{"href":"https:\/\/hyc.eshachem.com\/program\/wp-json\/wp\/v2\/media?parent=532"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}