NET仿微信Oauth2.0

這個文章先說一說Oauth2.0的原理,再到應用場景,最後纔是代碼實現,這樣才學會最終的思想,並在應用場景使用,所謂實踐出真理。web

 

1,Oauth2.0的原理數據庫

OAuth是一個關於受權(authorization)的開放網絡標準,在全世界獲得普遍應用,目前的版本是2.0版。在互聯網,常常用到OAuth2.0無非有三種場景:api

1.1對外徹底開放,系統與系統的對接,例如淘寶開放平臺。緩存

1.2內部系統對內部系統,如:(api.xxxx.com是一個子系統,web.xxxx.com是另一個業務線的子系統,交給不一樣的團隊處理,有NET,有JAVA)安全

1.3內部系統對內部客戶端系統。如(API.xxxx.com是對外開放的接口系統,IOS,安卓,C/S客戶端,相關對接).服務器

 

綜合三種應用場景,咱們來分解一下Oauth2.0怎樣在實踐中使用。微信

在Oauth2.0的使用場景中,最多見到的就是對外徹底開放,像淘寶開放平臺,新浪,騰訊QQ,微信,大量使用Oauth2.0,相反,後兩種因爲都是內部使用,因此通常都存在內部,讓我覺得Oauth2.0受權模式只有第一種,其實不是,Oauth2.0分別有四種受權模式,分別爲:網絡

 

  • 受權碼模式(authorization code)
  • 簡化模式(implicit)
  • 密碼模式(resource owner password credentials)
  • 客戶端模式(client credentials)

 

而微信公衆號採用的是受權碼模式(authorization code),即頒發用戶Appid,AppSecret,URL地址合法性,來進行統一調度與驗證。,app

 

 

 

 

1.一、受權碼模式

受權碼模式(authorization code)是功能最完整、流程最嚴密的受權模式。它的特色就是經過客戶端的後臺服務器,與"服務提供商"的認證服務器進行互動,因此通常開放平臺首先這一類受權碼模式,新浪開放平臺,公衆號API,淘寶開放平臺都採用這一類,通常搭配HTTPS來提升安全機制。微信公衆平臺

微信公衆號API就是採用這一種模式,咱們來梳理一下受權碼模式的流程:

它的步驟以下:

(A)用戶訪問客戶端,後者將前者導向認證服務器。

(B)用戶選擇是否給予客戶端受權。

(C)假設用戶給予受權,認證服務器將用戶導向客戶端事先指定的"重定向URI"(redirection URI),同時附上一個受權碼。

(D)客戶端收到受權碼,附上早先的"重定向URI",向認證服務器申請令牌。這一步是在客戶端的後臺的服務器上完成的,對用戶不可見。

(E)認證服務器覈對了受權碼和重定向URI,確認無誤後,向客戶端發送訪問令牌(access token)和更新令牌(refresh token)。

下面是上面這些步驟所須要的參數。

A步驟中,客戶端申請認證的URI,包含如下參數:

  • response_type:表示受權類型,必選項,此處的值固定爲"code"
  • client_id:表示客戶端的ID,必選項
  • redirect_uri:表示重定向URI,可選項
  • scope:表示申請的權限範圍,可選項
  • state:表示客戶端的當前狀態,能夠指定任意值,認證服務器會原封不動地返回這個值。

再來看一看微信公衆平臺OAuth2.0受權詳細步驟以下:

1. 用戶關注微信公衆帳號。
2. 微信公衆帳號提供用戶請求受權頁面URL。
3. 用戶點擊受權頁面URL,將向服務器發起請求
4. 服務器詢問用戶是否贊成受權給微信公衆帳號(scope爲snsapi_base時無此步驟)
5. 用戶贊成(scope爲snsapi_base時無此步驟)
6. 服務器將CODE經過回調傳給微信公衆帳號
7. 微信公衆帳號得到CODE
8. 微信公衆帳號經過CODE向服務器請求Access Token
9. 服務器返回Access Token和OpenID給微信公衆帳號
10. 微信公衆帳號經過Access Token向服務器請求用戶信息(scope爲snsapi_base時無此步驟)
11. 服務器將用戶信息回送給微信公衆帳號(scope爲snsapi_base時無此步驟)

 

 

是否是一目瞭然,那麼根椐這個需求,整理一下流程爲:

1,先去開放平臺(api地址)申請一個CODE,(那麼咱們須要判斷appid,請求過來的URL合法性,生成CODE,並附於某個時間有效,5分鐘失效,仿微信,而後作持久存儲)。

2,再根據CODE,APPID,AppSecret(平臺頒發的應用ID及密鎖),生成有效期的Access Token。(判斷APPID,AppSecret合法性,而後根椐Appid獲取持久存儲層的Access Token,並把CODE取消合法,而後返回Access Token過時時間)

3,判斷Access Token的合法化,完成。

流程梳理完了,咱們就開始實現代碼的編寫:

 /// <summary>
        /// 對接商家,開放平臺的接口
        /// </summary>
        /// <param name="AppId">應用ID</param>
        /// <param name="RedirectUri">受權回調地址</param>
        /// <param name="response_type"></param>
        /// <param name="scope"></param>
        /// <param name="state"></param>
        /// <returns></returns>
        [HttpGet]
        public dynamic authorize(string AppId, string RedirectUri, string response_type, string scope, string state)
        {
            //獲取APPID應用表的字段
            var model = _ISellerApplication.Get(AppId);
          //判斷APPID合法性及RedirectUri地址的合法
            if (model.AppId.Trim().ToLower() == model.AppId.ToLower() && RedirectUri.ToLower().Contains(model.ApplicationUrl.ToLower()))
            {
             

               //請求的地址合法性,若是不合法,直接返回
                if (!Request.RequestUri.AbsoluteUri.Contains(model.ApplicationUrl.ToLower()))
                {
                                    return Redirect($"{RedirectUri}?state={state}");
                }
                var appcodemodel = new AppCodeModel()
                {
                    AccessToken = string.Concat(Guid.NewGuid().ToString("N"), Guid.NewGuid().ToString("N")).ToLower().CutString(40),
                    AppCode = string.Concat(Guid.NewGuid().ToString("N"), Guid.NewGuid().ToString("N")).ToLower().CutString(40),
                    ApplicationUrl = RedirectUri,
                    CreateDate = DateTime.Now,
                   AppId=model.AppId

                };

                //若是插入成功,返回CODE給他們
                if (_IAppCode.Insert(appcodemodel))
                {
                    if (RedirectUri.Equals("?"))
                    { return Redirect($"{RedirectUri}&state={state}&code={appcodemodel.AppCode}");
                    }
                    else
                    { return Redirect($"{RedirectUri}?state={state}&code={appcodemodel.AppCode}");}
                }
            }
            //插入不成功,直接返回state
            return Redirect($"{RedirectUri}?state={state}");
        }

 

再實現第二步調用:

  /// <summary>
        ///經過網頁受權獲取access_token
        /// </summary>
        /// <param name="AppId"></param>
        /// <param name="AppSecret"></param>
        /// <param name="code"></param>
        /// <param name="grant_type"></param>
        /// <returns></returns>
        [HttpGet]
        public dynamic AccessToken(string AppId, string AppSecret, string code, string grant_type)
        {

//從持久層獲取APPID模型
var model = _IAppSoft.Get(AppId); //判斷Appid if (model == null || model.AppId.Trim() != AppId.Trim()) { return new ApiArgumentException(ApiArgumentExceptionEum.APPID出錯.ToString(), (int)ApiArgumentExceptionEum.APPID出錯); } //判斷AppSecret if (model.AppSecret.Trim() != AppSecret.Trim()) { return new ApiArgumentException(ApiArgumentExceptionEum.AppSecret錯誤.ToString(), (int)ApiArgumentExceptionEum.AppSecret錯誤); } //獲取CODE並判斷,CODE是否在過時範圍內,5表示5分鐘內有效 var AppCodeModel = _IAppCode.Get(5)); if (AppCodeModel == null) { return new ApiArgumentException(ApiArgumentExceptionEum.不合法的oauth_code.ToString(), (int)ApiArgumentExceptionEum.不合法的oauth_code); } var apptokenmodel = new AppTokenModel() { AccessToken = AppCodeModel.AccessToken, AppId = AppId, ClientId = model.ClientId, CreateDate = DateTime.Now, ExpireDate = DateTime.Now.AddHours(2), Status = 0 };
//把AccessToken插入數據庫,或者存儲緩存
bool bl = _IAppToken.Insert(apptokenmodel);
//返回 AccessToken 及過時時間
return new { AccessToken = AppCodeModel.AccessToken, ExpireDate = 7200 }; }

 

如今仿微信都完成了受權的核心代碼,第二步就可開始寫AOP,而後統一驗證AccessToken,因爲用到的是WEB API2.0,因此咱們統一寫一個AOP類,而且繼承  AuthorizationFilterAttribute, IActionFilter類,便可以寫一個簡單的AOP

代碼以下:

namespace Saas.AOP
{

    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)]

    

    public class AppOauthAuthentication : AuthorizationFilterAttribute, IActionFilter
    {
      
        protected AppTokenModel apptoken;

        public AppOauthAuthentication() { }

        public override void OnAuthorization(HttpActionContext actionContext)
        {
            var query = actionContext.Request.GetParamsFromUrl();

            var tokenString = query.Get("accessToken");


            if (string.IsNullOrWhiteSpace(tokenString) )
            {
                actionContext.Response = CreateResponse(actionContext, 101, "請檢查oauth認證參數");
                return;
            }
            apptoken = new AppTokenBusiness().GetByAccessToken(tokenString);


            if (apptoken == null)
            {
                actionContext.Response = CreateResponse(actionContext, 102, "未找到指定的accessToken");
                return;
            }


            if (apptoken.ExpireDate < DateTime.Now)
            {
                actionContext.Response = CreateResponse(actionContext, 104, "accessToken已過時");
                return;
            }

            if (apptoken.Status == 1)
            {
                actionContext.Response = CreateResponse(actionContext, 105, "APPID已更正,當前accessToken已失效");
                return;
            }
           Thread.CurrentPrincipal = new GenericPrincipal(new GenericIdentity(apptoken.AppId), null);
            base.OnAuthorization(actionContext);
        }

}}

調用以下:

   /// <summary>
        /// 例子
        /// </summary>
    
        /// <returns></returns>
        [HttpPost, AppOauthAuthentication]
        public int test()
        {   
return 1;
}

而簡單的一個仿微信的Oauth2.0,就完成了。

而關於Oauth2.0對於內部系統調用,以及APP怎樣搭建一個通用的Oauth2.0,在明天再寫,結合三種場景,搭建整一套的NET Oauth2.0系統,並完成跨平臺。

因爲公司項目,是整一套的,暫時沒整理實現開放,有須要交流的能夠加我微信,BON184195873,掃描如下二維碼,備註:CNBLOGS便可以。

相關文章
相關標籤/搜索