1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

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

        共 10961字,需瀏覽 22分鐘

         ·

        2021-01-16 19:28

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

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

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

        前言

        此次帶來(lái)的這個(gè)小項(xiàng)目是 OAuth2 登錄組件,看到 Java 平臺(tái) JustAuth 項(xiàng)目很方便的接入第三方平臺(tái)登錄,心里癢癢啊,搜了一大圈,發(fā)現(xiàn)我大 .netcore 能用的可說(shuō)是少之又少,而且代碼寫得一塌糊涂,全在一個(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"
        ????}
        ??}
        }

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

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

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

        把界面里的 Client ID,Client secret,連同上一個(gè)界面里填寫的 Authorization callback URL 全部填寫到配置文件對(duì)應(yīng)位置?,F(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 暫且不管他,你想深入了解它的作用的話,后面再說(shuō)。

        第三步:寫代碼

        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)代碼就好了。我們來(lái)運(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)這些步驟。下面講講原理之類的,隨便說(shuō)一些...如果覺得我啰嗦,那么就不用往下看了,因?yàn)橄旅嫖視?huì)更啰嗦。

        當(dāng)然,除了 github 現(xiàn)在已經(jīng)接入了12個(gè)平臺(tái),其中 QQ 和抖音我沒有注冊(cè)到應(yīng)用,無(wú)法測(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)登錄說(shuō)白了就是實(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ù)還是千差萬(wàn)別,無(wú)法做到完全的統(tǒng)一,那這就給我們開發(fā)者帶來(lái)一個(gè)困擾,開發(fā)第三方登錄時(shí)很困難,當(dāng)然,開發(fā)一兩個(gè)也無(wú)所謂,要是多了呢?

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

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

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

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

        1. GetAuthorizeUrl

        這一步通過(guò) client_id,redirect_uri 等幾個(gè)參數(shù)來(lái)獲取授權(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è)接口來(lái)規(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 來(lái)做演示:

        第一步:找平臺(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ù)說(shuō)性能提高了不少!

        第二步:寫對(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è)來(lái)說(shuō)一下:

        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)來(lái),因?yàn)閿U(kuò)展太容易了,但是吧,時(shí)間精力有限,再說(shuō)人上了年紀(jì),過(guò)了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微信交流群

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


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



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

        手機(jī)掃一掃分享

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

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            亚洲成人免费播放 | hitomi大乳boobs | 快穿系统女配啪啪任务h | 国产人成免费 | 男人天堂国产 | 黑人100部av解禁片 | 美女扒开双腿让男人桶 | 插逼免费观看 | 日本68xxxx | 成人视频亚洲 |