微軟在 OWIN 框架中對 OAuth 認證的支持很是好, 使用現有的 OWIN 中間件能夠作到:html
微軟提供了這麼多的 OAuth 認證中間件, 對天朝的牆內用戶來講, 只能用三個字來歸納「然並卵」。git
要接入國內 騰訊微信 、新浪微博提供的 OAuth2 認證, 仍是要根據現有的中間件 Microsoft.Owin.Security 進行二次開發, 上面微軟提供的 Facebook、 Google 等實現能夠做爲參考。github
先來簡單回顧一下 OAuth2 的 認證流程 , 以下圖所示:json
直接和 OAuth2 認證服務器交互的步驟有:瀏覽器
Microsoft.Owin.Security
對這些步驟提供了優秀的擴展支持, 擴展步驟以下:服務器
一、 建立自定義的 OAuth2AuthenticationOptions
,並繼承自 Microsoft.Owin.Security.AuthenticationOptions
, 代碼以下:微信
public class OAuth2AuthenticationOptions : AuthenticationOptions { // Client App Identifier public string AppId { get; set; } // Client App Secret public string AppSecret { get; set; } // The authorize url public string AuthorizationEndpoint { get; set; } // Token url public string TokenEndpoint { get; set; } // User info url public string UserInformationEndpoint { get; set; } }
二、 建立一個自定義的 Owin 中間件 OAuth2AuthenticationMiddleware
, 並繼承自 Microsoft.Owin.Security.AuthenticationMiddleware
:app
public class GdepAuthenticationMiddleware : AuthenticationMiddleware<GdepAuthenticationOptions> { protected override AuthenticationHandler<OAuth2AuthenticationOptions> CreateHandler() { return new OAuth2AuthenticationHandler(httpClient, logger); } }
重寫的基類的 CreateHandler
很是重要, 整個 OAuth2 認證的過程都會在這個方法建立的 AuthenticationHandler
實例中完成。框架
三、 接下來就是最重要的部分, OAuth2AuthenticationHandler
的實現了, 先來看一下基類 AuthenticationHandler
, 實現它須要實現下面的幾個方法:ide
public abstract class AuthenticationHandler { protected abstract Task<AuthenticationTicket> AuthenticateCoreAsync (); protected virtual Task ApplyResponseChallengeAsync () { } public virtual Task<bool> InvokeAsync () { } }
接下來分別說明一下這幾個方法的做用:
在 ApplyResponseChallengeAsync
方法中響應 HTTP 401 Unauthorized , 將用戶重定向到認證服務器, 即實現上面的步驟 (A) , 示例代碼以下:
var authorizationEndpoint = Options.AuthorizationEndpoint + "?response_type=code" + "&client_id=" + Uri.EscapeDataString(Options.AppId) + "&redirect_uri=" + Uri.EscapeDataString(redirectUri) + "&scope=" + Uri.EscapeDataString(scope) + "&state=" + Uri.EscapeDataString(state); var redirectContext = new GdepApplyRedirectContext(Context, Options, properties, authorizationEndpoint); Options.Provider.ApplyRedirect(redirectContext);
在 AuthenticateCoreAsync
方法中根據認證服務器返回的認證碼 (Authorization Code) 來獲取用戶信息, 示例代碼以下:
var requestPrefix = Request.Scheme + "://" + Request.Host; var redirectUri = requestPrefix + Request.PathBase + Options.CallbackPath; var tokenRequest = new Dictionary<string, string> { ["grant_type"] = "authorization_code", ["code"] = code, ["redirect_uri"] = redirectUri, ["client_id"] = Options.AppId, ["client_secret"] = Options.AppSecret }; var tokenResponse = await httpClient.PostAsync( Options.TokenEndpoint, new FormUrlEncodedContent(tokenRequest) ); tokenResponse.EnsureSuccessStatusCode(); string json = await tokenResponse.Content.ReadAsStringAsync(); var form = JObject.Parse(json); var accessToken = form.Value<string>("access_token"); var expires = form.Value<string>("expires_in"); var tokenType = form.Value<string>("token_type"); var refreshToken = form.Value<string>("refresh_token"); string graphAddress = Options.UserInformationEndpoint + "?access_token=" + Uri.EscapeDataString(accessToken); if (Options.SendAppSecretProof) { graphAddress += "&appsecret_proof=" + GenerateAppSecretProof(accessToken); } var graphRequest = new HttpRequestMessage(HttpMethod.Get, graphAddress); graphRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); var graphResponse = await httpClient.SendAsync(graphRequest, Request.CallCancelled); graphResponse.EnsureSuccessStatusCode(); json = await graphResponse.Content.ReadAsStringAsync(); JObject user = JObject.Parse(json);
在 InvokeReplyPathAsync
方法中用 SignInManager
登陸, 而後返回給後續的應用程序 WebAPI 來處理, 示例代碼以下:
var context = new GdepReturnEndpointContext(Context, ticker); context.SignInAsAuthenticationType = Options.SignInAsAuthenticationType; context.RedirectUri = ticker.Properties.RedirectUri; await Options.Provider.ReturnEndpoint(context); if (context.SignInAsAuthenticationType != null && context.Identity != null) { var grantIdentity = context.Identity; if (!string.Equals(grantIdentity.AuthenticationType, context.SignInAsAuthenticationType, StringComparison.Ordinal)) { grantIdentity = new ClaimsIdentity(grantIdentity.Claims, context.SignInAsAuthenticationType, grantIdentity.NameClaimType, grantIdentity.RoleClaimType); } Context.Authentication.SignIn(context.Properties, grantIdentity); }
到如今爲止, 自定義的 OAuth2 認證中間件基本上就完成了, 代碼量不算多, 若是有不清楚的地方, 能夠參閱 katanaproject 的源代碼。