asp.net core 外部認證多站點模式實現

PS:以前由於須要擴展了微信和QQ的認證,使得網站是可使用QQ和微信直接登陸。github 傳送門 。而後有小夥伴問,可否讓這個配置信息(appid, appsecret)按需改變,而不是在 ConfigureServices  裏面寫好。git

先上 官方文檔 :  https://docs.microsoft.com/zh-cn/aspnet/core/security/authentication/social/?view=aspnetcore-2.1 github

官方已經實現了 microsft,facebook,twitter,google 等這幾個網站認證。代碼能夠認證受權庫看到找到 https://github.com/aspnet/Security  。json

國內的QQ和微信其實也是基於OAuth來實現的,因此本身集成仍是比較容易。微信

正常狀況下,配置這個外部認證都是在 ConfigureServices 裏面配置好,而且使用配置或者是使用機密文件的形式來保存 appid 等信息。app

回到正文,多站點模式,就是一個網站下分爲多個子站點,而且不一樣的子站點能夠配置不一樣的appId 。Asp.net core 默認的配置模式,在這種場景下已經適應不了了。async

先上代碼: https://github.com/jxnkwlp/AspNetCore.AuthenticationQQ-WebChat/tree/muti-siteide

官方代碼分析:函數

1,RemoteAuthenticationHandler  遠程認證處理程序。位於 microsoft.aspnetcore.authentication  下 。 源碼 (https://github.com/aspnet/Security/blob/master/src/Microsoft.AspNetCore.Authentication/RemoteAuthenticationHandler.cs)post

這個是泛型類,而且須要一個  TOptions ,這個 TOptions 必須是繼承 RemoteAuthenticationOptions 的類。網站

 

2,OAuthHandler 實現 OAuth 認證處理程序,這個類繼承 RemoteAuthenticationHandler 。同時必須實現一個 OAuthOptions 。

正常狀況下實現 QQ、微信、github ,google ,facebook 等登陸都是基於這個來實現的。 OAuthHandler 已經實現了標準的 OAuth 認證。

源碼:https://github.com/aspnet/Security/blob/master/src/Microsoft.AspNetCore.Authentication.OAuth/OAuthHandler.cs

在 ConfigureServices 中,使用  AddFacebook 等方法,就是將 對於的 Handler 添加到 處理管道中,這些管到都是實現了 OAuth,而後傳遞 對應的 Options 來配置Handler 。

 

3,回到Account/ExternalLogin ,在提交外部登陸的請求中, AuthenticationProperties  properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl);  //這行代碼的做用是 配置當前外部登陸返回URL和認證的相關屬性。return Challenge(properties, provider);  // 將結果轉到相關相關處理程序。這裏返回的結果用於上面  OAuthHandler 做爲一個處理參數。從這開始,就進入了 OAuthHandler 的處理範圍了。

 

4,查看 OAuthHandler 代碼 。  Task HandleChallengeAsync(AuthenticationProperties properties);  這個函數做爲接收上一步中傳遞的 認證參數。   默認實現代碼:

protected override async Task HandleChallengeAsync(AuthenticationProperties properties) 
{

    if (string.IsNullOrEmpty(properties.RedirectUri)) 
    { 
        properties.RedirectUri = CurrentUri; 
    }

    // OAuth2 10.12 CSRF

    GenerateCorrelationId(properties);

    var authorizationEndpoint = BuildChallengeUrl(properties, BuildRedirectUri(Options.CallbackPath));

    var redirectContext = new RedirectContext<OAuthOptions>(

        Context, Scheme, Options,

        properties, authorizationEndpoint);

    await Events.RedirectToAuthorizationEndpoint(redirectContext); 
}
protected virtual string BuildChallengeUrl(AuthenticationProperties properties, string redirectUri)
{
    var scopeParameter = properties.GetParameter<ICollection<string>>(OAuthChallengeProperties.ScopeKey);
    var scope = scopeParameter != null ? FormatScope(scopeParameter) : FormatScope();

    var state = Options.StateDataFormat.Protect(properties);
    var parameters = new Dictionary<string, string>
    {
        { "client_id", Options.ClientId },
        { "scope", scope },
        { "response_type", "code" },
        { "redirect_uri", redirectUri },
        { "state", state },
    };

    return QueryHelpers.AddQueryString(Options.AuthorizationEndpoint, parameters);
}

 

在這裏面,構建了一個請求URL, 要求的這個URL 是目標站點受權的URL, 好比微信的那個黑色背景中間有二維碼的頁面。  這個構建請求URL的方法能夠重寫。

5,在上一步中,在須要受權的網站,受權完成後,會跳轉到本身的網站而且帶上受權相關數據。入口是  Task<HandleRequestResult> HandleRemoteAuthenticateAsync();  

改造方法:

在上面的分析中,官方的實現是 在 ConfigureServices 中配置好參數 TOptions ,而後在 Handler 中 獲取該參數。咱們的目的是在請求中能夠按需改變參數,如 client_id。

1,定義一個接口 IClientStore 和 一個實體 ClientStoreModel 。

public interface IClientStore
{
    /// <summary>
    ///<paramref name="provider"/><paramref name="subjectId"/> 查找 <seealso cref="ClientStoreModel"/>
    /// </summary> 
    ClientStoreModel FindBySubjectId(string provider, string subjectId);
}

 

/// <summary>
///  表示一個 Client 信息 
/// </summary>
public class ClientStoreModel
{
    public string Provider { get; set; }

    public string SubjectId { get; set; }

    /// <summary>
    /// Gets or sets the provider-assigned client id.
    /// </summary>
    public string ClientId { get; set; }

    /// <summary>
    /// Gets or sets the provider-assigned client secret.
    /// </summary>
    public string ClientSecret { get; set; }

}

 

IClientStore 用於查找 client 的配置信息

2,在 Account/ExternalLogin 中,新增一個 參數 subjectId  ,表示在當前某個認證(Provider)中是哪一個請求(SubjectId) 。

同時在返回的受權配置參數中將subjectId 保存起來。

 

3,定義一個 MultiOAuthHandler ,集成 RemoteAuthenticationHandler  ,不繼承  OAuthHandler 是由於 這裏須要一個新的 Options.  (完整代碼 請看代碼倉庫)  定義: class MultiOAuthHandler<TMultiOAuthOptions>:RemoteAuthenticationHandler<TMultiOAuthOptions>whereTMultiOAuthOptions:MultiOAuthOptions,new() 

在構造函數中添加參數 IClientStore 。

4,在默認的實現中,從外部受權網站跳轉回本身的網站的時候,默認的路徑是 /signin-{provider} , 好比 /signin-microsoft  。爲了區分請求的 subjectId ,  這個默認路徑將改成  /signin-{provider}/subject/{subjectId}  。

5,修改 HandleRemoteAuthenticateAsync  ,在開頭添加2行代碼,用於獲取 subjectId 。

var callbackPath = Options.CallbackPath.Add("/subject").Value;

var subjectId = Request.Path.Value.Remove(0, callbackPath.Length + 1);

 

6,修改 ExchangeCodeAsync 方法

protected virtual async Task<OAuthTokenResponse> ExchangeCodeAsync(string subjectId, string code, string redirectUri)
{
    var clientStore = GetClientStore(subjectId);

    var tokenRequestParameters = new Dictionary<string, string>()
    {
        { "client_id", clientStore.ClientId },
        { "client_secret", clientStore.ClientSecret },

        { "redirect_uri", redirectUri },
        { "code", code },
        { "grant_type", "authorization_code" },
    };

    var requestContent = new FormUrlEncodedContent(tokenRequestParameters);

    var requestMessage = new HttpRequestMessage(HttpMethod.Post, Options.TokenEndpoint);
    requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
    requestMessage.Content = requestContent;
    var response = await Backchannel.SendAsync(requestMessage, Context.RequestAborted);
    if (response.IsSuccessStatusCode)
    {
        var payload = JObject.Parse(await response.Content.ReadAsStringAsync());
        return OAuthTokenResponse.Success(payload);
    }
    else
    {
        var error = "OAuth token endpoint failure: " + await Display(response);
        return OAuthTokenResponse.Failed(new Exception(error));
    }
}

 

7,還有一些小修改,就不一一列出來了。  到這裏  MultiOAuthHandler  相關就調整好了。

我把這個單獨出來了  Microsoft.AspNetCore.Authentication.MultiOAuth 

 

8,使用 。 實現 IClientStore 接口,而後在 ConfigureServices  中添加以下代碼:

services.AddAuthentication()
    .AddMultiOAuthStore<MylientStore>() 
    .AddMultiWeixinAuthentication(); // 微信

 

9, 目前github 上的demo 只對 微信  作了實現。

 

PS:若有錯誤,歡迎指正。

 

源地址: https://blog.wuliping.cn/post/aspnet-core-security-authentication-social-multi-config 

相關文章
相關標籤/搜索