2020年2月22日 星期六

[C# ASP.NET]實現OAuth2授權在Google上(一)-基礎程式篇

  Google本身有提供很多API供設計師來抓取Google帳號資料,
但前提是必須由User同意授權,才能依憑證抓取相對應的資料。

本篇用簡單的方法來實現OAuth2授權並獲取GMail信箱(範例框架為webform,實現方式主要用Http來撰寫)

官方說明文件:
https://developers.google.com/identity/protocols/OAuth2

前置動作:
1.先到Google API Console 新建專案(https://console.developers.google.com/apis/credentials)
2.並設定OAuth 2.0的憑證和OAuth同意畫面

待會程式要做的步驟:
1.呼叫Google提供的 Authorize頁面
2.使用者按下同意後,接收回傳的Authorize code
3.用得到Authorize code和Goole來交換Access Token和Refresh Token
(Refresh Token非必定回傳,但是需要離線授權時會用到,因為Access Token只有1hr的效力)
4.用Access Token來呼叫Google API來獲取要求的User資料

1.創立空白頁後,在畫面上放一個Button(此按鈕作為登入按鈕),
並在Click事件上撰寫下面程式碼

//登入按鈕事件
protected void Btn_GoogleLogIn_Click(object sender, EventArgs e)
{
    //發送請求token 來 得到授權碼(Authorization code)
    //Authorize網址

    string StrUrl = "https://accounts.google.com/o/oauth2/auth";

    //Get參數
    StringBuilder StrParam= new StringBuilder();

    //client_id(Google API Console 中可以找到 新建立專案的編號)
    StrParam.Append("client_id=760...j8f.apps.googleusercontent.com&");
    //redirect_uri(Google API Console 中設定CallBack的頁面)
    StrParam.Append("redirect_uri=http://localhost:55036/GoogleCallBack.aspx&");
    //response_type(回傳 Authorization code)
    StrParam.Append("response_type=code&");
    //要求授權scope(這邊是用 Google OAuth2 API 底下 Scopes , 目的是要抓取使用者信箱)
    StrParam.Append("scope=https://www.googleapis.com/auth/userinfo.email&");
    //access_type(online或offline 選擇離線,如此才會回傳refresh token)
    StrParam.Append("access_type=offline&");
    //state 此值會隨著CallBack回來(可塞入Session token或者一些驗證機制,防止跨站請求偽造)這邊先不用
    StrParam.Append("state=&");

    //連結網址
    Response.Redirect(StrUrl +"?" + StrParam.ToString());


以上網址最後會導入到如下圖頁面
User在此頁登入Google帳號後,會跳轉到上面Get參數redirect_uri所指定的頁面

2.創立對應CallBack回來的頁面GoogleCallBack.aspx,並加入下面程式碼
畫面上放一個Label用來顯示錯誤訊息

protected void Page_Load(object sender, EventArgs e)
{
    //接收Google CallBack回來的參數(Get形式)
    //Google回傳的錯誤
   
    string StrErr = Request.QueryString["error"] == null ? "" : Request.QueryString["error"];
   
    //有錯誤的情況
    if (StrErr.Length > 0)
    {
        //顯示在畫面上,並且不繼續處理
        lbl_Msg.Text = StrErr;
        return;
    }

    //Authorize code
    string StrAuthCode = Request.QueryString["code"] == null ? "" : Request.QueryString["code"];

    if (StrAuthCode.Length == 0)
    {
        lbl_Msg.Text = "沒有獲取授權碼!!";
        return;
    }

    //用Authorize code 去交換 Access token 及 Refresh token
    string StrAccToken = "";
    string StrRefToken = "";
    StrErr = AuthCodeChgToken(StrAuthCode, ref StrAccToken, ref StrRefToken);

    //有錯誤的情況
    if (StrErr.Length > 0)
    {
        //顯示在畫面上,並且不繼續處理
        lbl_Msg.Text = StrErr;
        return;
    }

    //呼叫API用 Access token 去得到 GMail
    string StrGMail = "";
    StrErr = AccTokenChgGMail(StrAccToken, ref StrGMail);

    //有錯誤的情況
    if (StrErr.Length > 0)
    {
        //顯示在畫面上,並且不繼續處理
        lbl_Msg.Text = StrErr;
        return;
    }

    //成功得到 GMail及Refresh token後 可以存在資料庫中,
    //之後可以直接用Refresh token去得到Access token, 不用再經過授權

    StrErr = SaveSQL(StrGMail, StrRefToken);

    //有錯誤的情況
    if (StrErr.Length > 0)
    {
        //顯示在畫面上,並且不繼續處理
        lbl_Msg.Text = StrErr;
        return;
    }
}

/// <summary>
/// 用Authorize code 去得到 Access token 及 Refresh token
/// </summary>
/// <param name="AuthCode">Authorize code</param>
/// <param name="AccToken">Access token</param>
/// <param name="RefToken">Refresh token</param>
/// <returns>錯誤訊息</returns>

private string AuthCodeChgToken(string AuthCode, ref string AccToken, ref string RefToken)
{
    //Token網址
    string StrUrl = "https://oauth2.googleapis.com/token";

    //Post參數
    StringBuilder StrParam = new StringBuilder();
    //Authorize code
    StrParam.Append("code=" + AuthCode + "&");
    //client_id(Google API Console 中可以找到 此專案的編號)
    StrParam.Append("client_id=760...j8f.apps.googleusercontent.com&");
    //client_secret(Google API Console 中可以找到 此專案的密碼)
    StrParam.Append("client_secret=dHN...3b&");
    //redirect_uri(Google API Console 中設定CallBack的頁面)
    StrParam.Append("redirect_uri=http://localhost:55036/GoogleCallBack.aspx&");
    //grant_type
    StrParam.Append("grant_type=authorization_code&");

    //接收回傳內容 Json
    string StrReJson = "";

    using (WebClient WClient = new WebClient())
    {
        WClient.Headers.Add("content-type", "application/x-www-form-urlencoded");
        try
        {
            StrReJson = WClient.UploadString(StrUrl, "POST", StrParam.ToString());
        }
        catch
        {
            return "獲取Token失敗!";
        }
    }

    //解析JSON
    JObject Jobj= JsonConvert.DeserializeObject<JObject>(StrReJson);

    try{
        AccToken = Jobj["access_token"].ToString();
        //只有在初次User授權且請求參數為access_type=offline時才會得到
        //可以刪除Cookie來清掉User授權

        RefToken = Jobj["refresh_token"].ToString();
    }
    catch
    {
        return "解析回傳Token失敗!";
    }

    return "";
}

/// <summary>
/// 用 Access token 去得到 GMail
/// </summary>
/// <param name="AccToken">Access token</param>
/// <param name="GMail">GMail</param>
/// <returns>錯誤訊息</returns>

private string AccTokenChgGMail(string AccToken, ref string GMail)
{
    //API網址
    string StrUrl = "https://www.googleapis.com/oauth2/v2/userinfo";

    //接收回傳內容 Json
    string StrReJson = "";

    using (WebClient WClient = new WebClient())
    {
        WClient.Headers.Add("Authorization","Bearer " + AccToken);
        try
        {
            StrReJson = WClient.DownloadString(StrUrl);
        }
        catch
        {
            return "獲取GMail失敗!";
        }
    }

    //解析JSON
    JObject Jobj = JsonConvert.DeserializeObject<JObject>(StrReJson);

    try
    {
        GMail = Jobj["email"].ToString();
    }
    catch
    {
        return "解析回傳GMail失敗!";
    }

    return "";

}
   
/// <summary>
/// 將 Refresh token及GMail 儲存到SQL
/// </summary>
/// <param name="GMail"></param>
/// <param name="RefToken"></param>
/// <returns>錯誤訊息</returns>

private string SaveSQL(string GMail, string RefToken)
{
    //StrConn:連線字串自行定義
    using (SqlConnection Conn = new SqlConnection(StrConn))
    {
        //開啟連線
        Conn.Open();

        string StrSQL = "Insert Into GoogleOAuth2 (Gmail, RefreshToken) Values(@Gmail, @RefreshToken)";
        SqlCommand cmd = new SqlCommand(StrSQL, Conn);
        cmd.Parameters.AddWithValue("@Gmail", GMail);
        cmd.Parameters.AddWithValue("@RefreshToken", RefToken);

        try
        {
            cmd.ExecuteNonQuery();
        }
        catch
        {
            //關閉連線
            Conn.Close();
            return "寫入資料庫失敗!";
        }

        //關閉連線
        Conn.Close();
    }

    return "";
}



到這邊已經完成大部分內容,事實上這只是初步的運用,
如果有需要做到更困難的應用,建議可以直接用Google提供給各種程式語言的函式庫。

而用Refresh Token來刷新Access Token的部分,我將放到下一篇文章裡

沒有留言:

張貼留言