這個文章先說一說Oauth2.0的原理,再到應用場景,最後才是代碼實現,這樣才學會最終的思想,並在應用場景使用,所謂實踐出真理。
1,Oauth2.0的原理
OAuth是一個關於授權(authorization)的開放網絡標准,在全世界得到廣泛應用,目前的版本是2.0版。在互聯網,經常用到OAuth2.0無非有三種場景:
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),即頒發用戶Appid,AppSecret,URL地址合法性,來進行統一調度與驗證。,
授權碼模式(authorization code)是功能最完整、流程最嚴密的授權模式。它的特點就是通過客戶端的後台服務器,與"服務提供商"的認證服務器進行互動,所以一般開放平台首先這一類授權碼模式,新浪開放平台,公眾號API,淘寶開放平台都采用這一類,一般搭配HTTPS來提高安全機制。
微信公眾號API就是采用這一種模式,我們來梳理一下授權碼模式的流程:
它的步驟如下:
(A)用戶訪問客戶端,後者將前者導向認證服務器。
(B)用戶選擇是否給予客戶端授權。
(C)假設用戶給予授權,認證服務器將用戶導向客戶端事先指定的"重定向URI"(redirection URI),同時附上一個授權碼。
(D)客戶端收到授權碼,附上早先的"重定向URI",向認證服務器申請令牌。這一步是在客戶端的後台的服務器上完成的,對用戶不可見。
(E)認證服務器核對了授權碼和重定向URI,確認無誤後,向客戶端發送訪問令牌(access token)和更新令牌(refresh token)。
下面是上面這些步驟所需要的參數。
A步驟中,客戶端申請認證的URI,包含以下參數:
再來看一看微信公眾平台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即可以。