欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

asp.net core 外部认证多站点模式实现

程序员文章站 2022-04-04 15:00:20
PS:之前因为需要扩展了微信和QQ的认证,使得网站是可以使用QQ和微信直接登录。github 传送门 。然后有小伙伴问,能否让这个配置信息(appid, appsecret)按需改变,而不是在 ConfigureServices 里面写好。 先上 官方文档 : https://docs.micros ......

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 保存起来。

asp.net core 外部认证多站点模式实现

 

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:如有错误,欢迎指正。

 

源地址: