Google登入-手動建立登入 OAuth 2
Google 文件我實在不太習慣,所以這邊算是有點東拼西湊的,照自己的理解實作,我會把找到的文件參考連結,放在步驟裡面
Google 官方也有一份.NET OAuth,似乎是獨立文件,只是裡面有很多使用Google 自行開發的Library,我沒有採用,一樣放在參考部分
實作
-
先找到OAuth 驗證連結,參考要傳送的選項,組合驗證連結,這邊要留意文件中Score說明
1
2
3
4
5
6
7
8
9
10
11
12
13
|
public IActionResult Index()
{
State = Guid.NewGuid();
// scope 來自於要啟用的Google API,因為我們只是要取得最基本的資料,所以這邊直接設定 profile
// 如果是使用不同的Google API服務,這邊的Scope值也會不同
ViewData["GoogleAuth"] = $"https://accounts.google.com/o/oauth2/auth?" +
$"scope={HttpUtility.UrlEncode("profile email")}" +
$"&response_type=code" +
$"&state={State}" +
$"&redirect_uri={RedirectUrl}" +
$"&client_id={_appId}";
return View();
}
|
-
參考OAuth Response結果,這邊大致上都是code、state、error,依照這結果設置使用者同意或不同意的導回結果
1
2
3
4
5
6
|
public async Task<IActionResult> SignInGoogle(string code, Guid state, string error, string error_description)
{
// 有錯誤訊息(未授權等)、State遺失、State不相同、沒有code
if (!string.IsNullOrEmpty(error) || state == null || State != state || string.IsNullOrEmpty(code))
return RedirectToAction(nameof(Index));
}
|
-
尋找 使用代碼交換存取權杖 的連結,一樣在 OAuth 驗證頁面,先根據回傳結果建立物件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
public class GoogleLoginResource
{
[JsonProperty("access_token")]
public string AccessToken { get; set; }
[JsonProperty("token_type")]
public string TokenType { get; set; }
[JsonProperty("expires_in")]
public string ExpiresIn { get; set; }
[JsonProperty("scope")]
public string Scope { get; set; }
[JsonProperty("refresh_token")]
public string RefreshToken { get; set; }
}
|
-
透過API取得回傳結果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
var url = "https://oauth2.googleapis.com/token";
var postData = new Dictionary<string, string>()
{
{"client_id",_appId},
{"client_secret",_appSecret},
{"code",code},
{"grant_type","authorization_code"},
{"redirect_uri",RedirectUrl}
};
string json = JsonConvert.SerializeObject(postData);
HttpContent contentPost = new StringContent(json, Encoding.UTF8, "application/json");
var client = _clientFactory.CreateClient();
var response = await client.PostAsync(url, contentPost);
string responseContent;
if (response.IsSuccessStatusCode)
responseContent = await response.Content.ReadAsStringAsync();
else
return RedirectToAction(nameof(Index));
var googleLoginResource = JsonConvert.DeserializeObject<GoogleLoginResource>(responseContent);
|
-
有AccessToken,接下來就是找,我們要使用的服務,這邊只打算取得簡單的ID、Name、Email,所以瀏覽一下服務後,決定選擇People API使用,這時你會發現一個絕望的事實…Simple中沒有.NET 選項,所以可以放棄了…,然後點開Read Profiles,又會看到.NET Code,就會感到又驚又喜又害怕,一臉茫然不知道這個怎麼出來的,所以又可以放棄了…
-
不!,這時候就是回到原點(你料理的原點是什麼..?),我們參考了Protocol,就會發現,他其實是WebAPI使用而已
-
所以我們根據這個API,組合出下面的結果
1
2
3
4
5
6
7
8
|
url = $"https://people.googleapis.com/v1/people/me?personFields=names,emailAddresses";
response = await client.GetAsync(url);
if (response.IsSuccessStatusCode)
{
responseContent = await response.Content.ReadAsStringAsync();
var user = JsonConvert.DeserializeObject<GoogleProfile>(responseContent);
}
|
-
一切就是計畫中,世界就是這麼美好,直接看執行結果…WTF
-
幸福來的太突然…,翻找了前後程式碼不覺得有不同啊,就在三度想放棄的這個時候,忽然想起,似乎沒有用到AccessToken,苦熬著破爛的英文,翻找了數頁後,在Authorize Requests中,發現一句話…
-
所以我們憑直覺、憑運氣、憑經驗,直接加上這段,改成…
1
2
|
url = $"https://people.googleapis.com/v1/people/me?personFields=names,emailAddresses" +
$"&access_token={googleLoginResource.AccessToken}";
|
-
結果
完整程式碼
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
|
public class LoginController : Controller
{
private readonly string _appId = "{應用程式編號}";
private readonly string _appSecret = "{應用程式密鑰}";
private readonly IHttpClientFactory _clientFactory;
public LoginController(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
}
private string RedirectUrl => "https://" + HttpContext.Request.Host.ToString() + "/Login/SignInGoogle";
[TempData]
public Guid State { get; set; }
public IActionResult Index()
{
State = Guid.NewGuid();
// scope 來自於要啟用的Google API,因為我們只是要取得最基本的資料,所以這邊直接設定 profile
// 如果是使用不同的Google API服務,這邊的Scope值也會不同
ViewData["GoogleAuth"] = $"https://accounts.google.com/o/oauth2/auth?" +
$"scope={HttpUtility.UrlEncode("profile email")}" +
$"&response_type=code" +
$"&state={State}" +
$"&redirect_uri={RedirectUrl}" +
$"&client_id={_appId}";
return View();
}
public async Task<IActionResult> SignInGoogle(string code, Guid state, string error, string error_description)
{
// 有錯誤訊息(未授權等)、State遺失、State不相同、沒有code
if (!string.IsNullOrEmpty(error) || state == null || State != state || string.IsNullOrEmpty(code))
return RedirectToAction(nameof(Index));
// 使用代碼交換存取權杖
var url = "https://oauth2.googleapis.com/token";
var postData = new Dictionary<string, string>()
{
{"client_id",_appId},
{"client_secret",_appSecret},
{"code",code},
{"grant_type","authorization_code"},
{"redirect_uri",RedirectUrl}
};
string json = JsonConvert.SerializeObject(postData);
HttpContent contentPost = new StringContent(json, Encoding.UTF8, "application/json");
var client = _clientFactory.CreateClient();
var response = await client.PostAsync(url, contentPost);
string responseContent;
if (response.IsSuccessStatusCode)
responseContent = await response.Content.ReadAsStringAsync();
else
return RedirectToAction(nameof(Index));
var googleLoginResource = JsonConvert.DeserializeObject<GoogleLoginResource>(responseContent);
// 接下來就是應用Google API,因為我們只打算取得最基礎的ID、Name、Email,所以我們採用Google People API
// 這部份會影響一開始scope設定值,可以說,其實要先知道使用哪個API服務,前面是OAuth流程
url = $"https://people.googleapis.com/v1/people/me?personFields=names,emailAddresses" +
$"&access_token={googleLoginResource.AccessToken}";
response = await client.GetAsync(url);
if (response.IsSuccessStatusCode)
{
responseContent = await response.Content.ReadAsStringAsync();
var user = JsonConvert.DeserializeObject<GoogleProfile>(responseContent);
}
return View();
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
public class GoogleLoginResource
{
[JsonProperty("access_token")]
public string AccessToken { get; set; }
[JsonProperty("token_type")]
public string TokenType { get; set; }
[JsonProperty("expires_in")]
public string ExpiresIn { get; set; }
[JsonProperty("scope")]
public string Scope { get; set; }
[JsonProperty("refresh_token")]
public string RefreshToken { get; set; }
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
|
public class GoogleProfile
{
[JsonProperty("resourceName")]
public string ResourceName { get; set; }
[JsonProperty("etag")]
public string Etag { get; set; }
[JsonProperty("names")]
public Name[] Names { get; set; }
[JsonProperty("emailAddresses")]
public Emailaddress[] EmailAddresses { get; set; }
public class Name
{
[JsonProperty("metadata")]
public Metadata Metadata { get; set; }
[JsonProperty("displayName")]
public string DisplayName { get; set; }
[JsonProperty("familyName")]
public string FamilyName { get; set; }
[JsonProperty("givenName")]
public string GivenName { get; set; }
[JsonProperty("displayNameLastFirst")]
public string DisplayNameLastFirst { get; set; }
[JsonProperty("unstructuredName")]
public string UnstructuredName { get; set; }
}
public class Metadata
{
[JsonProperty("primary")]
public bool Primary { get; set; }
[JsonProperty("source")]
public Source Source { get; set; }
}
public class Source
{
[JsonProperty("type")]
public string Type { get; set; }
[JsonProperty("id")]
public string Id { get; set; }
}
public class Emailaddress
{
[JsonProperty("metadata")]
public Metadata Metadata { get; set; }
[JsonProperty("value")]
public string Value { get; set; }
}
}
|
補充
上面沒有關於Google People API的Response物件,是因為我有點懶得找了,直接使用將ReadAsStringAsync
,取得的資料,使用Visusal Studio 內建的貼上JSON做為類別的功能(編輯 > 選擇性貼上 > 貼上JSON做為類別),在稍加修改而成
正規流程應該是,先看一開始設置的Score範圍,可以讀取那些資料,然後再設置personFields的時候以,
間格加上去,再到這邊去看組成格式,產生JSON To Object物件。
參考
Google Using OAuth 2.0 for Web Server Applications
https://developers.google.com/identity/protocols/oauth2/web-server
Google 官方 .NET OAuth 實作
https://developers.google.com/api-client-library/dotnet/guide/aaa_oauth
Google People API
https://developers.google.com/people
Google People API Authorize Requests說明
https://developers.google.com/people/v1/how-tos/authorizing
Google People API 資料Response格式
https://developers.google.com/people/api/rest/v1/people
結論
嗯…其實結果畫面一出來,我大概就知道結論要打什麼了,這就是我為什麼"不那麼喜歡"串Google API的原因,一切盡在不言中,反正結果是出來了,希望這篇能讓你少走一些路…