<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Web on Intersteller Cabin</title><link>https://www.starsac.cn/tags/web/</link><description>Recent content in Web on Intersteller Cabin</description><generator>Hugo</generator><language>zh-cn</language><lastBuildDate>Thu, 02 Oct 2025 18:39:15 +0800</lastBuildDate><atom:link href="https://www.starsac.cn/tags/web/index.xml" rel="self" type="application/rss+xml"/><item><title>一文教会：内网穿透建站</title><link>https://www.starsac.cn/posts/how-to-self-host-websites-using-nat-traversal/</link><pubDate>Thu, 02 Oct 2025 18:39:15 +0800</pubDate><guid>https://www.starsac.cn/posts/how-to-self-host-websites-using-nat-traversal/</guid><description>&lt;h1 id="一文教会内网穿透建站"&gt;
 一文教会：内网穿透建站
 &lt;a class="heading-link" href="#%e4%b8%80%e6%96%87%e6%95%99%e4%bc%9a%e5%86%85%e7%bd%91%e7%a9%bf%e9%80%8f%e5%bb%ba%e7%ab%99"&gt;
 &lt;i class="fa-solid fa-link" aria-hidden="true" title="链接到标题"&gt;&lt;/i&gt;
 &lt;span class="sr-only"&gt;链接到标题&lt;/span&gt;
 &lt;/a&gt;
&lt;/h1&gt;
&lt;p&gt;Github Pages，CloudFlare Pages太慢？&lt;/p&gt;
&lt;p&gt;云服务器太贵？配置太差？带宽小水管？&lt;/p&gt;
&lt;p&gt;厌倦了Hexo等静态博客，想搭个高大上的动态网站？&lt;/p&gt;
&lt;p&gt;不用担心！本文教你使用sakura frp（其他类似的内网穿透服务同理）来搭建动态网站！整个过程完全免费！&lt;/p&gt;
&lt;h2 id="环境搭建"&gt;
 环境搭建
 &lt;a class="heading-link" href="#%e7%8e%af%e5%a2%83%e6%90%ad%e5%bb%ba"&gt;
 &lt;i class="fa-solid fa-link" aria-hidden="true" title="链接到标题"&gt;&lt;/i&gt;
 &lt;span class="sr-only"&gt;链接到标题&lt;/span&gt;
 &lt;/a&gt;
&lt;/h2&gt;
&lt;h3 id="硬件"&gt;
 硬件
 &lt;a class="heading-link" href="#%e7%a1%ac%e4%bb%b6"&gt;
 &lt;i class="fa-solid fa-link" aria-hidden="true" title="链接到标题"&gt;&lt;/i&gt;
 &lt;span class="sr-only"&gt;链接到标题&lt;/span&gt;
 &lt;/a&gt;
&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;HP Z240 Workstation
&lt;ul&gt;
&lt;li&gt;ChipSet：C236&lt;/li&gt;
&lt;li&gt;CPU：E3-1255v6@3.30GHz 4C4T&lt;/li&gt;
&lt;li&gt;RAM：16GB DDR4 2133MHz&lt;/li&gt;
&lt;li&gt;Storage：128GB NVMe SSD + 500GB HDD * 2（RAID1） + 128GB SATA SSD&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;PS：当然，用自己的电脑+IIS也可以XD&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="软件"&gt;
 软件
 &lt;a class="heading-link" href="#%e8%bd%af%e4%bb%b6"&gt;
 &lt;i class="fa-solid fa-link" aria-hidden="true" title="链接到标题"&gt;&lt;/i&gt;
 &lt;span class="sr-only"&gt;链接到标题&lt;/span&gt;
 &lt;/a&gt;
&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;OS：fnOS@latest&lt;/li&gt;
&lt;li&gt;虚拟化平台：fnOS自带QEMU虚拟机&lt;/li&gt;
&lt;li&gt;VMOS：Ubuntu 25.04&lt;/li&gt;
&lt;li&gt;FRP服务：sakura-frp（免费两条隧道，10Mbps带宽，可建站，签到送流量）&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;注意：有些FRP服务是不能建站的，像我之前买的9.9r 1年的FRP服务就焊死了端口号，这种没有80/443端口的也可以开web服务，但是基本只能自己用&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="安装服务器面板"&gt;
 安装服务器面板
 &lt;a class="heading-link" href="#%e5%ae%89%e8%a3%85%e6%9c%8d%e5%8a%a1%e5%99%a8%e9%9d%a2%e6%9d%bf"&gt;
 &lt;i class="fa-solid fa-link" aria-hidden="true" title="链接到标题"&gt;&lt;/i&gt;
 &lt;span class="sr-only"&gt;链接到标题&lt;/span&gt;
 &lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;推荐安装&lt;a href="https://1panel.cn/" class="external-link" target="_blank" rel="noopener"&gt;1Panel - 现代化、开源的Linux服务器运维管理面板&lt;/a&gt;，&lt;!-- raw HTML omitted --&gt;无论是从UI设计还是功能上我感觉都要比一股中年老登味的宝塔面板要好&lt;!-- raw HTML omitted --&gt;&lt;/p&gt;</description></item><item><title>FastAPI - OAuth2+JWT认证</title><link>https://www.starsac.cn/posts/fastapi-oauth-jwt-authorization/</link><pubDate>Sat, 05 Apr 2025 15:24:35 +0800</pubDate><guid>https://www.starsac.cn/posts/fastapi-oauth-jwt-authorization/</guid><description>&lt;h1 id="fastapi---oauth2jwt认证"&gt;
 FastAPI - OAuth2+JWT认证
 &lt;a class="heading-link" href="#fastapi---oauth2jwt%e8%ae%a4%e8%af%81"&gt;
 &lt;i class="fa-solid fa-link" aria-hidden="true" title="链接到标题"&gt;&lt;/i&gt;
 &lt;span class="sr-only"&gt;链接到标题&lt;/span&gt;
 &lt;/a&gt;
&lt;/h1&gt;
&lt;p&gt;OAuth2只是一种登录验证的模式，JWT只是登陆后生成，用来简化登录操作的token，是Bearer令牌的一种实现&lt;/p&gt;
&lt;h2 id="验证token"&gt;
 验证token
 &lt;a class="heading-link" href="#%e9%aa%8c%e8%af%81token"&gt;
 &lt;i class="fa-solid fa-link" aria-hidden="true" title="链接到标题"&gt;&lt;/i&gt;
 &lt;span class="sr-only"&gt;链接到标题&lt;/span&gt;
 &lt;/a&gt;
&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;引入OAuth2PasswordBearer，创建该类的示例，这个实例是一个可调用对象，会自动去Header里面找token并返回，如果找不到则抛出401Unauthorize异常&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;from&lt;/span&gt; &lt;span style="color:#ff7b72"&gt;fastapi.security&lt;/span&gt; &lt;span style="color:#ff7b72"&gt;import&lt;/span&gt; OAuth2PasswordBearer
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;oauth2_scheme &lt;span style="color:#ff7b72;font-weight:bold"&gt;=&lt;/span&gt; OAuth2PasswordBearer(tokenUrl&lt;span style="color:#ff7b72;font-weight:bold"&gt;=&lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;token&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol start="2"&gt;
&lt;li&gt;在需要认证token的路径处理函数里面声明收到token，引入oauth2_scheme依赖项，如果有token则会执行函数体&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#d2a8ff;font-weight:bold"&gt;@app.get&lt;/span&gt;(&lt;span style="color:#a5d6ff"&gt;&amp;#34;/token-string&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;async&lt;/span&gt; &lt;span style="color:#ff7b72"&gt;def&lt;/span&gt; &lt;span style="color:#d2a8ff;font-weight:bold"&gt;get_token_string&lt;/span&gt;(token: Annotated[str, Depends(oauth2_scheme)]):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff7b72"&gt;return&lt;/span&gt; {&lt;span style="color:#a5d6ff"&gt;&amp;#34;token&amp;#34;&lt;/span&gt;: token}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;此时只要我们请求头中有token（无论值是什么），就可以访问这个路由了&lt;/p&gt;
&lt;p&gt;&lt;img src="https://resources.starsac.cn/2025/04/image-20250405183414873.png" alt="image-20250405183414873"&gt;&lt;/p&gt;
&lt;h2 id="生成token"&gt;
 生成token
 &lt;a class="heading-link" href="#%e7%94%9f%e6%88%90token"&gt;
 &lt;i class="fa-solid fa-link" aria-hidden="true" title="链接到标题"&gt;&lt;/i&gt;
 &lt;span class="sr-only"&gt;链接到标题&lt;/span&gt;
 &lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;在创建oauth2_scheme时我们声明了获得token的路径，现在我们编写生成token的具体实现。&lt;/p&gt;
&lt;p&gt;OAuth2要求username和password两个必选字段必须通过表单数据的形式发送，也就是设置&lt;code&gt;Content-Type:application/x-www-form-urlencoded&lt;/code&gt;,将数据放在请求体里面&lt;/p&gt;
&lt;p&gt;以&lt;code&gt;grant_type=password&amp;amp;username=john&amp;amp;password=b&lt;/code&gt;的形式发送给后端。&lt;/p&gt;
&lt;p&gt;为了接收发送过来的参数，可以使用&lt;code&gt;OAuth2PasswordRequestForm&lt;/code&gt;，这是一个类依赖项，可以省略Depends()里面的内容&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;from&lt;/span&gt; &lt;span style="color:#ff7b72"&gt;fastapi.security&lt;/span&gt; &lt;span style="color:#ff7b72"&gt;import&lt;/span&gt; OAuth2PasswordRequestForm
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#d2a8ff;font-weight:bold"&gt;@app.post&lt;/span&gt;(&lt;span style="color:#a5d6ff"&gt;&amp;#34;/token&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;def&lt;/span&gt; &lt;span style="color:#d2a8ff;font-weight:bold"&gt;post_token&lt;/span&gt;(form_data:Annotated[OAuth2PasswordRequestForm,Depends()]):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#8b949e;font-style:italic"&gt;# 这里可以判断用户登录是否合法，返回响应结果或者抛出异常&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; name &lt;span style="color:#ff7b72;font-weight:bold"&gt;=&lt;/span&gt; form_data&lt;span style="color:#ff7b72;font-weight:bold"&gt;.&lt;/span&gt;username
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; password &lt;span style="color:#ff7b72;font-weight:bold"&gt;=&lt;/span&gt; form_data&lt;span style="color:#ff7b72;font-weight:bold"&gt;.&lt;/span&gt;password
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; user &lt;span style="color:#ff7b72;font-weight:bold"&gt;=&lt;/span&gt; user_dict&lt;span style="color:#ff7b72;font-weight:bold"&gt;.&lt;/span&gt;get(name)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; print(user, name, password)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff7b72"&gt;if&lt;/span&gt; &lt;span style="color:#ff7b72;font-weight:bold"&gt;not&lt;/span&gt; user:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff7b72"&gt;raise&lt;/span&gt; HTTPException(status_code&lt;span style="color:#ff7b72;font-weight:bold"&gt;=&lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;400&lt;/span&gt;, detail&lt;span style="color:#ff7b72;font-weight:bold"&gt;=&lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;invalidUser&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff7b72"&gt;if&lt;/span&gt; &lt;span style="color:#ff7b72;font-weight:bold"&gt;not&lt;/span&gt; password &lt;span style="color:#ff7b72;font-weight:bold"&gt;==&lt;/span&gt; user&lt;span style="color:#ff7b72;font-weight:bold"&gt;.&lt;/span&gt;get(&lt;span style="color:#a5d6ff"&gt;&amp;#34;password&amp;#34;&lt;/span&gt;):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff7b72"&gt;raise&lt;/span&gt; HTTPException(status_code&lt;span style="color:#ff7b72;font-weight:bold"&gt;=&lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;400&lt;/span&gt;, detail&lt;span style="color:#ff7b72;font-weight:bold"&gt;=&lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;invalidPasswd&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff7b72"&gt;return&lt;/span&gt; {&lt;span style="color:#a5d6ff"&gt;&amp;#34;access_token&amp;#34;&lt;/span&gt;: name, &lt;span style="color:#a5d6ff"&gt;&amp;#34;token_type&amp;#34;&lt;/span&gt;: &lt;span style="color:#a5d6ff"&gt;&amp;#34;bearer&amp;#34;&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;该路径函数的返回值格式是固定的，是OAuth2规范的一部分。&lt;/p&gt;</description></item></channel></rss>