
當我想透過前端存取 HiPKI Local Server 的時候,會遇到跨域存取問題。本篇將說明如何來解決。
跨來源資源共用(CORS)
前幾篇提到要取得卡片資訊可以到 http://127.0.0.1:61161/pkcs11info?withcert=true。在自己網站透過 fetch 該路徑會出現下圖的錯誤,因為我們的網站與 localhost 是不同網域,沒辦法做跨域存取,被瀏覽器擋下來。

原因是因為 CORS,CORS 代表跨來源資源共用(Cross-Origin Resource Sharing)。它是一種瀏覽器的安全性機制,用於控制網頁應用程式中的網頁來自不同源的資源是否可以被請求。網頁中的 JavaScript 通常僅能夠請求與包含該 JavaScript 檔案的網頁相同源的資源。「相同源」是指兩個網址的協議(通常是 http 或 https)、域名和埠(如果有指定埠)都相同。這是出於安全性考慮,以防止惡意網站盜用用戶在其他網站上的資源。
常見的 CORS 解決辦法
解決 CORS(Cross-Origin Resource Sharing)的問題有幾種方法:
- 伺服器端處理: 在伺服器端進行相應的配置,以允許跨來源請求。你可以在 HTTP 回應標頭中添加特定的 CORS 相關標頭,例如
Access-Control-Allow-Origin。 - 代理伺服器: 在你的伺服器和外部伺服器之間設置一個代理伺服器。客戶端只和代理伺服器通信,而代理伺服器負責轉發請求到外部伺服器。這樣,由於同源政策僅適用於瀏覽器,伺服器間的通信不受影響。
- 使用 JSONP(僅限 GET 請求): JSONP 是一種繞過同源政策的方法,但僅適用於支持 JSONP 的 API 和僅支持 GET 請求的情況。請確保你的 API 支持 JSONP 並遵從安全最佳實踐。
上述方法各有優勢和限制,選擇取決於你的應用程式需求和伺服器設置。伺服器端處理是最常見和最具靈活性的方法,但可能需要你有控制伺服器的權限。
postMessage 解決 CORS
除了上面幾種辦法,還可以在前端使用 postMessage 來解決 CORS。postMessage 是一種 HTML5 提供的跨窗口通訊機制,可以用來解決前端的跨來源請求(CORS)問題。這種方法通常被用於在不同的瀏覽器窗口或 iframe 之間進行通訊。
基本思想是,一個窗口(或 iframe)向另一個窗口(或 iframe)發送訊息,並且在接收訊息的窗口中處理相應的邏輯。這樣,不同源的窗口之間可以協同工作而無需依賴同源政策。
以下是使用 postMessage 解決 CORS 問題的基本步驟:
1. 在發送窗口(Sender Window)中:
- 使用
postMessage方法將訊息發送到目標窗口。這個方法接受兩個參數:訊息內容和目標窗口的來源(Origin)。
const targetWindow = window.open("https://target-domain.com")// targetWindow 或者指定特定 iframe 的 windowtargetWindow.postMessage('Hello, other window!', '*');2. 在接收窗口(Receiver Window)中:
- 在目標窗口中,需要添加一個監聽器以處理從發送窗口接收到的訊息。
- 監聽
message事件,檢查訊息的來源,確保它是來自預期的窗口。如果是,則執行相應的邏輯。
window.addEventListener('message', (event) => { // 檢查訊息的來源是否符合預期的來源 if (event.origin === 'https://sender-domain.com') { // 處理接收到的訊息 console.log('Received message:', event.data); }});這種方法可以用於解決跨域的問題,但要確保在發送和接收窗口之間建立了安全的通信。因此,確保在發送訊息時指定正確的目標窗口的來源,並在接收訊息時驗證訊息的來源,以防止潛在的安全漏洞。
HiPKI Local Server 的 popupForm
HiPKI Local Server 的 popupForm 是以 postMessage 解決跨域存取問題,溝通流程如下:
- 在網站會需要先呼叫 window.open 彈出 popupForm 視窗(http://localhost:61161/popupForm)
- popupForm 視窗開啟後,會回傳 getTbs 訊息到網站,代表呼叫成功
- 網站收到 getTbs 後,將要執行的指令傳送到 popupForm 視窗
- popupForm 視窗將執行結果與資料傳送回網站

popupForm 程式碼
以下是 popupForm 程式碼,可以了解業務邏輯,與交換資料的方法。由於 popupForm 在回傳資料使用 window.opener,因次只能用在彈跳小視窗,沒辦法把 popupForm 放在 iframe 內。
<!DOCTYPE html><html> <head> <meta charset="UTF-8" /> </head>
<body> <h2><span id="actionText"></span></h2> <img src="waiting.gif" /> <form name="DemoForm" action="sign" method="POST"> <input type="hidden" name="tbsPackage" /> </form> <script type="text/javascript"> function receiveMessage(event) { var matchstr = "(.+)($|:\\d+$)"; if (event.origin.match(matchstr) == null) { var ret = {}; ret["func"] = "sign"; ret["ret_code"] = 0x7600000f; ret["message"] = "禁止存取的網域"; window.opener.postMessage(JSON.stringify(ret), "*"); window.close(); return; } var ret = JSON.parse(event.data); var actionText = document.getElementById("actionText");
if (ret.func) { var action = null; var actionFun = null; if (ret.func == "MakeSignature") { action = "sign"; actionFun = "sign"; actionText.innerHTML = "簽章中"; } else if (ret.func == "umakeSig") { action = "umakeSig"; actionFun = "umakeSig"; actionText.innerHTML = "簽章中"; } else if (ret.func == "GetUserCert") { action = "pkcs11info?withcert=true"; actionFun = "pkcs11info"; actionText.innerHTML = "憑證讀取中"; } else if (ret.func == "DecryptData") { action = "decrypt"; actionFun = "decrypt"; actionText.innerHTML = "解密中"; } else if (ret.func == "writecert") { action = "writecert"; actionFun = "writecert"; actionText.innerHTML = "寫憑證中"; } else if (ret.func == "CheckEnvir") { action = "pkcs11info"; actionFun = "pkcs11info"; actionText.innerHTML = "資料讀取中"; } else if (ret.func == "changeUserPINCode") { action = "CardManagement/sendCardCommand"; actionFun = "changeUserPINCode"; actionText.innerHTML = "密碼變更中"; } else if (ret.func == "makeCsr") { action = "makeCsr"; actionFun = "makeCsr"; actionText.innerHTML = "製作憑證請求檔中"; } else if (ret.func == "signDetached") { action = "signDetached"; actionFun = "signDetached"; actionText.innerHTML = "簽章中"; }
if (action != null) { var xhttp = new XMLHttpRequest(); xhttp.onreadystatechange = function () { if (xhttp.readyState == 4) { if (xhttp.status == 200) { window.opener.postMessage(xhttp.responseText, "*"); window.close(); } else { window.opener.postMessage( '{"func":"' + actionFun + '","ret_code":' + xhttp.status + "}", "*" ); window.close(); } } }; xhttp.open("POST", action, true); xhttp.send("tbsPackage=" + event.data); } else { window.opener.postMessage( '{"func":"' + actionFun + '","ret_code":' + 0x7600000e + "}", "*" ); window.close(); } } } window.addEventListener("message", receiveMessage, false); var tbsData = {}; tbsData["func"] = "getTbs"; var msg = JSON.stringify(tbsData); var postTarget = window.opener; postTarget.postMessage(msg, "*"); </script> <br /> </body></html>結語
透過 postMessage 可以與 HiPKI Local Sever 進行交換資料,解決 CORS 問題,下一篇將說明如何與透過 postMessage 與 popupForm 操作自然人憑證。
▶ 閱讀更多:深入淺出自然人憑證