擴展 Microsoft.Owin.Security

微軟在 OWIN 框架中對 OAuth 認證的支持很是好, 使用現有的 OWIN 中間件能夠作到:html

微軟提供了這麼多的 OAuth 認證中間件, 對天朝的牆內用戶來講, 只能用三個字來歸納「然並卵」。git

要接入國內 騰訊微信 、新浪微博提供的 OAuth2 認證, 仍是要根據現有的中間件 Microsoft.Owin.Security 進行二次開發, 上面微軟提供的 Facebook、 Google 等實現能夠做爲參考。github

先來簡單回顧一下 OAuth2 的 認證流程 , 以下圖所示:json

直接和 OAuth2 認證服務器交互的步驟有:瀏覽器

  • (A) 將用戶代理(瀏覽器)重定向到認證服務器, 須要提供客戶端憑據 (Client Identifier) , 並取得認證碼 (Authorization Code) ;
  • (D) 使用認證服務器返回的認證碼 (Authorization Code) 獲取訪問憑據 (Access Token) ;
  • (E) 根據訪問憑據 (Access Token) 獲取用戶信息。

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 的源代碼。

相關文章
相關標籤/搜索