PS:之前因為需要擴展了微信和QQ的認證,使得網站是可以使用QQ和微信直接登錄。github 傳送門 。然後有小伙伴問,能否讓這個配置信息(appid, appsecret)按需改變,而不是在 ConfigureServices 裡面寫好。 先上 官方文檔 : https://docs.micros ...
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