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


PS:之前因為需要擴展了微信和QQ的認證,使得網站是可以使用QQ和微信直接登錄。github 傳送門 。然后有小伙伴問,能否讓這個配置信息(appid, appsecret)按需改變,而不是在 ConfigureServices  里面寫好。

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

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

國內的QQ和微信其實也是基於OAuth來實現的,所以自己集成還是比較容易。

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

回到正文,多站點模式,就是一個網站下分為多個子站點,並且不同的子站點可以配置不同的appId 。Asp.net core 默認的配置模式,在這種場景下已經適應不了了。

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

官方代碼分析:

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

這個是泛型類,並且需要一個  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 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM