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);  这个函数作为接收上一步中传递的 认证参数。   默认实现代码:

  1. protected override async Task HandleChallengeAsync(AuthenticationProperties properties)
  2. {
  3.  
  4. if (string.IsNullOrEmpty(properties.RedirectUri))
  5. {
  6. properties.RedirectUri = CurrentUri;
  7. }
  8.  
  9. // OAuth2 10.12 CSRF
  10.  
  11. GenerateCorrelationId(properties);
  12.  
  13. var authorizationEndpoint = BuildChallengeUrl(properties, BuildRedirectUri(Options.CallbackPath));
  14.  
  15. var redirectContext = new RedirectContext<OAuthOptions>(
  16.  
  17. Context, Scheme, Options,
  18.  
  19. properties, authorizationEndpoint);
  20.  
  21. await Events.RedirectToAuthorizationEndpoint(redirectContext);
  22. }
  1. protected virtual string BuildChallengeUrl(AuthenticationProperties properties, string redirectUri)
  2. {
  3. var scopeParameter = properties.GetParameter<ICollection<string>>(OAuthChallengeProperties.ScopeKey);
  4. var scope = scopeParameter != null ? FormatScope(scopeParameter) : FormatScope();
  5.  
  6. var state = Options.StateDataFormat.Protect(properties);
  7. var parameters = new Dictionary<string, string>
  8. {
  9. { "client_id", Options.ClientId },
  10. { "scope", scope },
  11. { "response_type", "code" },
  12. { "redirect_uri", redirectUri },
  13. { "state", state },
  14. };
  15.  
  16. return QueryHelpers.AddQueryString(Options.AuthorizationEndpoint, parameters);
  17. }

在这里面,构建了一个请求URL, 要求的这个URL 是目标站点授权的URL, 比如微信的那个黑色背景中间有二维码的页面。  这个构建请求URL的方法可以重写。

5,在上一步中,在需要授权的网站,授权完成后,会跳转到自己的网站并且带上授权相关数据。入口是  Task<HandleRequestResult> HandleRemoteAuthenticateAsync();

改造方法:

在上面的分析中,官方的实现是 在 ConfigureServices 中配置好参数 TOptions ,然后在 Handler 中 获取该参数。我们的目的是在请求中可以按需改变参数,如 client_id。

1,定义一个接口 IClientStore 和 一个实体 ClientStoreModel 。

  1. public interface IClientStore
  2. {
  3. /// <summary>
  4. /// 由 <paramref name="provider"/> 和 <paramref name="subjectId"/> 查找 <seealso cref="ClientStoreModel"/>
  5. /// </summary>
  6. ClientStoreModel FindBySubjectId(string provider, string subjectId);
  7. }
  1. /// <summary>
  2. /// 表示一个 Client 信息
  3. /// </summary>
  4. public class ClientStoreModel
  5. {
  6. public string Provider { get; set; }
  7.  
  8. public string SubjectId { get; set; }
  9.  
  10. /// <summary>
  11. /// Gets or sets the provider-assigned client id.
  12. /// </summary>
  13. public string ClientId { get; set; }
  14.  
  15. /// <summary>
  16. /// Gets or sets the provider-assigned client secret.
  17. /// </summary>
  18. public string ClientSecret { get; set; }
  19.  
  20. }

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 。

  1. var callbackPath = Options.CallbackPath.Add("/subject").Value;
  2.  
  3. var subjectId = Request.Path.Value.Remove(, callbackPath.Length + );

6,修改 ExchangeCodeAsync 方法

  1. protected virtual async Task<OAuthTokenResponse> ExchangeCodeAsync(string subjectId, string code, string redirectUri)
  2. {
  3. var clientStore = GetClientStore(subjectId);
  4.  
  5. var tokenRequestParameters = new Dictionary<string, string>()
  6. {
  7. { "client_id", clientStore.ClientId },
  8. { "client_secret", clientStore.ClientSecret },
  9.  
  10. { "redirect_uri", redirectUri },
  11. { "code", code },
  12. { "grant_type", "authorization_code" },
  13. };
  14.  
  15. var requestContent = new FormUrlEncodedContent(tokenRequestParameters);
  16.  
  17. var requestMessage = new HttpRequestMessage(HttpMethod.Post, Options.TokenEndpoint);
  18. requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
  19. requestMessage.Content = requestContent;
  20. var response = await Backchannel.SendAsync(requestMessage, Context.RequestAborted);
  21. if (response.IsSuccessStatusCode)
  22. {
  23. var payload = JObject.Parse(await response.Content.ReadAsStringAsync());
  24. return OAuthTokenResponse.Success(payload);
  25. }
  26. else
  27. {
  28. var error = "OAuth token endpoint failure: " + await Display(response);
  29. return OAuthTokenResponse.Failed(new Exception(error));
  30. }
  31. }

7,还有一些小修改,就不一一列出来了。  到这里  MultiOAuthHandler  相关就调整好了。

我把这个单独出来了  Microsoft.AspNetCore.Authentication.MultiOAuth

8,使用 。 实现 IClientStore 接口,然后在 ConfigureServices  中添加如下代码:

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

9, 目前github 上的demo 只对 微信  做了实现。

PS:如有错误,欢迎指正。

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

asp.net core 外部认证多站点模式实现的更多相关文章

  1. asp.net core 使用identityServer4的密码模式来进行身份认证(2) 认证授权原理

    前言:本文将会结合asp.net core 认证源码来分析起认证的原理与流程.asp.net core版本2.2 对于大部分使用asp.net core开发的人来说. 下面这几行代码应该很熟悉了. s ...

  2. asp.net core 自定义认证方式--请求头认证

    asp.net core 自定义认证方式--请求头认证 Intro 最近开始真正的实践了一些网关的东西,最近写几篇文章分享一下我的实践以及遇到的问题. 本文主要介绍网关后面的服务如何进行认证. 解决思 ...

  3. ASP.NET Core Token认证

    翻译:Token Authentication in ASP.NET Core 令牌认证(Token Authentication)已经成为单页应用(SPA)和移动应用事实上的标准.即使是传统的B/S ...

  4. 深入解读 ASP.NET Core 身份认证过程

    长话短说:上文我们讲了 ASP.NET Core 基于声明的访问控制到底是什么鬼? 今天我们乘胜追击:聊一聊ASP.NET Core 中的身份验证. 身份验证是确定用户身份的过程. 授权是确定用户是否 ...

  5. ASP.NET Core路由中间件[2]: 路由模式

    一个Web应用本质上体现为一组终结点的集合.终结点则体现为一个暴露在网络中可供外界采用HTTP协议调用的服务,路由的作用就是建立一个请求URL模式与对应终结点之间的映射关系.借助这个映射关系,客户端可 ...

  6. asp.net core 使用identityServer4的密码模式来进行身份认证(一)

    IdentityServer4是ASP.NET Core的一个包含OpenID和OAuth 2.0协议的框架.具体Oauth 2.0和openId请百度. 前言本博文适用于前后端分离或者为移动产品来后 ...

  7. ASP.NET Core 身份认证 (Identity、Authentication)

    Authentication和Authorization 每每说到身份验证.认证的时候,总不免说提及一下这2个词.他们的看起来非常的相似,但实际上他们是不一样的. Authentication想要说明 ...

  8. 避免在ASP.NET Core中使用服务定位器模式

    (此文章同时发表在本人微信公众号"dotNET每日精华文章",欢迎右边二维码来关注.) 题记:服务定位器(Service Locator)作为一种反模式,一般情况下应该避免使用,在 ...

  9. Visual Studio 2015/2017 与ASP.NET CORE 联合创建具有SPA模式的Angular2模板

    虽然注册博客园很久,但是一直没有什么可写的,真心感觉好尴尬了,这次终于找到了一点可以写,有点小兴奋和小害羞呢. 进入主题,前端SPA模式越来越受到欢迎,Core 也开始被很多企业提上日程,但是因为这个 ...

随机推荐

  1. Scanner的小实例

    package com.b; import java.util.Random; import java.util.Scanner; //猜拳游戏 1.人从键盘输入 2.计算机从电脑随机输入 3.条件判 ...

  2. java排序。。。

    题目:n位学生,m位评委,去掉一个最高分,和一个最低分,选手最后得分 package com.aini; import java.util.Arrays; public class WDS { int ...

  3. php代码中临时开启错误调试

    对php.ini 中参数的设置 也可用在php代码中完成. 调用:调用ini_set()函数 //开启php.ini中的display_errors指令 ini_set('display_errors ...

  4. Oracle常见的表连接的方法

    1 排序合并连接SMJ Sort merge join 排序合并总结: 1 通常情况下,排序合并连接的效率远不如hash join,前者适用范围更广,hj只使用于等值连接,smj范围更广(<,& ...

  5. Python之购物商场

    作业:购物商场 1.流程图 2.初始化用户账号存储文件 初始化存储一个空的用户账号字典,保存到文件 user.pkl.执行如下代码,即可初始化完成. #!/usr/bin/env python # - ...

  6. 8.solr学习速成之FacetPivot

    什么是Facet.pivot  Facet.pivot就是按照多个维度进行分组查询,是Facet的加强,在实际运用中经常用到,一个典型的例子就是商品目录树 NamedList解释: NamedList ...

  7. python闭包和装饰器的理解

    闭包: 两个函数的嵌套,外部函数返回内部函数的引⽤,外部函数⼀定有参数 def 外部函数(参数): def 内部函数(): pass return 内部函数 他跟函数之间的区别: 1.格式两个函数嵌套 ...

  8. Theos简介

    [Theos简介] Theos is a cross-platform suite of development tools for managing, developing, and deployi ...

  9. ionic中的后退方法

    1)$ionicHistory.goBack(); 2)$ionicNavBarDelegate.back(); 个人感觉: 1)$ionicHistory.goBack()会按照html历史来后退 ...

  10. oracle 创建表 外键约束

    create table usertable( id int primary key, username ) not null, birthday date, sex ), address ) ); ...