﻿# 如何使用 .NET Core 串接 e 政府服務平臺單一登入暨多因子驗證機制

我記得我最早在 2008 年就因為一個標案串接過 e 政府服務平臺的單一登入機制，事隔多年後，竟然什麼也沒有變，還是一樣透過 SOAP Web Service 來進行驗證。今天這篇文章我就來說說如何透過 .NET 7 實作 e 政府服務平臺的單一簽入機制，以及說明如何用 .NET 呼叫以 SOAP 為基礎的 Web Service 服務。

![SSO](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/ae6a7db448c811a709fe-e6a35d2b-405d-423f-8078-553feba9f7c0.webp)

## 大致整合步驟

關於**單一登入暨多因子驗證**的相關文件，可以參考這裡：[https://web.pcc.gov.tw/](https://web.pcc.gov.tw/)

基本上要完成單一簽入機制，只需要以下幾個步驟：

1.  組成一個單一簽入網址，將使用者導向至 e 政府服務平臺單一登入網頁
    
    網址結構如下：
    
    `https://www.cp.gov.tw/portal/Clogin.aspx?ReturnUrl={回呼網址}&level={驗證等級}`
    
    > 注意: 這裡的 **回呼網址** 不能隨便填寫，在 e 政府服務平台會做**域名驗證**，你必須在機關單位申請單一簽入時就要指定好，未指定的網址將無法完成單一簽入作業。這裡只會驗證 `Hostname` (域名) 部分，不會驗證 `Scheme` 或 `Port` 或 `Pathinfo` 等部分。
    
    我是這樣實作的：
    
    ```csharp
    void Main()
    {
        var url = GetLoginUrl("https://mysite.com.tw/callback", AuthLevel.帳號登入);
        //var url = GetLoginUrl("https://mysite.com.tw/callback", LoginType.憑證登入);
    }
    
    public string GetLoginUrl(string returnUrl, AuthLevel level)
    {
        var loginUrl = "https://www.cp.gov.tw/portal/Clogin.aspx";
    
        // 需安裝 Microsoft.AspNetCore.Http.Extensions 套件
        var qb = new QueryBuilder();
        qb.Add("ReturnUrl", returnUrl);
        qb.Add("level", level.ToString("D"));
    
        return $"{loginUrl}{qb.ToQueryString().Value}";
    }
    
    public enum AuthLevel
    {
        帳號登入 = 1,
        憑證登入 = 2
    }
    ```
    
2.  使用者登入成功後，會透過 HTTP POST 回到 `ReturnUrl` 指定的網址，你要透過 `application/x-www-form-urlencoded` 的方式取得 `twGovT1` 傳入欄位的值。
    
    以下我用 ASP.NET Core 7 Minimal API 的語法示範如何取得 `twGovT1` 欄位的值 (也就是所謂的 `Token1` 值)：
    
    ```csharp
    app.MapPost("/callback", async Task<string> (HttpRequest request) =>
    {
        var formData = await request.ReadFormAsync();
        var formDataDictionary = formData.ToDictionary(x => x.Key, x => x.Value.ToString());
    
        return formDataDictionary["twGovT1"];
    })
    ```
    
    > 注意: 直到 ASP.NET Core 7.0 的 Minimal API 都還沒有支援 `[FromForm]` 的繫結方式，必須等 ASP.NET Core 8.0 才開始有支援 [Binding to forms](https://learn.microsoft.com/en-us/aspnet/core/release-notes/aspnetcore-8.0?view=aspnetcore-7.0&WT.mc_id=DT-MVP-4015686#binding-to-forms)。
    
3.  取得 Token1 之後，還要呼叫一個以 SOAP 為主的 Web Service，取得使用者基本資料，這才算完成單一簽入流程。
    
    這個步驟稍微複雜些，我將在下一小節說明。
    
    > 注意: 由於 `ServiceID` 必須要透過機關單位申請後才能取得，而且該 Web Service 在呼叫時會驗證指定的來源 IP 地址，若申請單一簽入時沒有申請到正確的 IP 地址，將無法正確的呼叫該 Web 服務。
    

### 如何透過 .NET 呼叫以 SOAP 為主的 Web Service

其實你要撰寫 SOAP 的 Client 端，透過 Visual Studio 2022 其實蠻容易的，大致步驟如下：

1.  開啟 Connected Service 視窗
    
    ![image](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/dd9d23944bcc6025ecc8-9788277e-3442-4d89-a3b3-33416f91851e.webp)
    
2.  點擊 Add a service reference
    
    ![image](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/1a5ffd820d55a35a0657-5b498a5a-d03d-491b-b17a-ae5b491b113a.webp)
    
3.  選擇 WCF Web Service
    
    ![image](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/76ea865ccc49998e418f-da313fe5-3101-43f5-b2db-63542f3c6dcf.webp)
    
4.  輸入 URI ( e 政府服務平臺的單一登入機制的 Web Service 網址為 [https://www.cp.gov.tw/gsp2ws/rsmediator01.asmx](https://www.cp.gov.tw/gsp2ws/rsmediator01.asmx) )
    
    ![image](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/736a2d7ae055e5596788-47a19aff-6012-4284-871e-1b23d469abdd.webp)
    
5.  查看 Web Service 內容，並按下 Next 進入下一步
    
    ![image](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/ba1f00ffbf6aca5bb8c5-dc8ac837-79b7-4d8f-a53b-af10becad612.webp)
    
6.  按下 Next 進入下一步
    
    ![image](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/5cac7bdee8e39fd96a1d-4134c759-d435-444c-9ecd-2d1a9692c54c.webp)
    
7.  按下 Finish 完成
    
    ![image](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/fd04961604f2a961f709-82da98ae-b5bc-4502-930e-3b31de5c4e84.webp)
    
8.  按下 Close 關閉對話框
    
    ![image](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/327db434bc8cfc356622-c68a3925-1dcb-450b-9a02-d6d4627edb00.webp)
    
9.  完成後會自動產生一個 `Connected Services\ServiceReference1\Reference.cs` 檔案
    
    ![image](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/ad6da859c355ae00bb18-ad81e99c-8022-45d5-952d-812e3d86c4c3.webp)
    

如果不用 Visual Studio 2022 的話，就要透過 [dotnet-svcutil](https://www.nuget.org/packages/dotnet-svcutil) 全域工具來快速產生程式碼：

1.  安裝全域工具
    
    ```bash
    dotnet tool install --global dotnet-svcutil
    ```
    
2.  產生參考類別
    
    ```bash
    dotnet svcutil https://www.cp.gov.tw/gsp2ws/rsmediator01.asmx
    ```
    
    這個命令會產生 `ServiceReference\Reference.cs` 程式碼內容，其內容跟 Visual Studio 2022 產生的程式碼一樣。
    
3.  將 `ServiceReference\Reference.cs` 程式碼複製到你的專案下即可！
    

當我們擁有了 SOAP 的 Client 函式庫，剩下的就很簡單了，要呼叫 Web Service 只需要以下幾個步驟：

1.  建立 Web Service 的 Client 實例
    
    ```csharp
    var client = new GSP2_RS_Service_01SoapClient(GSP2_RS_Service_01SoapClient.EndpointConfiguration.GSP2_RS_Service_01Soap);
    ```
    
2.  呼叫並傳入 `AuthHeader` 參數
    
    ```csharp
    var result = await client.GetProfileColumns2Async(new AuthHeader()
    {
        ServiceID = "APP0000001",
        Token1 = "7c2232c2ca3a4a002fc4127d4c2ca8a9"
    });
    ```
    
3.  判斷是否有成功取得使用者基本資料
    
    ```csharp
    if (result.GetProfileColumns2Result.Code == 0)
    {
        Console.WriteLine("成功取得個人資料");
    }
    ```
    

### 相關連結

-   [e政府服務平臺營運中心網站 - 下載專區 - 單一登入暨多因子驗證](https://web.pcc.gov.tw/)
-   [WCF svcutil tool overview - .NET | Microsoft Learn](https://learn.microsoft.com/en-us/dotnet/core/additional-tools/dotnet-svcutil-guide?tabs=dotnetsvcutil2x&WT.mc_id=DT-MVP-4015686)
