[{"data":1,"prerenderedAt":592},["ShallowReactive",2],{"blog-kyoto-spring-diary":3,"blog-adjacent-kyoto-spring-diary":67,"blog-related-kyoto-spring-diary":591},{"id":4,"title":5,"body":6,"category":51,"cover":52,"date":53,"description":54,"draft":55,"extension":56,"meta":57,"navigation":58,"path":59,"readingTime":60,"seo":61,"stem":62,"tags":63,"__hash__":66},"blogs\u002Fblogs\u002Fkyoto-spring-diary.md","Kyoto in Spring",{"type":7,"value":8,"toc":44},"minimark",[9,13,18,21,24,28,31,34,38,41],[10,11,12],"p",{},"Spring in Kyoto is everything you've been told it is, and also nothing like it.",[14,15,17],"h2",{"id":16},"arashiyama-at-6-am","Arashiyama at 6 AM",[10,19,20],{},"Everyone photographs the bamboo grove. Almost no one does it at dawn. I walked in alone at six in the morning, mist still hanging between the stalks, the only sound my own footsteps on gravel. By nine the crowds would be three-deep on the path.",[10,22,23],{},"If you go, go early. If you can't go early, go in the rain.",[14,25,27],{"id":26},"a-breakfast-ritual","A breakfast ritual",[10,29,30],{},"Every morning I walked ten minutes from the guesthouse to a small cafe called Kissa Madoka. The owner, an old woman who spoke no English, would nod me toward my usual seat by the window. Thick toast, a soft-boiled egg, a small salad, and a pot of pour-over coffee.",[10,32,33],{},"She never asked what I wanted. She remembered from day two.",[14,35,37],{"id":36},"the-quiet-temples","The quiet temples",[10,39,40],{},"Everyone visits Kinkaku-ji and Fushimi Inari. They're worth it — barely. The temples that stayed with me were the ones whose names I never learned.",[10,42,43],{},"Travel, I think, is really about collecting these.",{"title":45,"searchDepth":46,"depth":46,"links":47},"",2,[48,49,50],{"id":16,"depth":46,"text":17},{"id":26,"depth":46,"text":27},{"id":36,"depth":46,"text":37},"travel","\u002Fimages\u002Ftravel\u002Fhome-placeholder-3.jpg","2026-04-02","Ten days of temples, noodles, and quiet mornings in old wooden houses. A slower kind of travel.",false,"md",{},true,"\u002Fblogs\u002Fkyoto-spring-diary",8,{"title":5,"description":54},"blogs\u002Fkyoto-spring-diary",[64,65],"Japan","Kyoto","QjUi5_JuDS8CmJs1x1Wyac5KvBLt7PcO9Yh6k4oLlIc",{"prev":68,"next":590},{"id":69,"title":70,"body":71,"category":577,"cover":578,"date":579,"description":580,"draft":55,"extension":56,"meta":581,"navigation":58,"path":582,"readingTime":190,"seo":583,"stem":584,"tags":585,"__hash__":589},"blogs\u002Fblogs\u002Fnuxt-dotnet-jwt-auth.md","Nuxt 3 与 .NET 8 的 JWT 认证实践",{"type":7,"value":72,"toc":570},[73,77,80,83,106,110,113,212,216,227,530,534,541,544,560,563,566],[14,74,76],{"id":75},"为什么选-jwt","为什么选 JWT",[10,78,79],{},"在前后端分离的项目里，JWT 是最常见的认证方案之一。它把用户身份编码到一个自包含的 token 里，服务器无需维护 session 状态，天然适合分布式部署。",[10,81,82],{},"但 JWT 不是银弹。它有三个必须正视的问题：",[84,85,86,94,100],"ol",{},[87,88,89,93],"li",{},[90,91,92],"strong",{},"token 一旦签发无法吊销","，除非维护 blacklist",[87,95,96,99],{},[90,97,98],{},"payload 可被解码","（base64 不是加密），不能放敏感信息",[87,101,102,105],{},[90,103,104],{},"过期时间权衡","：短则频繁登录，长则安全风险",[14,107,109],{"id":108},"后端net-8-配置","后端：.NET 8 配置",[10,111,112],{},"在 Program.cs 里加入 JWT 认证：",[114,115,119],"pre",{"className":116,"code":117,"language":118,"meta":45,"style":45},"language-csharp shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)\n    .AddJwtBearer(options =>\n    {\n        options.TokenValidationParameters = new TokenValidationParameters\n        {\n            ValidateIssuer = true,\n            ValidateAudience = true,\n            ValidateLifetime = true,\n            ValidateIssuerSigningKey = true,\n            ValidIssuer = builder.Configuration[\"Jwt:Issuer\"],\n            ValidAudience = builder.Configuration[\"Jwt:Audience\"],\n            IssuerSigningKey = new SymmetricSecurityKey(\n                Encoding.UTF8.GetBytes(builder.Configuration[\"Jwt:Key\"]))\n        };\n    });\n","csharp",[120,121,122,130,135,141,147,153,159,165,170,176,182,188,194,200,206],"code",{"__ignoreMap":45},[123,124,127],"span",{"class":125,"line":126},"line",1,[123,128,129],{},"builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)\n",[123,131,132],{"class":125,"line":46},[123,133,134],{},"    .AddJwtBearer(options =>\n",[123,136,138],{"class":125,"line":137},3,[123,139,140],{},"    {\n",[123,142,144],{"class":125,"line":143},4,[123,145,146],{},"        options.TokenValidationParameters = new TokenValidationParameters\n",[123,148,150],{"class":125,"line":149},5,[123,151,152],{},"        {\n",[123,154,156],{"class":125,"line":155},6,[123,157,158],{},"            ValidateIssuer = true,\n",[123,160,162],{"class":125,"line":161},7,[123,163,164],{},"            ValidateAudience = true,\n",[123,166,167],{"class":125,"line":60},[123,168,169],{},"            ValidateLifetime = true,\n",[123,171,173],{"class":125,"line":172},9,[123,174,175],{},"            ValidateIssuerSigningKey = true,\n",[123,177,179],{"class":125,"line":178},10,[123,180,181],{},"            ValidIssuer = builder.Configuration[\"Jwt:Issuer\"],\n",[123,183,185],{"class":125,"line":184},11,[123,186,187],{},"            ValidAudience = builder.Configuration[\"Jwt:Audience\"],\n",[123,189,191],{"class":125,"line":190},12,[123,192,193],{},"            IssuerSigningKey = new SymmetricSecurityKey(\n",[123,195,197],{"class":125,"line":196},13,[123,198,199],{},"                Encoding.UTF8.GetBytes(builder.Configuration[\"Jwt:Key\"]))\n",[123,201,203],{"class":125,"line":202},14,[123,204,205],{},"        };\n",[123,207,209],{"class":125,"line":208},15,[123,210,211],{},"    });\n",[14,213,215],{"id":214},"前端nuxt-3-拦截器","前端：Nuxt 3 拦截器",[10,217,218,219,222,223,226],{},"在 Nuxt 里用 ",[120,220,221],{},"useFetch"," 的 ",[120,224,225],{},"onRequest"," 钩子统一注入 token：",[114,228,232],{"className":229,"code":230,"language":231,"meta":45,"style":45},"language-typescript shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","export const useApi = () => {\n  const token = useCookie('auth-token')\n\n  return $fetch.create({\n    baseURL: useRuntimeConfig().public.apiBase,\n    onRequest({ options }) {\n      if (token.value) {\n        options.headers = {\n          ...options.headers,\n          Authorization: `Bearer ${token.value}`\n        }\n      }\n    },\n    onResponseError({ response }) {\n      if (response.status === 401) {\n        navigateTo('\u002Flogin')\n      }\n    }\n  })\n}\n","typescript",[120,233,234,261,292,297,316,343,360,381,395,409,434,439,444,449,463,488,505,510,516,524],{"__ignoreMap":45},[123,235,236,240,244,248,252,255,258],{"class":125,"line":126},[123,237,239],{"class":238},"s7zQu","export",[123,241,243],{"class":242},"spNyl"," const",[123,245,247],{"class":246},"sTEyZ"," useApi ",[123,249,251],{"class":250},"sMK4o","=",[123,253,254],{"class":250}," ()",[123,256,257],{"class":242}," =>",[123,259,260],{"class":250}," {\n",[123,262,263,266,269,272,276,280,283,287,289],{"class":125,"line":46},[123,264,265],{"class":242},"  const",[123,267,268],{"class":246}," token",[123,270,271],{"class":250}," =",[123,273,275],{"class":274},"s2Zo4"," useCookie",[123,277,279],{"class":278},"swJcz","(",[123,281,282],{"class":250},"'",[123,284,286],{"class":285},"sfazB","auth-token",[123,288,282],{"class":250},[123,290,291],{"class":278},")\n",[123,293,294],{"class":125,"line":137},[123,295,296],{"emptyLinePlaceholder":58},"\n",[123,298,299,302,305,308,311,313],{"class":125,"line":143},[123,300,301],{"class":238},"  return",[123,303,304],{"class":246}," $fetch",[123,306,307],{"class":250},".",[123,309,310],{"class":274},"create",[123,312,279],{"class":278},[123,314,315],{"class":250},"{\n",[123,317,318,321,324,327,330,332,335,337,340],{"class":125,"line":149},[123,319,320],{"class":278},"    baseURL",[123,322,323],{"class":250},":",[123,325,326],{"class":274}," useRuntimeConfig",[123,328,329],{"class":278},"()",[123,331,307],{"class":250},[123,333,334],{"class":246},"public",[123,336,307],{"class":250},[123,338,339],{"class":246},"apiBase",[123,341,342],{"class":250},",\n",[123,344,345,348,351,355,358],{"class":125,"line":155},[123,346,347],{"class":278},"    onRequest",[123,349,350],{"class":250},"({",[123,352,354],{"class":353},"sHdIc"," options",[123,356,357],{"class":250}," })",[123,359,260],{"class":250},[123,361,362,365,368,371,373,376,379],{"class":125,"line":161},[123,363,364],{"class":238},"      if",[123,366,367],{"class":278}," (",[123,369,370],{"class":246},"token",[123,372,307],{"class":250},[123,374,375],{"class":246},"value",[123,377,378],{"class":278},") ",[123,380,315],{"class":250},[123,382,383,386,388,391,393],{"class":125,"line":60},[123,384,385],{"class":246},"        options",[123,387,307],{"class":250},[123,389,390],{"class":246},"headers",[123,392,271],{"class":250},[123,394,260],{"class":250},[123,396,397,400,403,405,407],{"class":125,"line":172},[123,398,399],{"class":250},"          ...",[123,401,402],{"class":246},"options",[123,404,307],{"class":250},[123,406,390],{"class":246},[123,408,342],{"class":250},[123,410,411,414,416,419,422,425,427,429,431],{"class":125,"line":178},[123,412,413],{"class":278},"          Authorization",[123,415,323],{"class":250},[123,417,418],{"class":250}," `",[123,420,421],{"class":285},"Bearer ",[123,423,424],{"class":250},"${",[123,426,370],{"class":246},[123,428,307],{"class":250},[123,430,375],{"class":246},[123,432,433],{"class":250},"}`\n",[123,435,436],{"class":125,"line":184},[123,437,438],{"class":250},"        }\n",[123,440,441],{"class":125,"line":190},[123,442,443],{"class":250},"      }\n",[123,445,446],{"class":125,"line":196},[123,447,448],{"class":250},"    },\n",[123,450,451,454,456,459,461],{"class":125,"line":202},[123,452,453],{"class":278},"    onResponseError",[123,455,350],{"class":250},[123,457,458],{"class":353}," response",[123,460,357],{"class":250},[123,462,260],{"class":250},[123,464,465,467,469,472,474,477,480,484,486],{"class":125,"line":208},[123,466,364],{"class":238},[123,468,367],{"class":278},[123,470,471],{"class":246},"response",[123,473,307],{"class":250},[123,475,476],{"class":246},"status",[123,478,479],{"class":250}," ===",[123,481,483],{"class":482},"sbssI"," 401",[123,485,378],{"class":278},[123,487,315],{"class":250},[123,489,491,494,496,498,501,503],{"class":125,"line":490},16,[123,492,493],{"class":274},"        navigateTo",[123,495,279],{"class":278},[123,497,282],{"class":250},[123,499,500],{"class":285},"\u002Flogin",[123,502,282],{"class":250},[123,504,291],{"class":278},[123,506,508],{"class":125,"line":507},17,[123,509,443],{"class":250},[123,511,513],{"class":125,"line":512},18,[123,514,515],{"class":250},"    }\n",[123,517,519,522],{"class":125,"line":518},19,[123,520,521],{"class":250},"  }",[123,523,291],{"class":278},[123,525,527],{"class":125,"line":526},20,[123,528,529],{"class":250},"}\n",[14,531,533],{"id":532},"刷新-token-的陷阱","刷新 token 的陷阱",[10,535,536,537,540],{},"很多教程只讲 access token，不讲 refresh token。实际生产环境里，",[90,538,539],{},"没有 refresh token 的 JWT 方案是不完整的","。",[10,542,543],{},"关键设计：",[545,546,547,550,557],"ul",{},[87,548,549],{},"access token：15 分钟有效期，放在内存或 cookie",[87,551,552,553,556],{},"refresh token：7 天有效期，",[90,554,555],{},"必须"," HttpOnly cookie，防止 XSS 窃取",[87,558,559],{},"刷新接口：服务端校验 refresh token，签发新的 access token",[14,561,562],{"id":562},"总结",[10,564,565],{},"JWT 认证看似简单，但实际落地有不少细节。希望这篇文章能帮你避开常见的坑。",[567,568,569],"style",{},"html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}",{"title":45,"searchDepth":46,"depth":46,"links":571},[572,573,574,575,576],{"id":75,"depth":46,"text":76},{"id":108,"depth":46,"text":109},{"id":214,"depth":46,"text":215},{"id":532,"depth":46,"text":533},{"id":562,"depth":46,"text":562},"tech","\u002Fimages\u002Ftravel\u002Fhome-placeholder-1.jpg","2026-04-10","从前端登录到后端校验，一套可落地的最佳实践，包括刷新 token、跨域 cookie 和前后端错误处理的细节。",{},"\u002Fblogs\u002Fnuxt-dotnet-jwt-auth",{"title":70,"description":580},"blogs\u002Fnuxt-dotnet-jwt-auth",[586,587,588],"Nuxt",".NET","Auth","zTBCAyICqoS0EMy1UunGXyWApv3FEjl9Jpg1kjGcs6Y",null,[],1776954008091]