

<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Max的程式語言筆記</title>
	<atom:link href="https://stackoverflow.max-everyday.com/feed/" rel="self" type="application/rss+xml" />
	<link>https://stackoverflow.max-everyday.com</link>
	<description>我要當一個豬頭，快樂過每一天</description>
	<lastBuildDate>Tue, 30 Jun 2026 10:40:16 +0000</lastBuildDate>
	<language>zh-TW</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.0</generator>

<image>
	<url>https://stackoverflow.max-everyday.com/wp-content/uploads/2017/02/max-stackoverflow-256.png</url>
	<title>Max的程式語言筆記</title>
	<link>https://stackoverflow.max-everyday.com</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>門神阿福的點名簿： WAF 防火牆 IP 白名單維護指南</title>
		<link>https://stackoverflow.max-everyday.com/2026/06/waf-white-ip-list/</link>
					<comments>https://stackoverflow.max-everyday.com/2026/06/waf-white-ip-list/#respond</comments>
		
		<dc:creator><![CDATA[max-stackoverflow]]></dc:creator>
		<pubDate>Fri, 26 Jun 2026 03:47:23 +0000</pubDate>
				<category><![CDATA[Azure 筆記]]></category>
		<category><![CDATA[azure]]></category>
		<guid isPermaLink="false">https://stackoverflow.max-everyday.com/?p=8609</guid>

					<description><![CDATA[嗨，各位路過的探險家！今天來聊聊我們網站的超級門...]]></description>
										<content:encoded><![CDATA[
<figure class="wp-block-image size-large"><img fetchpriority="high" decoding="async" width="1024" height="559" src="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/06/waf-white-ip-list_clean-1024x559.jpg?v=1782445631" alt="" class="wp-image-8611" srcset="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/06/waf-white-ip-list_clean-1024x559.jpg?v=1782445631 1024w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/06/waf-white-ip-list_clean-600x327.jpg?v=1782445631 600w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/06/waf-white-ip-list_clean-768x419.jpg?v=1782445631 768w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/06/waf-white-ip-list_clean.jpg?v=1782445631 1408w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">嗨，各位路過的探險家！今天來聊聊我們網站的超級門神 ── WAF 防火牆。</p>



<p class="wp-block-paragraph">這個系統就像是一個戒備森嚴的城堡，為了不讓壞人進來搗亂，門神阿福手上有一張點名簿，也就是所謂的白名單。只要你的 IP 位址不在這張點名簿上，阿福就會直接把你塞進小黑屋，連大門口都進不來。</p>



<p class="wp-block-paragraph">最厲害的是，阿福非常有個性，他只保護特定的城堡。就算隔壁的系統不小心被圍攻，我們 Project Name 這邊依然穩如泰山。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">阿福的點名簿現況</h2>



<p class="wp-block-paragraph">目前只有拿到特別通行證的網段才能順利通行：</p>



<ul class="wp-block-list">
<li>某神秘機房專區： 192.168.1.0/24</li>



<li>核心團隊的秘密基地： 192.168.2.0/24</li>



<li>測試專用通道： 192.168.3.0/24</li>



<li>管理員的個人特權 IP： 203.0.113.89</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">前置作業：請出管理員鑰匙</h2>



<p class="wp-block-paragraph">在指揮阿福修改點名簿之前，請先確認你已經切換到正確的管理帳號，不然阿福是絕對不會理你的。</p>



<p class="wp-block-paragraph">指令如下：</p>



<p class="wp-block-paragraph">az account set &#8211;subscription &#8220;請輸入你的雲端帳號萬用鑰匙代碼&#8221;</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">新增特權 IP 的四大步驟</h2>



<p class="wp-block-paragraph">假設今天有兩組好朋友，一組是 192.168.99.0/24 的新同學，另一組是 198.51.100.6 的外包夥伴，想要加入點名簿。請跟著以下步驟做：</p>



<h3 class="wp-block-heading">步驟 1：偷看目前的點名簿</h3>



<p class="wp-block-paragraph">先把阿福現在手上到底登記了誰調查清楚。</p>



<p class="wp-block-paragraph">az network application-gateway waf-policy custom-rule show &#8211;policy-name 門神名稱 &#8211;resource-group 城堡名稱 &#8211;name 規則名稱 &#8211;query &#8220;matchConditions[0].matchValues&#8221; -o json</p>



<h3 class="wp-block-heading">步驟 2：大家排排站，準備新名單</h3>



<p class="wp-block-paragraph">請把剛剛查出來的舊名單複製出來，然後把新朋友的名字加進去，排成一列。</p>



<h3 class="wp-block-heading">步驟 3：大手一揮，整張蓋過去</h3>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph"><strong>超級重要：</strong> 阿福很健忘，你不能只跟他說我要加兩個人，你要給他一張全新的完整名單，否則舊的人會被他通通忘光光。</p>
</blockquote>



<p class="wp-block-paragraph">我們會用一個腳本把新舊名單綁在一起，然後直接覆蓋：</p>



<p class="wp-block-paragraph">$allIPs = @(</p>



<p class="wp-block-paragraph">&#8220;192.168.1.0/24&#8221;,</p>



<p class="wp-block-paragraph">&#8220;192.168.2.0/24&#8221;,</p>



<p class="wp-block-paragraph">&#8220;192.168.3.0/24&#8221;,</p>



<p class="wp-block-paragraph">&#8220;203.0.113.89&#8221;,</p>



<p class="wp-block-paragraph">&#8220;192.168.99.0/24&#8221;, # 這是新朋友一號</p>



<p class="wp-block-paragraph">&#8220;198.51.100.6&#8221; # 這是新朋友二號</p>



<p class="wp-block-paragraph">)</p>



<p class="wp-block-paragraph">（接下來把這串名單打包成 JSON 格式後，用雲端指令送給阿福更新。因為指令比較長，這裡就不贅述，重點是名單要帶齊。）</p>



<h3 class="wp-block-heading">步驟 4：檢查阿福有沒有認真工作</h3>



<p class="wp-block-paragraph">更新完之後，再次叫阿福把名單拿出來核對，看看剛才加的新朋友是不是都在上面了。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">如果想踢掉某個 IP 呢</h2>



<p class="wp-block-paragraph">方法完全一樣。先去步驟 1 看名單，在步驟 2 的時候拿橡皮擦把不想看到的人擦掉，最後用步驟 3 的覆蓋大法，這個人就被阿福除名了。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">實地測試：看看阿福有沒有偷懶</h2>



<p class="wp-block-paragraph">名單改完之後，我們一定要來測試看看阿福是不是真的有在認真抓壞人：</p>



<ul class="wp-block-list">
<li><strong>叫沒拿通行證的朋友點網址：</strong> 如果畫面跳出 403 錯誤，代表被阿福成功攔截，阿福好棒。</li>



<li><strong>自己人從白名單點網址：</strong> 如果正常顯示網頁，代表通行無阻。</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">筆記小重點</h2>



<ul class="wp-block-list">
<li><strong>隔壁鄰居很安全：</strong> 這個點名簿設定只針對我們 Project Name ，完全不會影響到同一個伺服器底下的其他系統。</li>



<li><strong>覆蓋完請稍等：</strong> 阿福收到新名單後，大概需要 1 到 2 分鐘的時間來認熟面孔，請給他一點時間。</li>



<li><strong>附贈隱形護盾：</strong> 除了不讓陌生人進來，阿福同時還開啟了國際防禦標準，一般的網路流氓就算換了白名單 IP 進來，只要手腳不乾淨一樣會被抓走喔。</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="wp-block-paragraph">如果你想使用 Azure Portal 網頁畫面來操作，步驟非常直覺，請跟著以下步驟點選。</p>



<p class="wp-block-paragraph"><strong>第一步、登入與尋找策略</strong></p>



<p class="wp-block-paragraph">首先打開瀏覽器登入 Azure Portal 網站。在網頁最上方的搜尋欄輸入 Web Application Firewall policies 或是 WAF 策略，然後點選進入。</p>



<figure class="wp-block-image size-full"><img decoding="async" width="705" height="982" src="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/06/chrome_2026-06-30-18-37-ca.jpg?v=1782815930" alt="" class="wp-image-8614" srcset="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/06/chrome_2026-06-30-18-37-ca.jpg?v=1782815930 705w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/06/chrome_2026-06-30-18-37-ca-431x600.jpg?v=1782815930 431w" sizes="(max-width: 705px) 100vw, 705px" /></figure>



<p class="wp-block-paragraph"><strong>第二步、選擇特定的策略</strong></p>



<p class="wp-block-paragraph">在列表中找到並點選你正在使用的 WAF 策略名稱。</p>



<p class="wp-block-paragraph"><strong>第三步、進入自訂規則</strong></p>



<p class="wp-block-paragraph">進入該策略的頁面後，查看左側的選單，在設定區塊中，點選自訂規則。</p>



<p class="wp-block-paragraph"><strong>第四步、修改名單</strong></p>



<p class="wp-block-paragraph">在自訂規則的列表中，點選你用來管制 IP 的那一條規則。點開之後，你會看到比對條件，找到比對變數為 RemoteAddr 的那一項，你就可以直接在下方的數值列表中，新增或刪除你要異動的 IP 位址。</p>



<figure class="wp-block-image size-large"><img decoding="async" width="453" height="1024" src="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/06/chrome_2026-06-30-18-39-cb-453x1024.jpg?v=1782816010" alt="" class="wp-image-8617" srcset="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/06/chrome_2026-06-30-18-39-cb-453x1024.jpg?v=1782816010 453w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/06/chrome_2026-06-30-18-39-cb-266x600.jpg?v=1782816010 266w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/06/chrome_2026-06-30-18-39-cb.jpg?v=1782816010 468w" sizes="(max-width: 453px) 100vw, 453px" /></figure>



<p class="wp-block-paragraph"><strong>第五步、儲存更新</strong></p>



<p class="wp-block-paragraph">修改完成後，點選畫面最上方的儲存按鈕。系統大約需要 1 到 2 分鐘的時間將新名單同步到防火牆。</p>



<p class="wp-block-paragraph"></p>
]]></content:encoded>
					
					<wfw:commentRss>https://stackoverflow.max-everyday.com/2026/06/waf-white-ip-list/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>一目了然的 Azure NSG 設定：多 Port 阻擋與特定 IP 放行的完美組合</title>
		<link>https://stackoverflow.max-everyday.com/2026/06/azure-nsg-port-ip-list/</link>
					<comments>https://stackoverflow.max-everyday.com/2026/06/azure-nsg-port-ip-list/#respond</comments>
		
		<dc:creator><![CDATA[max-stackoverflow]]></dc:creator>
		<pubDate>Fri, 26 Jun 2026 03:31:20 +0000</pubDate>
				<category><![CDATA[Azure 筆記]]></category>
		<category><![CDATA[azure]]></category>
		<guid isPermaLink="false">https://stackoverflow.max-everyday.com/?p=8606</guid>

					<description><![CDATA[在 Azure 的網路安全性群組（NSG）世界裡...]]></description>
										<content:encoded><![CDATA[
<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1024" height="572" src="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/06/9251883175766280132_clean.jpg?v=1782444672" alt="" class="wp-image-8607" srcset="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/06/9251883175766280132_clean.jpg?v=1782444672 1024w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/06/9251883175766280132_clean-600x335.jpg?v=1782444672 600w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/06/9251883175766280132_clean-768x429.jpg?v=1782444672 768w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">在 Azure 的網路安全性群組（NSG）世界裡，有時候我們會遇到一個很像「既要又要」的邊緣狀況：你想要同時把好幾個 Port 關起來，不讓壞人進來，但同時又希望特定幾位好朋友（白名單 IP）可以大搖大擺地走進去。</p>



<p class="wp-block-paragraph">要同時做到阻擋大家又放行自己人，最乾淨也最聰明的作法，就是利用兩條規則的「優先順序（Priority）」差來玩一場網路版的邏輯遊戲。</p>



<p class="wp-block-paragraph">因為 Azure NSG 這個門神在檢查規則時，是採取「由上到下」比對的。數字越小的規則越優先執行，而且只要一比對成功，它就會直接定案，懶得再看下面的規則了。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">核心邏輯大公開</h2>



<p class="wp-block-paragraph">簡單來說，你只需要左右開弓，建立以下兩條規則：</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><td><strong>規則名稱</strong></td><td><strong>優先順序</strong></td><td><strong>來源</strong></td><td><strong>目的連接埠</strong></td><td><strong>動作</strong></td><td><strong>門神心裡話</strong></td></tr></thead><tbody><tr><td>Allow-Whitelist-to-Ports</td><td>300</td><td>你的白名單 IP</td><td>80, 443, 8080</td><td>Allow（允許）</td><td>是好朋友拿著通行證來了！這幾個 Port 給你過！</td></tr><tr><td>Deny-All-to-Ports</td><td>301</td><td>Any（任何人）</td><td>80, 443, 8080</td><td>Deny（拒絕）</td><td>剩下的閒雜人等注意，只要想碰這幾個 Port，通通給我滾！</td></tr></tbody></table></figure>



<p class="wp-block-paragraph"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 重要提醒：允許（Allow）規則的 Priority 數字（例如 300）必須小於阻擋（Deny）規則的 Priority 數字（例如 301）。如果把阻擋數字寫得太小，門神就會先把大家都趕走，你的好朋友就再也進不來了。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">詳細設定步驟（Azure Portal 傳送門操作）</h2>



<p class="wp-block-paragraph">請直接移駕到你的 NSG （azure vmptstgjpe001-nsg）的「輸入安全性規則（Inbound security rules）」頁面，深呼吸之後點選「新增（Add）」：</p>



<h3 class="wp-block-heading">步驟 1：建立白名單允許規則</h3>



<ol start="1" class="wp-block-list">
<li>Source（來源）：下拉選單請選 IP Addresses。</li>



<li>Source IP addresses（來源 IP 位址）：輸入你想放行的白名單 IP。如果好朋友有點多，可以用半形逗號隔開，例如： 1.1.1.1, 2.2.2.0/24。</li>



<li>Source port ranges（來源連接埠範圍）：維持預設的 * 號就好。</li>



<li>Destination（目的地）：選 Any 或是直接指定那一台 VM 的內網 IP。</li>



<li>Destination port ranges（目的連接埠範圍）：輸入你想控制的多個 Port，中間記得用半形逗號隔開，例如： 80, 443, 8080。</li>



<li>Protocol（通訊協定）：看你心情與需求，選 TCP、UDP 或是 Any。</li>



<li>Action（動作）：大方地選擇 Allow。</li>



<li>Priority（優先順序）：給它一個比較小的數字，例如 300。</li>



<li>Name（名稱）：取一個好辨識的名字，像是 Allow_Whitelist_MultiPorts。</li>
</ol>



<h3 class="wp-block-heading">步驟 2：建立其餘全部阻擋規則</h3>



<p class="wp-block-paragraph">再次點選「新增（Add）」來建立第二道關卡：</p>



<ol start="1" class="wp-block-list">
<li>Source（來源）：這次要選 Any，代表任何路人甲乙丙。</li>



<li>Source port ranges：一樣維持 * 號。</li>



<li>Destination：選 Any。</li>



<li>Destination port ranges：請輸入與步驟 1 完全一模一樣的多個 Port，例如： 80, 443, 8080。</li>



<li>Protocol：請與步驟 1 保持同步。</li>



<li>Action（動作）：這次要狠下心選擇 Deny。</li>



<li>Priority（優先順序）：數字必須比步驟 1 還要大，例如 301。</li>



<li>Name（名稱）：給它一個威武的名字，像是 Deny_All_MultiPorts。</li>
</ol>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">進階管理小技巧</h2>



<p class="wp-block-paragraph">如果你的白名單 IP 常常像天氣一樣變來變去，或者你手底下的 VM 越來越多，每次都要手動來改 NSG 規則真的會讓人想砸鍵盤。</p>



<p class="wp-block-paragraph">這時候建議可以利用「應用程式安全性群組（Application Security Group, ASG）」或者 Azure 的「IP Groups」功能。這就像是把所有好朋友的名字直接打包進一個群組懶人包，以後 NSG 規則的來源直接指定這個群組就可以了，管理起來輕鬆到可以提早下班！</p>



<p class="wp-block-paragraph"></p>
]]></content:encoded>
					
					<wfw:commentRss>https://stackoverflow.max-everyday.com/2026/06/azure-nsg-port-ip-list/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>如何申請google oauth</title>
		<link>https://stackoverflow.max-everyday.com/2026/06/apply-google-oauth/</link>
					<comments>https://stackoverflow.max-everyday.com/2026/06/apply-google-oauth/#respond</comments>
		
		<dc:creator><![CDATA[max-stackoverflow]]></dc:creator>
		<pubDate>Thu, 25 Jun 2026 09:34:38 +0000</pubDate>
				<category><![CDATA[電腦相關應用]]></category>
		<guid isPermaLink="false">https://stackoverflow.max-everyday.com/?p=8601</guid>

					<description><![CDATA[申請 Google OAuth 其實不難，主要是...]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">申請 Google OAuth 其實不難，主要是在 <strong>Google Cloud Console</strong> 中完成。不論你是要開發網站登入、串接 Google Drive 還是發送 Gmail，流程都大同小異。</p>



<p class="wp-block-paragraph">以下為你整理出最流暢的申請 4 大步驟：</p>



<h3 class="wp-block-heading">第一步：建立新專案</h3>



<ol start="1" class="wp-block-list">
<li>前往 <a href="https://console.cloud.google.com/" target="_blank" rel="noreferrer noopener">Google Cloud Console</a>。</li>



<li>登入你的 Google 帳號。</li>



<li>點擊左上角的專案下拉選單，選擇 <strong>「新建專案」 (New Project)</strong>。</li>



<li>輸入專案名稱（例如：<code>MyAwesomeApp</code>），然後點擊 <strong>「建立」</strong>。</li>
</ol>



<h3 class="wp-block-heading">第二步：設定 OAuth 同意畫面 (OAuth Consent Screen)</h3>



<p class="wp-block-paragraph">這是使用者在登入你的 App 時會看到的授權畫面，必須先設定它。</p>



<ol start="1" class="wp-block-list">
<li>點擊左側選單的 <strong>「API 和服務」 > 「OAuth 同意畫面」</strong>。</li>



<li>選擇 <strong>User Type</strong>：
<ul class="wp-block-list">
<li><strong>外部 (External)：</strong> 開放給所有 Google 帳號（一般應用程式選這個）。</li>



<li><strong>內部 (Internal)：</strong> 僅限你 Google Workspace 組織內的成員（企業內部工具選這個）。</li>
</ul>
</li>



<li>填寫基本資料：<strong>應用程式名稱</strong>、<strong>使用者支援電子郵件</strong> 以及 <strong>開發人員聯絡資訊</strong>，完成後點擊「儲存並繼續」。</li>



<li><strong>範圍 (Scopes)：</strong> 設定你想拿到的權限（如 <code>email</code>, <code>profile</code>）。初次測試可以先不選，直接跳過。</li>



<li><strong>測試使用者 (Test Users)：</strong> 如果你的專案還在「測試中」階段，<strong>必須</strong>在這裡新增你自己的測試 Email，否則其他帳號會無法登入。</li>
</ol>



<h3 class="wp-block-heading">第三步：建立憑證 (Credentials) 取得密鑰</h3>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="496" src="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/06/chrome_2026-06-25-17-16-c1-1024x496.jpg?v=1782379301" alt="" class="wp-image-8602" srcset="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/06/chrome_2026-06-25-17-16-c1-1024x496.jpg?v=1782379301 1024w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/06/chrome_2026-06-25-17-16-c1-600x291.jpg?v=1782379301 600w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/06/chrome_2026-06-25-17-16-c1-768x372.jpg?v=1782379301 768w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/06/chrome_2026-06-25-17-16-c1-1536x744.jpg?v=1782379301 1536w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/06/chrome_2026-06-25-17-16-c1.jpg?v=1782379301 1754w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">這一步會生成你的 <code>Client ID</code> 和 <code>Client Secret</code>。</p>



<ol start="1" class="wp-block-list">
<li>點擊左側選單的 <strong>「憑證」 (Credentials)</strong>。</li>



<li>點擊上方的 <strong>「+ 建立憑證」 > 「OAuth 用戶端 ID」</strong>。</li>



<li>選擇你的 <strong>應用程式類型</strong>（例如：網頁應用程式、Android、iOS 等）。</li>



<li><strong>填寫核心網址（以網頁應用程式為例）：</strong>
<ul class="wp-block-list">
<li><strong>已授權的 JavaScript 來源：</strong> 你的網站首頁（例如：<code>http://localhost:3000</code> 或 <code>https://yourdomain.com</code>）。</li>



<li><strong>已授權的重新導向 URI：</strong> 使用者登入成功後，Google 把 Code 送回你網站的後端路由（例如：<code>http://localhost:3000/auth/google/callback</code>）。<em>※ 注意：這裡一定要設定精準，否則會噴 <code>redirect_uri_mismatch</code> 錯誤。</em></li>
</ul>
</li>



<li>點擊 <strong>「建立」</strong>。</li>
</ol>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4a1.png" alt="💡" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>恭喜！</strong> 畫面上會跳出你的 <strong>用戶端 ID (Client ID)</strong> 和 <strong>用戶端密鑰 (Client Secret)</strong>。請立刻複製並妥善保管，<strong>密鑰絕對不能外流</strong>！</p>
</blockquote>



<h3 class="wp-block-heading">第四步：切換為「生產模式」（選填）</h3>



<p class="wp-block-paragraph">在開發測試階段，你的專案狀態會是「測試中 (Testing)」，只有你在第二步設定的測試小夥伴能登入，且可能會有不安全應用的警告。</p>



<ul class="wp-block-list">
<li>當你的 App 準備好上線時，回到 <strong>「OAuth 同意畫面」</strong>，點擊 <strong>「發布應用程式」 (Publish App)</strong> 切換為正式生產模式。</li>



<li><em>備註：如果你申請了敏感權限（如讀取使用者的 Gmail 私訊），Google 會需要人工審查，可能需要幾天到幾週的時間。</em></li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="wp-block-paragraph">我有測試環境 domain name  與正式環境 domain name 要申請2組 OAuth 2.0 用戶端 ID 嗎? 還是同一組ID即可。</p>



<p class="wp-block-paragraph"><strong>建議申請「2 組獨立的 OAuth 2.0 用戶端 ID (Client ID)」。</strong></p>



<p class="wp-block-paragraph">雖然在技術上，Google 允許你在同一組 Client ID 的「已授權的重新導向 URI」中，同時填入測試環境與正式環境的網址，但<strong>非常強烈建議分開申請</strong>。原因如下：</p>



<h3 class="wp-block-heading">1. 安全性考量（最重要的原因）</h3>



<ul class="wp-block-list">
<li><strong>憑證外流風險：</strong> 測試環境通常防護較弱，可能會有較多開發人員、測試人員接觸到設定檔。如果測試環境的 Client Secret（用戶端密鑰）不小心外流，且你兩邊用同一組 ID，<strong>攻擊者就能利用外流的密鑰去染指或偽造正式環境的登入請求</strong>。</li>



<li><strong>網址白名單污染：</strong> 萬一測試環境網址被惡意劫持或存在漏洞，它與正式環境共享同一個 OAuth 信任圈，會帶來額外的安全隱患。</li>
</ul>



<h3 class="wp-block-heading">2. Google 審查與「發布狀態」限制</h3>



<p class="wp-block-paragraph">Google Cloud 專案的 OAuth 同意畫面有分兩種狀態：</p>



<ul class="wp-block-list">
<li><strong>測試中 (Testing)：</strong> 只有白名單內的「測試使用者」可以登入，且登入時會跳出「未經驗證的應用程式」警告。</li>



<li><strong>生產模式 (In Production)：</strong> 開放給所有 Google 帳號登入。如果使用了敏感權限（Scopes），還需要通過 Google 的人工驗證。</li>
</ul>



<p class="wp-block-paragraph"><strong>如果你只用一組：</strong></p>



<p class="wp-block-paragraph">當你的專案為了正式環境切換成「生產模式」並送交審查時，如果你的網址白名單裡還夾帶了 <code>project-name.test.example.com.tw</code>（而且通常測試環境在外網無法被 Google 審查官順利連上），<strong>這可能會導致你的 Google 審查被拒絕 (Rejected)</strong>，因為 Google 要求生產模式下的網址必須是公開、真實且符合規範的。</p>



<h3 class="wp-block-heading">最佳實作做法 (Best Practice)</h3>



<p class="wp-block-paragraph">為了架構清晰與安全，你可以選擇以下兩種配置方式：</p>



<h4 class="wp-block-heading">方案 A：在同一個 Google Cloud 專案下，建立 2 個 Client ID（推薦，最方便）</h4>



<p class="wp-block-paragraph">在同一個專案的「憑證」頁面中，點擊兩次「建立憑證 &gt; OAuth 用戶端 ID」：</p>



<ul class="wp-block-list">
<li><strong>第一組 ID（命名：Project Name &#8211; Test）：</strong>
<ul class="wp-block-list">
<li>重新導向 URI 填：<code>https://projectname.test.example.com.tw/oauth2/callback</code></li>
</ul>
</li>



<li><strong>第二組 ID（命名：Project Name &#8211; Prod）：</strong>
<ul class="wp-block-list">
<li>重新導向 URI 填：<code>https://projectname.example.com.tw/oauth2/callback</code></li>
</ul>
</li>
</ul>



<p class="wp-block-paragraph"><em>這樣你在程式碼的環境變數（<code>.env</code>）中，測試機就讀取測試的 ID/Secret，正式機就讀取正式的 ID/Secret，彼此互不干擾。</em></p>



<h4 class="wp-block-heading">方案 B：建立 2 個獨立的 Google Cloud 專案（最嚴謹）</h4>



<ul class="wp-block-list">
<li>專案一：<code>PorjectName-Test</code>（專案狀態保持在 Testing，方便開發隨時測試）</li>



<li>專案二：<code><code>PorjectName</code>-Prod</code>（專案狀態切換為 Production，專門用來跑正式上線與 Google 審查流程）</li>
</ul>



<p class="wp-block-paragraph"><strong>總結：</strong> 為了維護系統的安全防線與後續維護的便利性，請務必為測試與正式環境分開申請獨立的憑證！</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="wp-block-paragraph">應用程式是哪種類型, Vue 3 + Vite</p>



<p class="wp-block-paragraph">既然你的前端技術棧是 <strong>Vue 3 + Vite</strong>，在架構設計上你首先需要釐清一個關鍵問題：<strong>你的應用程式有沒有後端（Backend，如 Node.js, Python, Go）？</strong></p>



<p class="wp-block-paragraph">這會直接決定你如何實作 Google OAuth。以下為你說明這兩種情境與實作方式：</p>



<h3 class="wp-block-heading">情境一：有後端（推薦，最安全）</h3>



<p class="wp-block-paragraph">這是最正統的做法。Vue 3 只負責顯示「登入按鈕」，使用者點擊後會引導到 Google 登入，登入成功後 Google 會把驗證碼（Authorization Code）傳給你的<strong>後端</strong>，由後端去跟 Google 交換 Token，並簽發你網站自己的 JWT 給 Vue 3。</p>



<p class="wp-block-paragraph">在這種模式下，你的 <strong>OAuth 2.0 用戶端 ID 類型</strong>要選擇：<strong>「網頁應用程式」 (Web Application)</strong>。</p>



<h4 class="wp-block-heading">Vite / Vue 3 前端實作步驟：</h4>



<p class="wp-block-paragraph">目前社群上最推崇、封裝最完整的套件是 <code>vue3-google-signin</code>。</p>



<p class="wp-block-paragraph"><strong>安裝套件：</strong>Bash</p>



<pre class="wp-block-code"><code><code>npm install vue3-google-signin</code></code></pre>



<p class="wp-block-paragraph"><strong>在 <code>main.js</code> 或 <code>main.ts</code> 中初始化：</strong></p>



<pre class="wp-block-code"><code>import { createApp } from 'vue'
import App from './App.vue'
import GoogleSignInPlugin from 'vue3-google-signin'

const app = createApp(App)

app.use(GoogleSignInPlugin, {
  // Vite 的環境變數必須以 VITE_ 開頭
  clientId: import.meta.env.VITE_GOOGLE_CLIENT_ID,
})

app.mount('#app')</code></pre>



<p class="wp-block-paragraph"><strong>在畫面上建立登入按鈕 (<code>Login.vue</code>)：</strong></p>



<pre class="wp-block-code"><code>&lt;script setup>
import { GoogleLogin } from 'vue3-google-signin'

const handleLoginSuccess = (response) => {
  // 這裡會拿到 Google 回傳的 code 或 credential
  console.log("Google 登入成功:", response);

  // 【關鍵】將這個 response (含 code) 用 axios 傳送給你的後端
  // axios.post('https://projectname.example.come.tw/api/auth/google', { code: response.code })
}

const handleLoginError = () => {
  console.error("Google 登入失敗")
}
&lt;/script>

&lt;template>
  &lt;div class="login-container">
    &lt;h2>入口網登入&lt;/h2>
    &lt;GoogleLogin :callback="handleLoginSuccess" :popup-type="'CODE'" @error="handleLoginError" />
  &lt;/div>
&lt;/template></code></pre>



<p class="wp-block-paragraph"></p>



<h3 class="wp-block-heading">情境二：純前端（SPA 單頁應用，無後端）</h3>



<p class="wp-block-paragraph">如果你的 Vue 3 是一個完全靜態的網站，沒有任何自己的後端資料庫，單純只想確認「他是誰」或者想在前端呼叫 Google API（例如讀取他的 Google Calendar）。</p>



<p class="wp-block-paragraph">在這種模式下，你的 <strong>OAuth 2.0 用戶端 ID 類型</strong>一樣選擇：<strong>「網頁應用程式」</strong>。但流程會走「隱含授權 (Implicit Flow)」，直接在前端取得 <code>access_token</code>。</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>安全警告：</strong> 因為純前端沒有秘密可言，你的 <code>Client Secret</code> 絕對不能寫在 Vue 3 的原始碼或 <code>.env</code> 裡（因為打包後任何人都能看光）。在 Google Console 建立憑證時，這種模式通常不需要用到 Client Secret。</p>
</blockquote>



<h4 class="wp-block-heading">前端設定差異：</h4>



<p class="wp-block-paragraph">如果你是用上面提到的 <code>vue3-google-signin</code>，在按鈕上就不要加 <code>:popup-type="'CODE'"</code>，它預設會直接在前端回傳一串 JWT（身分憑證），你可以直接解開它來取得使用者的 Email 和姓名：</p>



<p class="wp-block-paragraph">JavaScript</p>



<pre class="wp-block-code"><code>import { decodeCredential } from 'vue3-google-signin'

const handleLoginSuccess = (response) =&gt; {
  // 直接在前端解密 Google 回傳的使用者資訊
  const userData = decodeCredential(response.credential)
  console.log("使用者資料:", userData) // 包含 email, name, picture
}
</code></pre>



<h3 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4a1.png" alt="💡" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 搭配你上一題的測試/正式環境，Vite 該如何設定？</h3>



<p class="wp-block-paragraph">因為你有兩個環境，請在 Vue 3 專案根目錄建立兩個環境變數檔案：</p>



<ol start="1" class="wp-block-list">
<li><strong><code>.env.development</code></strong> (測試環境用)程式碼片段<code>VITE_GOOGLE_CLIENT_ID=你的測試環境_CLIENT_ID.apps.googleusercontent.com</code></li>



<li><strong><code>.env.production</code></strong> (正式環境用)程式碼片段<code>VITE_GOOGLE_CLIENT_ID=你的正式環境_CLIENT_ID.apps.googleusercontent.com</code></li>
</ol>



<p class="wp-block-paragraph">當你執行 <code>vite preview</code> 或 <code>vite build</code> 時，Vite 就會自動根據環境抓取正確的 Client ID，完美對應到正式環境與測試環境了！</p>
]]></content:encoded>
					
					<wfw:commentRss>https://stackoverflow.max-everyday.com/2026/06/apply-google-oauth/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>預覽 PlantUML 的 .puml 檔案</title>
		<link>https://stackoverflow.max-everyday.com/2026/06/preview-plant-uml-puml/</link>
					<comments>https://stackoverflow.max-everyday.com/2026/06/preview-plant-uml-puml/#respond</comments>
		
		<dc:creator><![CDATA[max-stackoverflow]]></dc:creator>
		<pubDate>Thu, 25 Jun 2026 09:20:48 +0000</pubDate>
				<category><![CDATA[電腦相關應用]]></category>
		<guid isPermaLink="false">https://stackoverflow.max-everyday.com/?p=8596</guid>

					<description><![CDATA[這裡幫大家整理了 4 個超實用的方法，不管是想要...]]></description>
										<content:encoded><![CDATA[
<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="572" src="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/06/preview-plant-uml-puml_clean-1024x572.jpg?v=1782379243" alt="" class="wp-image-8599" srcset="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/06/preview-plant-uml-puml_clean-1024x572.jpg?v=1782379243 1024w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/06/preview-plant-uml-puml_clean-600x335.jpg?v=1782379243 600w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/06/preview-plant-uml-puml_clean-768x429.jpg?v=1782379243 768w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/06/preview-plant-uml-puml_clean.jpg?v=1782379243 1376w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">這裡幫大家整理了 4 個超實用的方法，不管是想要神級的操作體驗，還是懶得安裝任何軟體的速成法，這篇通通有。</p>



<h2 class="wp-block-heading">方法 1 ： VS Code 神級流暢體驗（強烈推薦）</h2>



<p class="wp-block-paragraph">如果你平常就在用 Visual Studio Code 寫程式，這絕對是最頂的選擇，可以享受一邊打字、旁邊畫面一邊即時更新的快感。</p>



<ol start="1" class="wp-block-list">
<li>打開 VS Code ，到擴充功能市場搜尋並安裝 PlantUML 這個套件。</li>



<li>注意，這裡有一個重要的小跟班需要安裝。因為 PlantUML 畫圖需要 Graphviz 的協助，請打開命令提示字元輸入 winget install Graphviz 來快速搞定它。</li>



<li>如果真的懶得在電腦安裝 Graphviz 怎麼辦？也可以直接到 VS Code 的設定裡搜尋 plantuml.server ，把它改成連到官方的線上伺服器來幫你畫圖。</li>



<li>萬事俱備後，打開 .puml 檔案，按下 Alt + D ，右邊就會跳出即時預覽畫面。</li>
</ol>



<h2 class="wp-block-heading">方法 2 ： JetBrains 宇宙（ IntelliJ 、 PyCharm 專屬）</h2>



<p class="wp-block-paragraph">如果你是 JetBrains 系列 IDE 的忠實粉絲，根本不用離開視窗，裡面就自帶強大功能。</p>



<ol start="1" class="wp-block-list">
<li>點選 File 進入 Settings ，接著找到 Plugins 區塊。</li>



<li>搜尋 PlantUML Integration 並點擊安裝。</li>



<li>把 IDE 重新啟動。</li>



<li>接下來只要打開任何 .puml 檔案，右邊就會自動長出專屬的預覽視窗，完全不用操心。</li>
</ol>



<h2 class="wp-block-heading">方法 3 ： 獨立 Java 檔案（免安裝開發工具）</h2>



<p class="wp-block-paragraph">如果不想為了看張圖就開啟笨重的開發工具，也可以用官方提供的輕量級 Java 小工具。</p>



<ol start="1" class="wp-block-list">
<li>請先確保電腦裡有安裝 Java 執行環境。</li>



<li>到 <a href="https://plantuml.com/" target="_blank" rel="noreferrer noopener">PlantUML 官方網站</a> 下載 plantuml.jar 檔案。</li>



<li>打開命令提示字元，切換到下載的資料夾，輸入 java -jar plantuml.jar -gui 這一行指令。</li>



<li>畫面上就會跳出一個小巧的視窗，只要選好放 .puml 檔案的資料夾，每次只要存檔，它就會自動幫你刷新圖片。</li>
</ol>



<h2 class="wp-block-heading">方法 4 ： 線上免安裝（終極懶人法）</h2>



<p class="wp-block-paragraph">如果只是臨時要看一下檔案，連一秒鐘都不想浪費在安裝上，那就直接交給瀏覽器吧。</p>



<ul class="wp-block-list">
<li>官方線上伺服器：直接把程式碼複製貼上到 <a href="https://gemini.google.com/www.plantuml.com/plantuml" target="_blank" rel="noreferrer noopener">PlantUML Web Server</a> 就能立刻看到結果。</li>



<li>網頁編輯器： <a href="https://gemini.google.com/planttext.com" target="_blank" rel="noreferrer noopener">PlantText</a> 是一個非常受歡迎的線上即時編輯器，畫面好看而且專為 PlantUML 設計，非常適合臨時救急。</li>
</ul>
]]></content:encoded>
					
					<wfw:commentRss>https://stackoverflow.max-everyday.com/2026/06/preview-plant-uml-puml/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>在使用 Windows 系統 ssh: UNPROTECTED PRIVATE KEY FILE!</title>
		<link>https://stackoverflow.max-everyday.com/2026/06/%e5%9c%a8%e4%bd%bf%e7%94%a8-windows-%e7%b3%bb%e7%b5%b1-ssh-unprotected-private-key-file/</link>
					<comments>https://stackoverflow.max-everyday.com/2026/06/%e5%9c%a8%e4%bd%bf%e7%94%a8-windows-%e7%b3%bb%e7%b5%b1-ssh-unprotected-private-key-file/#respond</comments>
		
		<dc:creator><![CDATA[max-stackoverflow]]></dc:creator>
		<pubDate>Wed, 24 Jun 2026 09:25:29 +0000</pubDate>
				<category><![CDATA[電腦相關應用]]></category>
		<category><![CDATA[ssh]]></category>
		<category><![CDATA[Windows]]></category>
		<guid isPermaLink="false">https://stackoverflow.max-everyday.com/?p=8593</guid>

					<description><![CDATA[在 Windows 執行 ssh 顯示錯誤訊息 ...]]></description>
										<content:encoded><![CDATA[
<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1024" height="572" src="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/06/16307345641785880220.jpg?v=1782293092" alt="" class="wp-image-8594" srcset="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/06/16307345641785880220.jpg?v=1782293092 1024w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/06/16307345641785880220-600x335.jpg?v=1782293092 600w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/06/16307345641785880220-768x429.jpg?v=1782293092 768w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">在 Windows 執行 ssh  顯示錯誤訊息</p>



<pre class="wp-block-preformatted">C:\Users\max&gt;ssh 4.216.213.123<br>Bad permissions. Try removing permissions for user: BUILTIN\\Users (S-1-5-32-545) on file C:/Users/max/.ssh/vm-max-pc-key.pem.<br>@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@<br>@         WARNING: UNPROTECTED PRIVATE KEY FILE!          @<br>@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@<br>Permissions for 'C:\\Users\\max/.ssh/vm-max-pc-key.pem' are too open.<br>It is required that your private key files are NOT accessible by others.<br>This private key will be ignored.<br>Load key "C:\\Users\\max/.ssh/vm-max-pc-key.pem": bad permissions<br>azureuser@4.216.213.123: Permission denied (publickey).</pre>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="wp-block-paragraph">在使用 Windows 系統連線到遠端伺服器的時候，是不是常常遇到 SSH 跟你鬧脾氣？</p>



<p class="wp-block-paragraph">畫面跳出一大串警告，寫著 WARNING UNPROTECTED PRIVATE KEY FILE ，然後無情地拒絕連線。這其實不是你的伺服器壞掉，而是 SSH 的安全機制太過傲嬌。它覺得你的金鑰檔案太過公開，像是把家裡鑰匙直接貼在大門口一樣危險，所以故意裝作不認識你。</p>



<p class="wp-block-paragraph">這個問題通常是因為金鑰檔案的權限開太大，導致系統內的其他使用者也能讀取。要解決這個尷尬的狀況，有兩種快速的方法可以馴服它。</p>



<p class="wp-block-paragraph">第一種方法是使用命令提示字元。</p>



<p class="wp-block-paragraph">請開啟命令提示字元，並依序輸入以下指令。記得將路徑換成自己的金鑰檔案路徑。</p>



<p class="wp-block-paragraph">第一步是取消繼承權限，指令為 </p>



<pre class="wp-block-code"><code>icacls C:\Users\your_username\.ssh\my_key.pem /inheritance:r</code></pre>



<p class="wp-block-paragraph">第二步是重新給予自己完整的控制權限，指令為 </p>



<pre class="wp-block-code"><code>icacls C:\Users\your_username\.ssh\my_key.pem /grant:r your_username:F </code></pre>



<p class="wp-block-paragraph">第二種方法是使用圖形介面，適合喜歡用滑鼠點擊的人。</p>



<p class="wp-block-paragraph">請打開檔案總管，找到金鑰檔案。在檔案上按右鍵選擇內容，切換到安全性標籤，接著點擊進階。</p>



<p class="wp-block-paragraph">在進階安全性設定視窗中，點擊停用繼承，並選擇將繼承的權限轉換為此物件的明確權限。</p>



<p class="wp-block-paragraph">最後在權限項目清單中，找到 Users 這個群組並將它移除，只留下自己的使用者帳號擁有存取權限。點擊套用並確定就完成了。</p>



<p class="wp-block-paragraph">設定完成之後，再次嘗試原本的連線指令，應該就能順利連上伺服器。如果還是失敗，可以嘗試在指令中明確加上減 i 參數，例如 </p>



<pre class="wp-block-code"><code>ssh -i C:\Users\your_username.ssh\my_key.pem user@1.2.3.4 </code></pre>



<p class="wp-block-paragraph">這樣就能成功登入了。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://stackoverflow.max-everyday.com/2026/06/%e5%9c%a8%e4%bd%bf%e7%94%a8-windows-%e7%b3%bb%e7%b5%b1-ssh-unprotected-private-key-file/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>把 unsafe-inline 從你的 CSP 裡面拿掉？ 醒醒吧，你的前端元件會直接罷工！</title>
		<link>https://stackoverflow.max-everyday.com/2026/06/%e6%8a%8a-unsafe-inline-%e5%be%9e%e4%bd%a0%e7%9a%84-csp-%e8%a3%a1%e9%9d%a2%e6%8b%bf%e6%8e%89%ef%bc%9f-%e9%86%92%e9%86%92%e5%90%a7%ef%bc%8c%e4%bd%a0%e7%9a%84%e5%89%8d%e7%ab%af%e5%85%83%e4%bb%b6/</link>
					<comments>https://stackoverflow.max-everyday.com/2026/06/%e6%8a%8a-unsafe-inline-%e5%be%9e%e4%bd%a0%e7%9a%84-csp-%e8%a3%a1%e9%9d%a2%e6%8b%bf%e6%8e%89%ef%bc%9f-%e9%86%92%e9%86%92%e5%90%a7%ef%bc%8c%e4%bd%a0%e7%9a%84%e5%89%8d%e7%ab%af%e5%85%83%e4%bb%b6/#respond</comments>
		
		<dc:creator><![CDATA[max-stackoverflow]]></dc:creator>
		<pubDate>Mon, 22 Jun 2026 00:51:02 +0000</pubDate>
				<category><![CDATA[電腦相關應用]]></category>
		<guid isPermaLink="false">https://stackoverflow.max-everyday.com/?p=8587</guid>

					<description><![CDATA[在網頁資安的攻防戰中，很多工程師在被資安掃描器（...]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">在網頁資安的攻防戰中，很多工程師在被資安掃描器（ 例如 ZAP ）洗臉之後，看到報告上寫著大大的「 Medium 風險： style-src 包含 unsafe-inline 」，第一個直覺通常是：「 噢，那我就在 Content-Security-Policy 裡面把 unsafe-inline 拿掉不就得了？」</p>



<p class="wp-block-paragraph">如果你現在用的是 Vue 3 搭配 Element Plus，而你在拿掉的瞬間，你的網頁基本上就半身不遂了。</p>



<ul class="wp-block-list">
<li>短期（建議）： 維持 Element Plus，接受 unsafe-inline Medium 風險，專注業務功能。</li>



<li>中期： 若資安掃描壓力大，評估 Naive UI 替換（相同 Vue 生態、學習成本低）。</li>



<li>長期： 新專案直接選 React + shadcn/ui 或 React + Mantine，避免此問題。</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading">兩大資安大哉問： 到底能不能直接拔掉？</h3>



<p class="wp-block-paragraph">我們先把問題拆成兩個層面來看。</p>



<h4 class="wp-block-heading">1. 在 FastAPI 設定 CSP 跟在 Nginx 設定，有差嗎？</h4>



<p class="wp-block-paragraph">答案是： 完全沒有差別。</p>



<p class="wp-block-paragraph">瀏覽器是很單純的，它只看最後收到的 HTTP response header。 不管這個資安政策是後端框架 FastAPI 吐出來的，還是前端大門 Nginx 塞給它的，對瀏覽器來說都長得一樣。</p>



<p class="wp-block-paragraph">目前專案只有在 FastAPI 設定，那就繼續留在那邊就好，不需要特地搬家。</p>



<h4 class="wp-block-heading">2. 移除 unsafe-inline 之後，Element Plus 還能動嗎？</h4>



<p class="wp-block-paragraph">答案是： 絕對會壞掉，而且壞得很難看。</p>



<p class="wp-block-paragraph">如果你的 CSP 政策直接移除了 unsafe-inline，下面這些畫面上最亮眼的主角會集體罷工：</p>



<ul class="wp-block-list">
<li><strong>el-select / el-dropdown</strong>： 下拉選單直接迷路，位置飛到九霄雲外或是乾脆隱形。</li>



<li><strong>el-tooltip / el-popover</strong>： 滑鼠移過去原本該跳出的提示，現在比你的前任還要冷淡，完全沒反應。</li>



<li><strong>el-dialog</strong>： 彈出視窗直接彈歪，跟你的預期完全對不上。</li>



<li><strong>el-table 固定列</strong>： 說好要固定的欄位，現在放飛自我跟著一起滾動。</li>
</ul>



<p class="wp-block-paragraph">為什麼會這樣？ 因為這些元件在運作時，JavaScript 會在幕後瘋狂計算畫面的座標，然後直接把 <code>style="top: 200px; left: 150px;"</code> 這種類型的行內樣式塞進 HTML 標籤裡。</p>



<p class="wp-block-paragraph">當你把 unsafe-inline 拿掉，瀏覽器就會化身鐵面判官，只要看到這種行內樣式，一律當成非法入侵直接封鎖。 結果就是你的 CSP 報告看起來滿分，但使用者的畫面看起來像一場災難。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading">那難道無解了嗎？ 現代資安的標準解法</h3>



<p class="wp-block-paragraph">好消息是，我們不需要為了資安把整個 Element Plus 換掉（ 畢竟換框架是會出人命的 ）。 在 CSP Level 3 的現代標準中，資安政策已經被細分得更聰明了。</p>



<p class="wp-block-paragraph">以前的 <code>style-src</code> 是一刀切，但現在我們可以把「 標籤 」和「 屬性 」分開管理。</p>



<p class="wp-block-paragraph">你可以試試看這套完美的資安配置：</p>



<p class="wp-block-paragraph">HTTP</p>



<pre class="wp-block-code"><code>Content-Security-Policy: style-src-elem 'self' https://cdnjs.cloudflare.com; style-src-attr 'unsafe-inline';
</code></pre>



<p class="wp-block-paragraph">這段設定的意思其實就是跟瀏覽器講悄悄話：</p>



<ul class="wp-block-list">
<li><strong>style-src-elem &#8216;self&#8217;</strong>： 只要有人想用 <code>&lt;style&gt;</code> 標籤或外掛惡意 CSS 檔案來劫持網頁，一律給我擋掉！ 這招直接封鎖了最危險的 CSS 注入攻擊。</li>



<li><strong>style-src-attr &#8216;unsafe-inline&#8217;</strong>： 至於 HTML 標籤裡面的 <code>style="..."</code> 屬性，我們就開個特權給它過吧。</li>
</ul>



<h4 class="wp-block-heading">改前 vs 改後 戰力分析</h4>



<p class="wp-block-paragraph">這樣改到底差在哪裡？ 我們直接看對比：</p>



<ul class="wp-block-list">
<li><strong><code>&lt;style&gt;</code> 注入攻擊</strong>： 改前是允許的（ 超危險 ），改後直接封鎖（ 安全 ）。</li>



<li><strong>外掛惡意 CSS 檔案</strong>： 改前是允許的（ 超危險 ），改後直接封鎖（ 安全 ）。</li>



<li><strong>Element Plus 動態定位</strong>： 改前正常，改後依然正常運作。</li>



<li><strong>資安掃描器的警告</strong>： 改前會跳出黃牌警告，改後警告直接消失。</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading">結論</h3>



<p class="wp-block-paragraph">想兼顧資安跟漂亮的 UI，不需要直接把 unsafe-inline 趕盡殺絕。</p>



<p class="wp-block-paragraph">透過 <code>style-src-elem</code> 和 <code>style-src-attr</code> 的分工合作，你既能把大門關緊防範黑客，又能留個小窗讓 Element Plus 的元件繼續正常跳舞。 這才是現代工程師優雅的解決之道。</p>



<p class="wp-block-paragraph">結論上來說，改用 </p>



<p class="wp-block-paragraph"></p>
]]></content:encoded>
					
					<wfw:commentRss>https://stackoverflow.max-everyday.com/2026/06/%e6%8a%8a-unsafe-inline-%e5%be%9e%e4%bd%a0%e7%9a%84-csp-%e8%a3%a1%e9%9d%a2%e6%8b%bf%e6%8e%89%ef%bc%9f-%e9%86%92%e9%86%92%e5%90%a7%ef%bc%8c%e4%bd%a0%e7%9a%84%e5%89%8d%e7%ab%af%e5%85%83%e4%bb%b6/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>CSP (Content Security Policy) 的網站要如何安全的使用 Inline script</title>
		<link>https://stackoverflow.max-everyday.com/2026/06/csp-content-security-policy-%e7%9a%84%e7%b6%b2%e7%ab%99%e8%a6%81%e5%a6%82%e4%bd%95%e5%ae%89%e5%85%a8%e7%9a%84%e4%bd%bf%e7%94%a8-inline-script/</link>
					<comments>https://stackoverflow.max-everyday.com/2026/06/csp-content-security-policy-%e7%9a%84%e7%b6%b2%e7%ab%99%e8%a6%81%e5%a6%82%e4%bd%95%e5%ae%89%e5%85%a8%e7%9a%84%e4%bd%bf%e7%94%a8-inline-script/#respond</comments>
		
		<dc:creator><![CDATA[max-stackoverflow]]></dc:creator>
		<pubDate>Thu, 18 Jun 2026 09:01:22 +0000</pubDate>
				<category><![CDATA[css筆記]]></category>
		<category><![CDATA[css]]></category>
		<guid isPermaLink="false">https://stackoverflow.max-everyday.com/?p=8583</guid>

					<description><![CDATA[當你在幫網站做健康檢查，看到弱掃報告（ 像是 Z...]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">當你在幫網站做健康檢查，看到弱掃報告（ 像是 ZAP ）跳出一個橘黃色的 Medium 警告，寫著 <code>style-src 'unsafe-inline'</code> 或是發現 <code>script-src</code> 滿是漏洞時，心裡是不是頓時涼了一半？</p>



<p class="wp-block-paragraph">簡單來說，如果你的 CSP （ 網頁安全政策 ）開了 <code>unsafe-inline</code> ，就等於是在大門裝了密碼鎖，卻把密碼用便利貼貼在門口一樣。駭客只要找到機會塞一段惡意腳本（ XSS 攻擊 ），你的網站就直接變成他的遊樂場。</p>



<p class="wp-block-paragraph">但是偏偏像是 Google Tag Manager （ GTM ）這種必裝的分析工具，官方給的程式碼裡面就是有一大段 Inline Script ：</p>



<p class="wp-block-paragraph">HTML</p>



<pre class="wp-block-code"><code>&lt;script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"&gt;&lt;/script&gt;
&lt;script&gt;
  window.dataLayer = window.dataLayer || &#91;];
  function gtag(){dataLayer.push(arguments);}
  gtag('js', new Date());
  gtag('config', 'G-XXXXXXXXXX');
&lt;/script&gt;
</code></pre>



<p class="wp-block-paragraph">既想要防禦 XSS 攻擊，又不能把 GTM <sup></sup>拔掉，這時候你有兩條路可以走，讓我們來看看這兩個相愛相殺的解決方案。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading">方案一：使用 Nonce （ 一次性密碼鎖 ）</h3>



<p class="wp-block-paragraph">這個方法就像是去看演唱會，每次進場工作人員都會在你的手上蓋一個當天限定、過期失效的印章。</p>



<p class="wp-block-paragraph">在 CSP 標頭（ Header ）裡，你可以這樣設定：</p>



<p class="wp-block-paragraph">HTTP</p>



<pre class="wp-block-code"><code>Content-Security-Policy: script-src 'nonce-每天換的神秘亂數密碼'; img-src www.googletagmanager.com
</code></pre>



<p class="wp-block-paragraph">然後在你的網頁程式碼中，那段 GTM 的標籤也必須帶上相同的密碼：</p>



<p class="wp-block-paragraph">HTML</p>



<pre class="wp-block-code"><code>&lt;script nonce='每天換的神秘亂數密碼'&gt;
  // GTM 的扣（Code）放這裡
&lt;/script&gt;
</code></pre>



<h4 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4a1.png" alt="💡" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 工程師真心話</h4>



<ul class="wp-block-list">
<li><strong>優點</strong>：超級安全。因為駭客根本猜不到你下一個 Request 的亂數密碼是什麼，就算他成功塞了惡意指令，也會因為沒有通關密碼而被瀏覽器直接擋在門外。</li>



<li><strong>缺點（ 也就是坑 ）</strong>：這非常考驗後端的大腦。你的伺服器必須在「 每次 」收到請求時，都用高強度的加密演算法生出一組全新的隨機字串。最慘的是，你的網頁從此跟「 回應快取 」（ Response Cache ）說再見，因為一旦網頁被快取，密碼就不會變了，那這個鎖就形同虛設。</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading">方案二：使用 Hash （ 驗收指紋認證 ）</h3>



<p class="wp-block-paragraph">如果你的網站是 SPA （ 單頁應用程式 ），或是部署在 GitHub Pages 這種根本沒有後端可以幫忙生密碼的靜態伺服器，那 Hash 就是你的救星。</p>



<p class="wp-block-paragraph">這個概念是，瀏覽器會直接去核對這段程式碼的「 指紋 」。</p>



<p class="wp-block-paragraph">步驟非常簡單，把 <code>&lt;script&gt;</code> 到 <code>&lt;/script&gt;</code> 之間的程式碼內容，丟到 <a target="_blank" rel="noreferrer noopener" href="https://stackoverflow.max-everyday.com/wp-admin/post.php?post=8583&amp;action=edit">SHA256 線上計算工具</a> 裡面去跑一下，會得到一串像是 <code>0bbd063b7...</code> 的雜湊值（ Hash ）。</p>



<p class="wp-block-paragraph">接著把這串指紋放進你的 CSP 設定裡：</p>



<p class="wp-block-paragraph">HTTP</p>



<pre class="wp-block-code"><code>Content-Security-Policy: script-src 'sha256-計算出來的那串指紋'; img-src www.googletagmanager.com
</code></pre>



<h4 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4a1.png" alt="💡" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 工程師真心話</h4>



<ul class="wp-block-list">
<li><strong>優點</strong>：前端自己就能搞定，不需要後端伺服器配合，而且網頁依然可以開開心心地做快取，提升載入速度。</li>



<li><strong>缺點（ 最雷的地方 ）</strong>：
<ol start="1" class="wp-block-list">
<li><strong>字元敏感度破表</strong>：SHA256 是一個極度龜毛的演算法。只要你的程式碼裡面多了一個空格、少了一個換行，甚至不同工程師的電腦存檔時，斷行符號一個是 LF （ <code>\n</code> ）一個是 CRLF （ <code>\r\n</code> ），算出來的指紋就完全不一樣。指紋一對不上，你的 GTM 就會直接被 CSP 射下來。</li>



<li><strong>維護地獄</strong>：哪天行銷團隊突然要求修改 GTM 裡面的參數，只要動到一個字，你就必須重新計算一次 Hash 值、重新更新 CSP 標頭。如果交接沒做好，後續接手的工程師改了程式碼發現網站功能壞掉，可能要在電腦前哭著找兇手找很久。</li>
</ol>
</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading">總結防禦指南</h3>



<p class="wp-block-paragraph">想要高枕無憂、後端架構也撐得住，請選 <strong>Nonce</strong> 。</p>



<p class="wp-block-paragraph">如果是靜態網站、追求速度與快取，請選 <strong>Hash</strong> ，但每次改動程式碼時，請記得去燒香拜拜順便更新指紋。</p>



<p class="wp-block-paragraph">總之，快把 <code>unsafe-inline</code> 從你的 CSP 裡面拿掉，別再讓你的網站裸奔了。</p>



<p class="wp-block-paragraph"></p>
]]></content:encoded>
					
					<wfw:commentRss>https://stackoverflow.max-everyday.com/2026/06/csp-content-security-policy-%e7%9a%84%e7%b6%b2%e7%ab%99%e8%a6%81%e5%a6%82%e4%bd%95%e5%ae%89%e5%85%a8%e7%9a%84%e4%bd%bf%e7%94%a8-inline-script/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>vue 3 Element Plus 與 vite 的關係</title>
		<link>https://stackoverflow.max-everyday.com/2026/06/vue-3-element-plus-vite/</link>
					<comments>https://stackoverflow.max-everyday.com/2026/06/vue-3-element-plus-vite/#respond</comments>
		
		<dc:creator><![CDATA[max-stackoverflow]]></dc:creator>
		<pubDate>Thu, 18 Jun 2026 07:29:27 +0000</pubDate>
				<category><![CDATA[React 筆記]]></category>
		<guid isPermaLink="false">https://stackoverflow.max-everyday.com/?p=8581</guid>

					<description><![CDATA[在 Vue 3 的開發世界中，Element P...]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">在 Vue 3 的開發世界中，<strong>Element Plus</strong> 與 <strong>Vite</strong> 是一對<strong>天生一對的最佳拍檔</strong>。它們兩者在專案中扮演著完全不同、卻又緊密合作的角色。</p>



<p class="wp-block-paragraph">簡單來說：<strong>Vue 3 是核心大腦，Element Plus 是外在的精美皮囊（UI 元件），而 Vite 則是負責把這一切快速組裝、運送到瀏覽器上的「超級工廠（構建工具）」。</strong></p>



<p class="wp-block-paragraph">以下為你詳細拆解它們之間的深層關係，以及為什麼現在開發 Vue 3 專案時，大家總是把它們綁在一起：</p>



<h2 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f6e0.png" alt="🛠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 1. 角色定位：元件庫 vs. 構建工具</h2>



<ul class="wp-block-list">
<li><strong>Element Plus (UI 框架)：</strong> 它是寫好的 Vue 3 組件庫（按鈕、表格、下拉選單等）。它關心的是「畫面好不好看」<strong>、</strong>「功能好不好用」。</li>



<li><strong>Vite (前端構建工具)：</strong> 它是新一代的前端開發伺服器與打包工具。它不關心你的按鈕是藍色還是綠色，它關心的是「程式碼載入速度夠不夠快」<strong>、</strong>「打包出來的檔案體積夠不夠小」。</li>
</ul>



<h2 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/26a1.png" alt="⚡" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 2. 為什麼兩者結合會「快上加快」？</h2>



<p class="wp-block-paragraph">Vite 的核心特點是利用了瀏覽器原生的 <strong>ES Modules (ESM)</strong>，在開發環境下不需要像舊時代的 Webpack 一樣把幾萬行程式碼全部打包完才開機。而 Element Plus 也是完全基於 ESM 架構重構的。</p>



<p class="wp-block-paragraph">這帶來了兩個巨大的優勢：</p>



<h3 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4a1.png" alt="💡" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 瞬間啟動 (Lightning Fast Dev Server)</h3>



<p class="wp-block-paragraph">當你在 Vite 專案中引入 Element Plus 時，Vite 會利用 <strong>Esbuild</strong> 對 Element Plus 進行「預構建 (Dependency Pre-bundling)」。這意味著即使 Element Plus 包含幾百個組件，Vite 也能在 <strong>1 秒內</strong>啟動開發伺服器，完全不會因為組件庫太龐大而卡頓。</p>



<h3 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4a1.png" alt="💡" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 完美的自動按需引入 (Tree Shaking)</h3>



<p class="wp-block-paragraph">這是兩者合作最漂亮的亮點。在過去，如果你只用一個 Element Plus 的按鈕 <code>&lt;el-button&gt;</code>，你可能得把幾萬行的整包組件庫都下載到瀏覽器。</p>



<p class="wp-block-paragraph">但在 Vite 生態圈中，透過官方推薦的 Vite 插件（<code>unplugin-vue-components</code> 和 <code>unplugin-auto-import</code>），Vite 可以在編譯時<strong>自動偵測</strong>你在程式碼裡寫了什麼。</p>



<p class="wp-block-paragraph">JavaScript</p>



<pre class="wp-block-code"><code>// vite.config.ts 範例配置
import { defineConfig } from 'vite'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'

export default defineConfig({
  plugins: &#91;
    // Vite 會自動幫你在背後偷偷引入 Element Plus 的組件與樣式
    AutoImport({ resolvers: &#91;ElementPlusResolver()] }),
    Components({ resolvers: &#91;ElementPlusResolver()] }),
  ],
})
</code></pre>



<p class="wp-block-paragraph">有了這個關係，你在寫 Vue 檔案時<strong>完全不需要手動 <code>import</code> 元件</strong>，直接寫 <code>&lt;el-table&gt;</code>，Vite 就會自動幫你打包對應的 JS 和 CSS，做到極致的效能優化（Tree-shaking）。</p>



<h2 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f3a8.png" alt="🎨" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 3. 樣式與主題定制的橋樑</h2>



<p class="wp-block-paragraph">Element Plus 內建使用了大量的 <strong>CSS 變數 (CSS Variables)</strong>。而 Vite 對於 CSS、SCSS 的處理速度極快，且原生支援 CSS 變數。</p>



<ul class="wp-block-list">
<li><strong>開發時：</strong> 當你在 Vite 專案中微調 Element Plus 的主題顏色（例如把品牌色從藍色改成綠色），Vite 的 HMR（熱模組替換）能在<strong>幾毫秒內</strong>讓瀏覽器畫面直接變色，開發體驗非常絲滑。</li>



<li><strong>打包時：</strong> Vite 底層使用 Rollup，它會把 Element Plus 被你用到元件的 CSS 提取出來，與你自己的 CSS 合併並壓縮，這也是為什麼你在生產環境（Production build）時，預設能拿到乾淨、沒有 <code>unsafe-inline</code> 的獨立 CSS 檔案。</li>
</ul>



<h2 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f3af.png" alt="🎯" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 總結</h2>



<p class="wp-block-paragraph">Element Plus 與 Vite 的關係，就像是「優質賽車（Element Plus）」<strong>與</strong>「頂級賽道/後勤團隊（Vite）」。</p>



<p class="wp-block-paragraph">Element Plus 專心提供基於 Vue 3 的強大組件，而 Vite 則利用現代瀏覽器的特性，確保這些組件在開發時能秒速渲染、在部署時能被極致壓縮。這也是目前 Vue 3 生態系中最標準、最推薦的官方級主流配置。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://stackoverflow.max-everyday.com/2026/06/vue-3-element-plus-vite/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>網頁開發者必看！用白話秒懂 CSS Injection 與 CSP 防禦</title>
		<link>https://stackoverflow.max-everyday.com/2026/06/css-injection-csp-url/</link>
					<comments>https://stackoverflow.max-everyday.com/2026/06/css-injection-csp-url/#respond</comments>
		
		<dc:creator><![CDATA[max-stackoverflow]]></dc:creator>
		<pubDate>Thu, 18 Jun 2026 06:39:22 +0000</pubDate>
				<category><![CDATA[css筆記]]></category>
		<category><![CDATA[React 筆記]]></category>
		<category><![CDATA[css]]></category>
		<guid isPermaLink="false">https://stackoverflow.max-everyday.com/?p=8572</guid>

					<description><![CDATA[為什麼連寫個看起來無害的 CSS 都會被瀏覽器當...]]></description>
										<content:encoded><![CDATA[
<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="572" src="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/06/css-injection-csp-url__clean-1024x572.jpg?v=1781764740" alt="" class="wp-image-8579" srcset="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/06/css-injection-csp-url__clean-1024x572.jpg?v=1781764740 1024w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/06/css-injection-csp-url__clean-600x335.jpg?v=1781764740 600w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/06/css-injection-csp-url__clean-768x429.jpg?v=1781764740 768w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/06/css-injection-csp-url__clean.jpg?v=1781764740 1376w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">為什麼連寫個看起來無害的 CSS 都會被瀏覽器當成恐怖份子。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">瀏覽器的大內總管：CSP 到底在嚴格什麼？</h2>



<p class="wp-block-paragraph">各位前端與後端苦力們，有沒有遇過這種慘劇？</p>



<p class="wp-block-paragraph">當你興高采烈地在網站加上這行內容安全政策（CSP）：</p>



<pre class="wp-block-code"><code><code>Content-Security-Policy: default-src 'self';</code></code></pre>



<p class="wp-block-paragraph">本以為從此高枕無憂，結果下一秒打開網頁，發現自己寫的行內腳本（inline-script）全部陣亡：</p>



<pre class="wp-block-code"><code><code>&lt;script&gt;console.log('連我都不能跑是怎樣');&lt;/script&gt;</code></code></pre>



<p class="wp-block-paragraph">好啦，大家都知道 JavaScript 權限大、壞人最愛用，所以瀏覽器預設不讓它亂跑很合理。為了安撫大內總管，我們還得乖乖去算雜湊值（Hash）或是塞個 nonce 安全憑證，像是這樣：</p>



<pre class="wp-block-code"><code><code>Content-Security-Policy: default-src 'self'; script-src 'sha256-xxx...';</code></code></pre>



<p class="wp-block-paragraph">但是，最讓人傻眼的是，連單純想幫網頁化妝的行內樣式（inline-style）也被一起關進大牢了：</p>



<pre class="wp-block-code"><code><code>&lt;style&gt;h1 { color: yellow; }&lt;/style&gt;</code></code></pre>



<p class="wp-block-paragraph">CSS 到底做錯了什麼？它只是個孩子啊！</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">警匪片現場：當 CSS 變成滲透工具</h2>



<p class="wp-block-paragraph">資安世界有三大神聖支柱：機密性、完整性、可用性。而隨便亂加的 CSS，其實正在默默破壞這一切。</p>



<h3 class="wp-block-heading">疑犯一號：DOM 元素私生子（破壞完整性）</h3>



<p class="wp-block-paragraph">你可能會想：「這網頁是我寫的，裡面的 CSS 當然也是我寫的啊！」</p>



<p class="wp-block-paragraph">事情可沒那麼簡單。在 JavaScript 的世界裡，壞人可以用各種神奇手法動態產生元素：</p>



<p class="wp-block-paragraph">JavaScript</p>



<pre class="wp-block-code"><code>let style = document.createElement('style');
style.innerText = 'h1 { color: blue; }';
document.body.appendChild(style);
</code></pre>



<p class="wp-block-paragraph">這段程式碼一旦被惡意注入，它就能神不知鬼不覺地改掉你的網頁外觀。這就是為什麼瀏覽器乾脆一不做二不休，預設把所有沒登記過的行內樣式全部封鎖。</p>



<h3 class="wp-block-heading">疑犯二號：假藉美化名義的間諜（破壞機密性）</h3>



<p class="wp-block-paragraph">CSS 裡面有兩個看起來很無辜、但實際上超常被當作間諜的語法： <code>@import</code> 和 <code>url()</code> 。</p>



<p class="wp-block-paragraph">當你用了背景圖片：</p>



<pre class="wp-block-code"><code><code>background-image: url(https://example.com/spy.jpg);</code></code></pre>



<p class="wp-block-paragraph">瀏覽器看到這行，就會自動發出一個請求去下載圖片。這時候，壞人就可以利用這個機制搞鬼：</p>



<ul class="wp-block-list">
<li><strong>偷看你的行蹤（Beacon 技術）：</strong> 只要把圖片網址加上個人專屬的編碼，當你一打開網頁，壞人的伺服器收到圖片請求，就知道「喔！某某某在幾點幾分看了這個頁面！」</li>



<li><strong>Cookie 順風車：</strong> 如果網站沒設定好，這個請求甚至會順便把你的 Cookie 跨站送出去，直接把你的登入狀態雙手奉上。</li>
</ul>



<p class="wp-block-paragraph">所以，想要在有 CSP 的防護下安全地秀出圖片，你還必須跟總管報備圖片的白色名單：</p>



<pre class="wp-block-code"><code><code>Content-Security-Policy: default-src 'self'; style-src 'sha256-...'; img-src http://信任的圖片來源;</code></code></pre>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">結論</h2>



<p class="wp-block-paragraph">在這個壞人招數層出不窮的時代，網頁開發者真的不能只活在「CSS 只是用來排版」的粉紅泡泡裡。多了解一點 CSP 的龜毛規則，雖然設定時會寫到抓狂，但至少能保住大家的飯碗，不讓網站變成駭客的免費遊樂園！</p>



<p class="wp-block-paragraph"></p>
]]></content:encoded>
					
					<wfw:commentRss>https://stackoverflow.max-everyday.com/2026/06/css-injection-csp-url/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Vite 打包的大坑：為什麼測試環境 staging 默默變成了 production ？</title>
		<link>https://stackoverflow.max-everyday.com/2026/06/vite-staging-production-build-check/</link>
					<comments>https://stackoverflow.max-everyday.com/2026/06/vite-staging-production-build-check/#respond</comments>
		
		<dc:creator><![CDATA[max-stackoverflow]]></dc:creator>
		<pubDate>Thu, 18 Jun 2026 02:00:50 +0000</pubDate>
				<category><![CDATA[React 筆記]]></category>
		<category><![CDATA[React]]></category>
		<guid isPermaLink="false">https://stackoverflow.max-everyday.com/?p=8568</guid>

					<description><![CDATA[當我們在開發前端專案時，常常會遇到明明在本地開發...]]></description>
										<content:encoded><![CDATA[
<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="572" src="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/06/vite-staging-production-build-check_clean-1024x572.jpg?v=1781748044" alt="" class="wp-image-8570" srcset="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/06/vite-staging-production-build-check_clean-1024x572.jpg?v=1781748044 1024w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/06/vite-staging-production-build-check_clean-600x335.jpg?v=1781748044 600w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/06/vite-staging-production-build-check_clean-768x429.jpg?v=1781748044 768w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/06/vite-staging-production-build-check_clean.jpg?v=1781748044 1376w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">當我們在開發前端專案時，常常會遇到明明在本地開發測試都好好的，但一打包部署到測試環境（ staging ），某些功能就莫名其妙失蹤的靈異事件！</p>



<p class="wp-block-paragraph">這次苦主遇到的狀況是：使用 pnpm build:staging 指令打包後，左側導覽列的「管理員選單（ isAdmin ）」居然直接蒸發了！</p>



<p class="wp-block-paragraph">經過一番鍵盤柯南式的徹查，終於抓到幕後黑手，原來是我們自以為很懂的 Vite 在搞鬼。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f50d.png" alt="🔍" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 案發現場：為什麼選單不見了？</h2>



<p class="wp-block-paragraph">在原本的程式碼中，開發者用了 import.meta.env.PROD 來做判斷。心裡想著：「開發環境（ dev ）是 false ，測試環境（ staging ）應該也是 false ，只有真正的正式環境（ production ）才會是 true 吧？」</p>



<p class="wp-block-paragraph">結果 Vite 表示：「你想得美！」</p>



<p class="wp-block-paragraph">我們來看看 Vite 在不同指令下的真實真面目：</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><td><strong>執行的指令</strong></td><td><strong>MODE 變數</strong></td><td><strong>PROD 變數（ 關鍵在這裡 ）</strong></td><td><strong>導致的後果</strong></td></tr></thead><tbody><tr><td>vite (dev)</td><td>development</td><td>false</td><td>順利繞過檢查，選單正常顯示。</td></tr><tr><td>vite build &#8211;mode staging</td><td>staging</td><td><strong>true</strong> <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f6a8.png" alt="🚨" class="wp-smiley" style="height: 1em; max-height: 1em;" /></td><td>誤判為正式環境，跑去呼叫真實 API 檢查，結果因為 IP 不對直接被拒絕，選單慘遭隱形。</td></tr><tr><td>vite build</td><td>production</td><td>true</td><td>正常的正式環境流程。</td></tr></tbody></table></figure>



<p class="wp-block-paragraph"><strong>破案關鍵</strong>：在 Vite 的世界裡，只要你執行了 vite build 這個打包指令，不管後面帶的 &#8211;mode 是什麼，它都會偷偷把 NODE_ENV 設定為 production 。因此， import.meta.env.PROD 在測試環境下也會被無情地判定為 true ！</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f6e0.png" alt="🛠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 拯救選單：動手修正它</h2>



<p class="wp-block-paragraph">既然知道了 Vite 的脾氣，修正的方法就很簡單了。我們不要再相信傲嬌的 PROD 變數，直接去檢查 MODE 的名字是不是叫做 production 就好了！</p>



<h3 class="wp-block-heading">程式碼改動</h3>



<p class="wp-block-paragraph">請把原本的防禦邏輯修改如下：</p>



<p class="wp-block-paragraph">JavaScript</p>



<pre class="wp-block-code"><code>// &#x274c; 原本的寫法（ 會誤判測試環境 ）
if (!import.meta.env.PROD) { ... }

//  修正後的寫法（ 精準識別是不是真的正式環境 ）
if (import.meta.env.MODE !== 'production') { ... }
</code></pre>



<p class="wp-block-paragraph">同時，別忘了同步更新專案的 README.md 說明文件，讓未來的自己或其他同事不會再踩進同一個坑裡。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f389.png" alt="🎉" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 修正後的快樂結局</h2>



<p class="wp-block-paragraph">改用 import.meta.env.MODE 來把關之後，判斷邏輯就變得非常聽話了：</p>



<ul class="wp-block-list">
<li>pnpm run dev <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f449.png" alt="👉" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 模式為 development <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f449.png" alt="👉" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 順利放行，直接顯示選單！</li>



<li>pnpm run build:staging <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f449.png" alt="👉" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 模式為 staging <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f449.png" alt="👉" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 順利放行，直接顯示選單！</li>



<li>pnpm run build <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f449.png" alt="👉" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 模式為 production <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f449.png" alt="👉" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 嚴格把關，呼叫 API 檢查！</li>
</ul>



<p class="wp-block-paragraph">現在重新執行 pnpm build:staging 打包，那個令人朝思暮想的管理員選單終於乖乖下凡，回歸到它該出現的地方囉！</p>
]]></content:encoded>
					
					<wfw:commentRss>https://stackoverflow.max-everyday.com/2026/06/vite-staging-production-build-check/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
