asp.net core 外部认证多站点模式实现
ps:之前因为需要扩展了微信和qq的认证,使得网站是可以使用qq和微信直接登录。github 传送门 。然后有小伙伴问,能否让这个配置信息(appid, appsecret)按需改变,而不是在 configureservices 里面写好。
先上 官方文档 :
官方已经实现了 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:如有错误,欢迎指正。
源地址:
上一篇: C#系列之聊聊.Net Core的InMemoryCache
下一篇: 不是缺心眼