<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          OAuth2.0實(shí)戰(zhàn):ASP.NET Core接入第三方登錄

          共 10961字,需瀏覽 22分鐘

           ·

          2021-01-16 19:28

          新年新氣象,趁著新年的喜慶,肝了十來天,終于發(fā)了第一版,希望大家喜歡。

          如果有不喜歡看文字的童鞋,可以直接看下面的地址體驗(yàn)一下:

          • Github: https://github.com/mrhuo/MrHuo.OAuth
          • 唯一官網(wǎng):https://oauthlogin.net

          前言

          此次帶來的這個(gè)小項(xiàng)目是 OAuth2 登錄組件,看到 Java 平臺(tái) JustAuth 項(xiàng)目很方便的接入第三方平臺(tái)登錄,心里癢癢啊,搜了一大圈,發(fā)現(xiàn)我大 .netcore 能用的可說是少之又少,而且代碼寫得一塌糊涂,全在一個(gè)庫(kù)里,代碼風(fēng)格也看不慣,所以下定決定,操起鍵盤,開干。

          關(guān)于 OAuth2 的一些基礎(chǔ)、原理介紹文章太多了,寫的好的不在少數(shù),在頁(yè)尾我提供了幾個(gè)鏈接,喜歡的朋友看一下,這里就不深入解釋,直入主題。

          如何使用

          這里拿接入 github 登錄做演示,新建 Asp.NET Core Web應(yīng)用程序 項(xiàng)目,名叫 GithubLogin(PS:你可以自己起個(gè)和更牛×的名字),選擇模型視圖控制器這個(gè),當(dāng)然你可以選擇其他的。

          第一步:安裝

          安裝這個(gè) nuget 包:

          Install-Package?MrHuo.OAuth.Github?-Version?1.0.0

          第二步:配置

          打開 appsettings.json 寫入下面的配置:

          {
          ??"oauth":?{
          ????"github":?{
          ??????"app_id":?"github_app_id",
          ??????"app_key":?"github_app_key",
          ??????"redirect_uri":?"https://oauthlogin.net/oauth/githubcallback",
          ??????"scope":?"repo"
          ????}
          ??}
          }

          這里的配置可以通過 https://github.com/settings/applications/new 來注冊(cè),redirect_uri 可以填寫本地 localhost 地址的,超級(jí)方便,這也是為什么使用 github 登錄做演示的原因。

          創(chuàng)建完成后,在這個(gè)界面里生成 client secret

          輸入密碼,生成成功后是這樣的:

          把界面里的 Client IDClient secret,連同上一個(gè)界面里填寫的 Authorization callback URL 全部填寫到配置文件對(duì)應(yīng)位置。現(xiàn)在配置文件 appsettings.json 是這樣的:

          {
          ??"Logging":?{
          ????"LogLevel":?{
          ??????"Default":?"Information",
          ??????"Microsoft":?"Warning",
          ??????"Microsoft.Hosting.Lifetime":?"Information"
          ????}
          ??},
          ??"AllowedHosts":?"*",
          ??"oauth":?{
          ????"github":?{
          ??????"app_id":?"c95fxxxxxx0d09",
          ??????"app_key":?"c6a73xxxxxx6375",
          ??????"redirect_uri":?"http://localhost:5000/oauth/githubcallback",
          ??????"scope":?"repo"
          ????}
          ??}
          }

          下面的 scope 暫且不管他,你想深入了解它的作用的話,后面再說。

          第三步:寫代碼

          Startup.cs 文件中注入組件:

          //?This?method?gets?called?by?the?runtime.?Use?this?method?to?add?services?to?the?container.
          public?void?ConfigureServices(IServiceCollection?services)
          {
          ????services.AddControllersWithViews();
          ????services.AddSingleton(new?GithubOAuth(OAuthConfig.LoadFrom(Configuration,?"oauth:github")));
          }

          文件中其他代碼沒有修改,只加了這一行而已。

          新建一個(gè) OAuthController 類,代碼如下:

          using?System.Threading.Tasks;
          using?Microsoft.AspNetCore.Mvc;
          using?MrHuo.OAuth.Github;
          namespace?GithubLogin.Controllers
          {
          ????public?class?OAuthController:?Controller
          ????{
          ????????[HttpGet("oauth/github")]
          ????????public?IActionResult?Github([FromServices]?GithubOAuth?githubOAuth)
          ????????{
          ????????????return?Redirect(githubOAuth.GetAuthorizeUrl());
          ????????}
          ????????[HttpGet("oauth/githubcallback")]
          ????????public?async?Task?GithubCallback(
          ????????????[FromServices]?GithubOAuth?githubOAuth,
          ????????????[FromQuery]?string?code
          )

          ????????{
          ????????????return?Json(await?githubOAuth.AuthorizeCallback(code));
          ????????}
          ????}
          }

          你沒看錯(cuò),就這點(diǎn)代碼就好了。我們來運(yùn)行一下試試:

          項(xiàng)目運(yùn)行之后,在地址欄里輸入下面這個(gè)地址:http://localhost:5000/oauth/github,因?yàn)槲覀儧]有修改任何代碼,沒有在視圖上做任何鏈接,所以就勞煩手動(dòng)啦~~

          回車之后,順利跳轉(zhuǎn)到 github 授權(quán):

          點(diǎn)擊綠色的 Authorize 按鈕之后稍等片刻,你會(huì)看到下面這個(gè)結(jié)果:

          順利拿到了用戶信息(PS:請(qǐng)忽略我少的可憐的粉絲,曾經(jīng)我不強(qiáng)求 --ToT)

          好了,到這里我的表演結(jié)束了,可以看到接入流程非常流暢,卡人主要是在申請(qǐng)這些步驟。下面講講原理之類的,隨便說一些...如果覺得我啰嗦,那么就不用往下看了,因?yàn)橄旅嫖視?huì)更啰嗦。

          當(dāng)然,除了 github 現(xiàn)在已經(jīng)接入了12個(gè)平臺(tái),其中 QQ 和抖音我沒有注冊(cè)到應(yīng)用,無法測(cè)試,所以暫時(shí)沒有 nuget 包,一個(gè)人的力量總是有限的,在這里我請(qǐng)求各位有閑時(shí)間或者有 appid 資源的大佬,為這個(gè)小項(xiàng)目做一些貢獻(xiàn),是她走的遠(yuǎn)一些。

          更多的 nuget 包,進(jìn)這里 https://www.nuget.org/profiles/mrhuo 或者在 VS nuget 包管理器里搜索 MrHuo.OAuth,就可以了。

          請(qǐng)忽略 nuget 上其他幾個(gè) 垃圾 包,那是很多年很多年以前寫的,舍不得刪。

          開發(fā)背景

          第三方平臺(tái)登錄說白了就是實(shí)現(xiàn) OAuth2 協(xié)議,很多平臺(tái)比如支付寶、百度、github、微軟,甚至是抖音、快手很多平臺(tái)都提供了開放接口。但是,很多平臺(tái)會(huì)在這個(gè)標(biāo)準(zhǔn)協(xié)議的基礎(chǔ)上增加、修改一些東西,比如:標(biāo)準(zhǔn)協(xié)議里,獲取 authorize code 時(shí)應(yīng)提供 client_id,微信公眾平臺(tái)非要把它改成 appid。再比如:獲取用戶信息時(shí),只需要 access_token 參數(shù),微信公眾平臺(tái)這邊非要提供一個(gè) openid,當(dāng)然這是在所難免的,因?yàn)楦鱾€(gè)平臺(tái)實(shí)際業(yè)務(wù)還是千差萬別,無法做到完全的統(tǒng)一,那這就給我們開發(fā)者帶來一個(gè)困擾,開發(fā)第三方登錄時(shí)很困難,當(dāng)然,開發(fā)一兩個(gè)也無所謂,要是多了呢?

          假如有這么一個(gè)產(chǎn)品經(jīng)理,他想接入很多的登錄方式,讓使用者無論使用哪種平臺(tái),都能在這里順利登錄,找到回家的路呢(PS:產(chǎn)品經(jīng)理你別跑,看我40米的大刀)。

          無疑,給我們一個(gè)考驗(yàn),如何做到一個(gè)標(biāo)準(zhǔn)化,可配置,可擴(kuò)展呢?這就是一個(gè)需要深究的問題。下面我就說說我肝這個(gè)項(xiàng)目的一些想法,說的不好別噴我,我還年輕(PS:三十多歲老大叔別裝嫩),還要臉......

          制定標(biāo)準(zhǔn)

          看了很多文檔之后,我們會(huì)發(fā)現(xiàn),萬變不離其宗,總有規(guī)律可循,總的來說,有下面3個(gè)步驟:

          1. GetAuthorizeUrl

          這一步通過 client_idredirect_uri 等幾個(gè)參數(shù)來獲取授權(quán) url,跳轉(zhuǎn)到這個(gè) url 之后將在第三方平臺(tái)上完成登錄,完成登錄之后會(huì)跳轉(zhuǎn)到上面提供的 redirect_uri 這個(gè)地址,并且?guī)弦粋€(gè) code 參數(shù)。

          1. GetAccessToken

          這一步里,拿到上面的 code 之后去第三方平臺(tái)換 access_token

          1. GetUserInfo

          這一步并非必須,但是我們既然是做第三方登錄,登錄之后還是需要和自己平臺(tái)的一些業(yè)務(wù)綁定用戶賬號(hào),或者使用現(xiàn)有信息注冊(cè)一個(gè)用戶,這個(gè)方法就顯得尤為重要了。

          到此,就這3個(gè)步驟,我覺得是需要制定在標(biāo)準(zhǔn)里面的,所以我就寫了下面這個(gè)接口來規(guī)范它:

          ///?
          ///?OAuth?登錄?API?接口規(guī)范
          ///?

          public?interface?IOAuthLoginApi<TAccessTokenModel,?TUserInfoModel>
          ????where?TAccessTokenModel?:?IAccessTokenModel
          ????where?TUserInfoModel?:?IUserInfoModel
          {
          ????///?
          ????///?獲取跳轉(zhuǎn)授權(quán)的?URL
          ????///?

          ????///?
          ????///?
          ????string?GetAuthorizeUrl(string?state?=?"");

          ????///?
          ????///?異步獲取?AccessToken
          ????///?

          ????///?
          ????///?
          ????///?
          ????Task?GetAccessTokenAsync(string?code,?string?state?=?"");

          ????///?
          ????///?異步獲取用戶詳細(xì)信息
          ????///?

          ????///?
          ????///?
          ????Task?GetUserInfoAsync(TAccessTokenModel?accessTokenModel);
          }

          可以看到我將 AccessTokenUserInfo 做成了泛型參數(shù),因?yàn)樗麄兪沁@個(gè)規(guī)范里的可變部分。代碼中 state 參數(shù)的作用呢就是為了防止 CORS 攻擊做的防偽驗(yàn)證,這里暫不做解釋,其他文檔里都有這個(gè)參數(shù)的解釋。

          如何擴(kuò)展新的平臺(tái)

          這里拿 Gitee 來做演示:

          第一步:找平臺(tái)對(duì)應(yīng) OAuth 文檔,找到獲取用戶信息接口返回JSON,轉(zhuǎn)換為 C# 實(shí)體類。如下:

          根據(jù)自己需要和接口標(biāo)準(zhǔn),擴(kuò)展用戶屬性

          public?class?GiteeUserModel?:?IUserInfoModel
          {
          ????[JsonPropertyName("name")]
          ????public?string?Name?{?get;?set;?}

          ????[JsonPropertyName("avatar_url")]
          ????public?string?Avatar?{?get;?set;?}

          ????[JsonPropertyName("message")]
          ????public?string?ErrorMessage?{?get;?set;?}

          ????[JsonPropertyName("email")]
          ????public?string?Email?{?get;?set;?}

          ????[JsonPropertyName("blog")]
          ????public?string?Blog?{?get;?set;?}

          ????//...其他屬性類似如上
          }

          這里使用了 .netcore 內(nèi)置的 Json 序列化庫(kù),據(jù)說性能提高了不少!

          第二步:寫對(duì)應(yīng)平臺(tái)的授權(quán)接口

          ///?
          ///?https://gitee.com/api/v5/oauth_doc#/
          ///?

          public?class?GiteeOAuth?:?OAuthLoginBase<GiteeUserModel>
          {
          ????public?GiteeOAuth(OAuthConfig?oauthConfig)?:?base(oauthConfig)?{?}
          ????protected?override?string?AuthorizeUrl?=>?"https://gitee.com/oauth/authorize";
          ????protected?override?string?AccessTokenUrl?=>?"https://gitee.com/oauth/token";
          ????protected?override?string?UserInfoUrl?=>?"https://gitee.com/api/v5/user";
          }

          加上注釋,總共十行,如你所見,非常方便。如果該平臺(tái)協(xié)議遵循 OAuth2 標(biāo)準(zhǔn)開發(fā),那么就這么幾行就好了。


          當(dāng)然,如果不按規(guī)矩自定義字段的平臺(tái),也可以擴(kuò)展,比如微信公眾平臺(tái)。

          WechatAccessTokenModel.cs AccessToken 類擴(kuò)展

          namespace?MrHuo.OAuth.Wechat
          {
          ????public?class?WechatAccessTokenModel?:?DefaultAccessTokenModel
          ????{
          ????????[JsonPropertyName("openid")]
          ????????public?string?OpenId?{?get;?set;?}
          ????}
          }

          繼承自 DefaultAccessTokenModel,新增字段 OpenId,因?yàn)楂@取用戶信息需要獲取 OpenId,所以這里需要它。

          WechatUserInfoModel.cs 用戶信息類

          using?System.Collections.Generic;
          using?System.Text.Json.Serialization;

          namespace?MrHuo.OAuth.Wechat
          {
          ????public?class?WechatUserInfoModel?:?IUserInfoModel
          ????{
          ????????[JsonPropertyName("nickname")]
          ????????public?string?Name?{?get;?set;?}

          ????????[JsonPropertyName("headimgurl")]
          ????????public?string?Avatar?{?get;?set;?}

          ????????[JsonPropertyName("language")]
          ????????public?string?Language?{?get;?set;?}

          ????????[JsonPropertyName("openid")]
          ????????public?string?Openid?{?get;?set;?}

          ????????[JsonPropertyName("sex")]
          ????????public?int?Sex?{?get;?set;?}

          ????????[JsonPropertyName("province")]
          ????????public?string?Province?{?get;?set;?}

          ????????[JsonPropertyName("city")]
          ????????public?string?City?{?get;?set;?}

          ????????[JsonPropertyName("country")]
          ????????public?string?Country?{?get;?set;?}

          ????????///?
          ????????///?用戶特權(quán)信息,json?數(shù)組,如微信沃卡用戶為(chinaunicom)
          ????????///?

          ????????[JsonPropertyName("privilege")]
          ????????public?List<string>?Privilege?{?get;?set;?}

          ????????[JsonPropertyName("unionid")]
          ????????public?string?UnionId?{?get;?set;?}

          ????????[JsonPropertyName("errmsg")]
          ????????public?string?ErrorMessage?{?get;?set;?}
          ????}
          }

          這里用戶信息字段上邊的 [JsonPropertyName("xxxx")] 完全按照文檔里的字段寫,否則獲取不到正確的值。如果不需要太多的字段,自行刪減。

          WechatOAuth.cs 核心類

          using?System.Collections.Generic;
          namespace?MrHuo.OAuth.Wechat
          {
          ????///?
          ????///?Wechat OAuth 相關(guān)文檔參考:
          ????///?https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html
          ????///?

          ????public?class?WechatOAuth?:?OAuthLoginBase<WechatAccessTokenModel,?WechatUserInfoModel>
          ????{
          ????????public?WechatOAuth(OAuthConfig?oauthConfig)?:?base(oauthConfig)?{?}
          ????????protected?override?string?AuthorizeUrl?=>?"https://open.weixin.qq.com/connect/oauth2/authorize";
          ????????protected?override?string?AccessTokenUrl?=>?"https://api.weixin.qq.com/sns/oauth2/access_token";
          ????????protected?override?string?UserInfoUrl?=>?"https://api.weixin.qq.com/sns/userinfo";
          ????????protected?override?Dictionary<string,?string>?BuildAuthorizeParams(string?state)
          ????????{
          ????????????return?new?Dictionary<string,?string>()
          ????????????{
          ????????????????["response_type"]?=?"code",
          ????????????????["appid"]?=?oauthConfig.AppId,
          ????????????????["redirect_uri"]?=?System.Web.HttpUtility.UrlEncode(oauthConfig.RedirectUri),
          ????????????????["scope"]?=?oauthConfig.Scope,
          ????????????????["state"]?=?state
          ????????????};
          ????????}
          ????????public?override?string?GetAuthorizeUrl(string?state?=?"")
          ????????{
          ????????????return?$"{base.GetAuthorizeUrl(state)}#wechat_redirect";
          ????????}
          ????????protected?override?Dictionary<string,?string>?BuildGetAccessTokenParams(Dictionary<string,?string>?authorizeCallbackParams)
          ????????{
          ????????????return?new?Dictionary<string,?string>()
          ????????????{
          ????????????????["grant_type"]?=?"authorization_code",
          ????????????????["appid"]?=?$"{oauthConfig.AppId}",
          ????????????????["secret"]?=?$"{oauthConfig.AppKey}",
          ????????????????["code"]?=?$"{authorizeCallbackParams["code"]}"
          ????????????};
          ????????}
          ????????protected?override?Dictionary<string,?string>?BuildGetUserInfoParams(WechatAccessTokenModel?accessTokenModel)
          ????????{
          ????????????return?new?Dictionary<string,?string>()
          ????????????{
          ????????????????["access_token"]?=?accessTokenModel.AccessToken,
          ????????????????["openid"]?=?accessTokenModel.OpenId,
          ????????????????["lang"]?=?"zh_CN",
          ????????????};
          ????????}
          ????}
          }

          乍一看好多內(nèi)容,懵了?先別懵,我一個(gè)一個(gè)來說一下:

          protected?override?Dictionary<string,?string>?BuildAuthorizeParams(string?state)
          {
          ????return?new?Dictionary<string,?string>()
          ????{
          ????????["response_type"]?=?"code",
          ????????["appid"]?=?oauthConfig.AppId,
          ????????["redirect_uri"]?=?System.Web.HttpUtility.UrlEncode(oauthConfig.RedirectUri),
          ????????["scope"]?=?oauthConfig.Scope,
          ????????["state"]?=?state
          ????};
          }

          細(xì)心的讀者發(fā)現(xiàn)了,這一段就是為了構(gòu)造 Authorize Url 時(shí)后邊的參數(shù)列表,返回一個(gè) Dictionary 即可,以為微信公眾號(hào)把 client_id 字段修改為 appid,所以這里需要處理一下。

          public?override?string?GetAuthorizeUrl(string?state?=?"")
          {
          ????return?$"{base.GetAuthorizeUrl(state)}#wechat_redirect";
          }

          這一段,在 Authorize Url 后邊綴了個(gè) #wechat_redirect,雖然不知道微信在這個(gè)參數(shù)上做了什么文章(PS:知道的朋友,言傳一下~~),但是他文檔里寫就給他寫上吧。

          protected?override?Dictionary<string,?string>?BuildGetAccessTokenParams(Dictionary<string,?string>?authorizeCallbackParams)
          {
          ????return?new?Dictionary<string,?string>()
          ????{
          ????????["grant_type"]?=?"authorization_code",
          ????????["appid"]?=?$"{oauthConfig.AppId}",
          ????????["secret"]?=?$"{oauthConfig.AppKey}",
          ????????["code"]?=?$"{authorizeCallbackParams["code"]}"
          ????};
          }

          同理,這一段是為了構(gòu)造 GetAccessToken 接口參數(shù)。

          protected?override?Dictionary<string,?string>?BuildGetUserInfoParams(WechatAccessTokenModel?accessTokenModel)
          {
          ????return?new?Dictionary<string,?string>()
          ????{
          ????????["access_token"]?=?accessTokenModel.AccessToken,
          ????????["openid"]?=?accessTokenModel.OpenId,
          ????????["lang"]?=?"zh_CN",
          ????};
          }

          同理,這一段是為了構(gòu)造 GetUserInfo 接口參數(shù)。

          可以看到哈,這個(gè)框架本著自由、開放的原則,任何能自定義的地方,都可以自定義。還有我原本的出發(fā)點(diǎn),并非只針對(duì) OAuth 登錄這一個(gè)方向,我想把他平臺(tái)里面提供的 API 全部接入進(jìn)來,因?yàn)閿U(kuò)展太容易了,但是吧,時(shí)間精力有限,再說人上了年紀(jì),過了30歲,腦袋就不怎么靈光了,所以機(jī)會(huì)留給年輕人。

          加入貢獻(xiàn)

          期待更多的朋友能加入到這個(gè)項(xiàng)目中,貢獻(xiàn)代碼也好,貢獻(xiàn) appid 資源做測(cè)試也好,提供意見建議也好。如果你也感興趣,請(qǐng)聯(lián)系我。

          如果覺得有用幫到你了,貢獻(xiàn)一顆幼兒園之星 ?,點(diǎn)個(gè)關(guān)注,fork 走一波~~(PS: 手動(dòng)調(diào)皮)

          相關(guān)文檔:

          • OAuth2:https://oauth.net/2/
          • rfc6749:https://tools.ietf.org/html/rfc6749
          • ruanyifeng:http://www.ruanyifeng.com/blog/2019/04/github-oauth.html







          回復(fù)?【關(guān)閉】學(xué)關(guān)
          回復(fù)?【實(shí)戰(zhàn)】獲取20套實(shí)戰(zhàn)源碼
          回復(fù)?【被刪】學(xué)個(gè)
          回復(fù)?【訪客】學(xué)
          回復(fù)?【小程序】學(xué)獲取15套【入門+實(shí)戰(zhàn)+賺錢】小程序源碼
          回復(fù)?【python】學(xué)微獲取全套0基礎(chǔ)Python知識(shí)手冊(cè)
          回復(fù)?【2019】獲取2019 .NET 開發(fā)者峰會(huì)資料PPT
          回復(fù)?【加群】加入dotnet微信交流群

          臥槽又來一個(gè)神器,可以查看微信朋友圈訪客記錄!


          副業(yè)剛需,個(gè)人開發(fā)者如何通過小程序變現(xiàn)?已經(jīng)有朋友變現(xiàn)月入4k了!



          瀏覽 158
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产精品成人无码久久久 | 欧美在线 | 亚洲 | 人人插人人靠 | 日韩亚洲美洲欧洲综三区一品在线 | 婷婷丁香五月久久 |