使用微服务架构思想,设计部署OAuth2.0授权认证框架

Posted 深蓝医生

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用微服务架构思想,设计部署OAuth2.0授权认证框架相关的知识,希望对你有一定的参考价值。

1,授权认证与微服务架构

1.1,由不同团队合作引发的授权认证问题

去年的时候,公司开发一款新产品,但人手不够,将B/S系统的Web开发外包,外包团队使用Vue.js框架,调用我们的WebAPI,但是这些WebAPI并不在一台服务器上,甚至可能是第三方提供的WebAPI。同时处于系统安全的架构设计,后端WebAPI是不能直接暴露在外面的;另一方面,我们这个新产品还有一个C/S系统,C端登录的时候,要求统一到B/S端登录,可以从C端无障碍的访问任意B/S端的页面,也可以调用B/S系统的一些API,所以又增加了一个API网关代理。

整个系统的架构示意图如下:

注:上图还有一个iMSF,这是一个实时消息服务框架,这里用来做文件服务,参见《消息服务框架使用案例之--大文件上传(断点续传)功能》。在Web端会读取这些上传的文件。

1.2,微服务--分布式“最彻底”的分

1.2.1,为什么需要分布式

大部分情况下,如果你的系统不是很复杂,API和授权认证服务,文件服务都可以放到一台服务器:Web Port 服务器上,但要把它们分开部署到不同的站点,或者不同的服务器,主要是出于以下考虑:

1,职责单一:每一个服务都只做一类工作,比如某某业务WebAPI,授权服务,用户身份认证服务,文件服务等;职责单一使得开发、部署和维护变得容易,比如很容易知道当前是授权服务的问题,而不是业务API问题。

2,系统安全:采用内外网隔离的方案,一些功能需要直接暴露在公网,这需要付出额外的成本,比如带宽租用和安全设施;另外一些功能部署在内网,这样能够提供更大的安全保证。

3,易于维护:每一个服务职责都比较单一,所以每一个服务都足够小,那么开发维护就更容易,比如要更新一个功能,只需要更新一个服务而不用所有服务器都暂停;另一方面也更加容易监控服务器的负载,如果发现某一个服务器负载太大可以增加服务器来分散负载。

4,第三方接入:现在系统越来越复杂,内部的系统很可能需要跟第三方的系统对接,一起协同工作;或者整个系统一部分是 .NET开发的,一部分又是Java平台开发的,两个平台部署的环境有很大差异,没法部署在一起;或者虽然同是ASP.NET MVC,但是一个是MVC3,一个是MVC5,所以需要分别独立部署。

以上就是各个服务需要分开部署的原因,而这样做的结果就是我们常说的分布式计算了,这是自然需求的结果,不是为了分而才分。

1.2.2,依赖于中间层而不直接依赖于服务

客户端直接访问后端服务,对后端的服务会形成比较强的依赖。有架构经验的朋友都知道,解决依赖的常见手段就是添加一个中间层,客户端依赖于这个中间层而不是直接依赖于服务层。这样做有几个很大的好处:

  • 当服务负载过大的时候可以在中间层做负载均衡;
  • 或者后端某个服务出现问题可以切换主备服务;
  • 或者替换后端某个服务的版本做灰度发布。

另一方面,当后端服务部署为多个独立的进程/服务器后,客户端直接访问这些服务,将是一个更加较复杂的问题,负载均衡,主备切换,灰度发布等运维功能更难操作,除此之外,还有下面两个比较重要的问题:

  • 客户端直接访问后端多个服务,将暴露过多的后端服务器地址,从而增加安全隐患;
  • 后端服务太多,需要在客户端维护这些服务访问关系,增加开发调试的复杂性;
  • B/S页面的AJax跨域问题,WebAPI地址跟主站地址不一样,要解决跨域问题比较复杂并且也会增加安全隐患。

所以,为了解决客户端对后端服务层的依赖,并且解决后端服务太多以后引起的问题,我们需要在客户端和后端服务层之间添加一个中间层,这个中间层就是我们的服务代理层,也就是我们后面说的服务网关代理(WebAPI Gateway Proxy),它作为我们所有Web访问的入口站点,这就是上图所示的 Web Port。有了网关代理,后台所有的WebAPI都可以通过这个统一的入口提供对外服务的功能,而对于后端不同服务地址的路由,由网关代理的路由功能来实现,所以这个代理功能很像nginx这样的反向代理,只不过,这里仅仅代理WebAPI,而不是其它Web资源。

现在,网关已经成为很多分布式系统的标配,比如TX的这个架构:

注:上图来源于网络,侵删!

另外,这个读写分离代理,如果使用SOD框架,可以在AdoHelper对象直接设置读写不同的连接字符串简单达到效果。

1.2.3,微服务架构

经过上面的设计,我们发现这个架构有几个特点:

  1. 每个服务足够小,职责单一;
  2. 每个服务运行在自己的进程或者独立的服务器中,独立发布部署和开发维护;
  3. 服务对外提供访问或者服务之间进行通信,都是使用轻量级的HTTP API;
  4. 每个服务有自己独立的存储,彼此之间进行数据交互都通过接口进行;
  5. 有一个API代理网关统一提供服务的对外访问。

这些特点是非常符合现在流行的微服务思想的,比如在《什么是微服务》这篇文章中,像下面说的这样:

微服务最早由Martin Fowler与James Lewis于2014年共同提出,微服务架构风格是一种使用一套小服务来开发单个应用的方式途径,每个服务运行在自己的进程中,
并使用轻量级机制通信,通常是HTTP API,这些服务基于业务能力构建,并能够通过自动化部署机制来独立部署,这些服务使用不同的编程语言实现,以及不同数据存储技术,
并保持最低限度的集中式管理。

所以我们这个架构是基本符合微服务思想的,它的诞生背景也是要解决其它传统单体软件项目现在遇到的问题一样的,是在比较复杂的实际需求环境下自然而然的一种需求,不过好在它没有过多的“技术债务”,所以设计实施起来比较容易。下面我们来详细看看这个架构是如何落地的。

2,“授权\\认证\\资源”独立服务的OAuth2.0架构

2.1,为什么需要OAuth2.0 ?

OAuth 2.0已经是一个“用户验证和授权”的工业级标准。OAuth(开放授权)是一个开放标准,1.0版本于2006年创立,它允许用户让第三方应用访问该用户在某一网站上存储的私密的资源(如照片,视频,联系人列表),而无需将用户名和密码提供给第三方应用。 OAuth 2.0关注客户端开发者的简易性,同时为Web应用,桌面应用和手机,和起居室设备提供专门的认证流程。2012年10月,OAuth 2.0协议正式发布为RFC 6749。以上内容详见OAuth 2.0官网

现在百度开放平台,腾讯开放平台等大部分的开放平台都是使用的OAuth 2.0协议作为支撑,国内越来越多的企业都开始支持OAuth2.0协议。现在,我们的产品设计目标是要能够和第三方系统对接,那么在对接过程中的授权问题就是无法回避的问题。在我们原来的产品中,有用户授权验证的模块,但并没有拆分出独立的服务,用它与第三方系统对接会导致比较大的耦合性;另一方面,与第三方系统对接合作不一定每次都是以我们为主导,也有可能要用第三方的授权认证系统。这就出现了选择哪一方的授权认证方案的问题。之前我曾经经历过一个项目,因为其中的授权认证问题导致系统迟迟不能集成。所以,选择一个开放标准的授权认证方案,才是最佳的解决方案,而OAuth 2.0正是这样的方案。

2.2,OAuth的名词解释和规范

(1)Third-party application:第三方应用程序,本文中又称”客户端”(client),即上一节例子中的“Web Port”或者C/S客户端应用程序。
(2)HTTP service:HTTP服务提供商,即上一节例子中提供软件产品的我们公司或者第三方公司。
(3)Resource Owner:资源所有者,本文中又称“用户”(user)。
(4)User Agent:用户代理,本文中就是指浏览器或者C/S客户端应用程序。
(5)Authorization server:授权服务器,即服务提供商专门用来处理认证的服务器。
(6)Resource server:资源服务器,即服务提供商存放用户生成的资源的服务器,即上一节例子中的内部API服务器、第三方外部API服务器和文件服务器等。它与认证服务器,可以是同一台服务器,也可以是不同的服务器。

以上名词是OAuth规范内必须理解的一些名词,然后我们才能方便的讨论OAuth2.0是如何授权的。有关OAuth的思路、运行流程和详细的四种授权模式,请参考阮一峰老师的《理解OAuth 2.0》。

2.3,OAuth2.0的授权模式

为了表述方便,先简单说说这4种授权模式:

  1. 授权码模式(authorization code)--是功能最完整、流程最严密的授权模式。它的特点就是通过客户端的后台服务器,与"服务提供商"的认证服务器进行互动。
  2. 简化模式(implicit)--不通过第三方应用程序的服务器,直接在浏览器中向认证服务器申请令牌,跳过了"授权码"这个步骤,因此得名。所有步骤在浏览器中完成,令牌对访问者是可见的,且客户端不需要认证。
  3. 密码模式(resource owner password credentials)--用户向客户端提供自己的用户名和密码。客户端使用这些信息,向"服务商提供商"索要授权。在这种模式中,用户必须把自己的密码给客户端,但是客户端不得储存密码。
  4. 客户端模式(client credentials)--指客户端以自己的名义,而不是以用户的名义,向"服务提供商"进行认证。严格地说,客户端模式并不属于OAuth框架所要解决的问题。在这种模式中,用户直接向客户端注册,客户端以自己的名义要求"服务提供商"提供服务,其实不存在授权问题。

在我们的需求中,用户不仅仅通过B/S系统的浏览器进行操作,还会通过C/S程序的客户端进行操作,B/S,C/S系统主要都是我们提供和集成的,客户购买了我们这个产品要使用它就意味着客户信任我们的产品。授权码模式虽然是最完整的授权模式,但是授权码模式授权完成后需要浏览器的跳转,显然浏览器无法直接跳转到我们的C/S客户端,虽然从技术上可以模拟,但实现起来成本还是比较高;简化模式也有这个问题。所以我们最终决定采用OAuth2.0的密码模式。

2.4,OAuth2.0密码模式授权流程

 简单来说,密码模式的步骤如下:

  1.  用户向客户端提供用户名和密码。
  2. 客户端将用户名和密码发给认证服务器,向后者请求令牌。
  3. 认证服务器确认无误后,向客户端提供访问令牌。

 上面这个步骤只是说明了令牌的获取过程,也就是我们常说用户登陆成功的过程。当用户登陆成功之后,客户端得到了一个访问令牌,然后再使用这个令牌去访问资源服务器,具体说来还有如下后续过程:

  • 4,客户端携带此访问令牌,访问资源服务器;
  • 5,资源服务器去授权服务器验证客户端的访问令牌是否有效;
  • 6,如果访问令牌有效,授权服务器给资源服务器发送用户标识信息;
  • 7,资源服务器根据用户标识信息,处理业务请求,最后发送响应结果给客户端。

下面是流程图:

注意:这个流程适用于资源服务器、授权服务器相分离的情况,否则,流程中的第5,6步不是必须的,甚至第4,7步都是显而易见的事情而不必说明。现在大部分有关OAuth2.0的介绍文章都没有4,5,6,7步骤的说明,可能为了表述方便,默认都是将授权服务器跟资源服务器合在一起部署的。

2.5,授权、认证与资源服务的分离

什么情况下授权服务器跟资源服务器必须分开呢?

如果一个系统有多个资源服务器并且这些资源服务器的框架版本不兼容,运行环境有差异,代码平台不同(比如一个是.NET,一个是Java),或者一个是内部系统,一个是外部的第三方系统,必须分开部署。在这些情况下,授权服务器跟任意一个资源服务器部署在一起都不利于另一些资源服务器的使用,导致系统集成成本增加。这个时候,授权服务器必须跟资源服务器分开部署,我们在具体实现OAuth2.0系统的时候,需要做更多的事情。

什么情况下授权服务器跟认证服务器必须分开呢?

 授权(authorization)和认证(authentication)有相似之处,但也是两个不同的概念:

  • 授权(authorization):授权,批准;批准(或授权)的证书;
  • 认证(authentication):认证;身份验证;证明,鉴定;密押。

仅仅从这两个词的名词定义可能不太容易分辨,我们用实际的例子来说明他们的区别:

有一个管理系统,包括成熟的人员管理,角色管理,权限管理,系统登录的时候,用户输入的用户名和密码到系统的人员信息表中查询,通过后取得该用户的角色权限。

在这个场景中,用户登录系统实际上分为了3个步骤:

  1. 用户在登录界面,输入用户名和密码,提交登录请求;
  2. 【认证】系统校验用户输入的用户名和密码是否在人员信息表中;
  3. 【授权】给当前用户授予相应的角色权限。

现在,该管理系统需要和第三方系统对接,根据前面的分析,这种情况下最好将授权功能独立出来,采用OAuth这种开放授权方案,而认证问题,原有管理系统坚持用户信息是敏感信息,不能随意泄露给第三方,要求在原来管理系统完成认证。这样一来,授权和认证,只好分别作为两个服务,独立部署实现了。

本文的重点就是讲述如何在授权服务器和资源服务器相分离,甚至授权和认证服务器相分离的情况下,如何设计实现OAuth2.0的问题。

3,PWMIS OAuth2.0 方案

PWMIS OAuth2.0 方案就是一个符合上面要求的授权与认证相分离,授权与资源服务相分离的架构设计方案,该方案已经成功支撑了我们产品的应用。下面分别来说说该方案是如何设计和落地的。

3.1,使用Owin中间件搭建OAuth2.0认证授权服务器

这里主要总结下本人在这个产品中搭建OAuth2.0服务器工作的经验。至于为何需要OAuth2.0、为何是Owin、什么是Owin等问题,不再赘述。我假定读者是使用Asp.Net,并需要搭建OAuth2.0服务器,对于涉及的Asp.Net Identity(Claims Based Authentication)、Owin、OAuth2.0等知识点已有基本了解。若不了解,请先参考以下文章:

我们的工作,可以从研究《OWIN OAuth 2.0 Authorization Server》这个DEMO开始,不过为了更好的结合本文的主题,实现授权与认证相分离的微服务架构,推荐大家直接从我的DEMO开始:https://github.com/bluedoctor/PWMIS.OAuth2.0 

PS:大家觉得好,先点个赞支持下,谢谢!

克隆我这个DEMO到本地,下面开始我们OAuth2.0如何落地的正式讲解。

3.2,PWMIS.OAuth2.0解决方案介绍

首先看到解决方案视图,先逐个做下简单说明:

编号

角色

程序集名称

说明

1

授权服务器

PWMIS.OAuth2.AuthorizationCenter

授权中心

ASP.NET Web API+OWIN

2

资源服务器

Demo.OAuth2.WebApi

提供API资源

ASP.NET Web API+OWIN

   

Demo.OAuth2.WebApi2

 提供API资源

 ASP.NET Web API 

3

客户端

Demo.OAuth2.ConsoleTest

控制台测试程序,测试令牌申请等功能

   

 Demo.OAuth2.WinFormTest

 测试登录到B/S和打开B/S页面等功能

4

 API代理网关

Demo.OAuth2.Port

用户的Web入口,本测试程序入口

ASP.NET MVC 5.0

5

认证服务器

Demo.OAuth2.IdentityServer

简单登录账号认证

ASP.NET Web API

   

Demo.OAuth2.Mvc

 简单登录账号认证,支持登录会话

 ASP.NET Web MVC 

6

 其它

PWMIS.OAuth2.Tools

提供OAuth2.0 协议访问的一些有用的工具类

 

3.2.1,运行解决方案

将解决方案的项目,除了PWMIS.OAuth2.Tools,全部设置为启动项目,启动之后,在 http://localhost:62424/ 站点,输入下面的地址:

http://localhost:62424/Home

然后就可以看到下面的界面:

点击登录页面,为了方便演示,不真正验证用户名和密码,所以随意输入,提交后结果如下图:

点击确定,进入了业务操作页面,如下图:

如果能够看到这个页面,我们的OAuth2.0演示程序就成功了。

还可以运行解决方案里面的WinForm测试程序,先登录,然后运行性能测试,如下图:

更多信息,请参考下文的【3.8集成C/S客户端访问】

下面我们来看看各个程序集项目的构建过程。

3.3,项目 PWMIS.OAuth2.AuthorizationCenter

首先添加一个MVC5项目PWMIS.OAuth2.AuthorizationCenter,然后添加如下包引用:

Microsoft.AspNet.Mvc
Microsoft.Owin.Host.SystemWeb
Microsoft.Owin.Security.OAuth
Microsoft.Owin.Security.Cookies

然后在项目根目录下添加一个OWin的启动类 Startup:

using Microsoft.Owin;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.OAuth;
using Owin;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Web.Http;

namespace PWMIS.OAuth2.AuthorizationCenter
{
    public partial class Startup
    {
        public void ConfigureAuth(IAppBuilder app)
        {
            var OAuthOptions = new OAuthAuthorizationServerOptions
            {
                AllowInsecureHttp = true,
                AuthenticationMode = AuthenticationMode.Active,
                TokenEndpointPath = new PathString("/api/token"), //获取 access_token 授权服务请求地址
                AuthorizeEndpointPath = new PathString("/authorize"), //获取 authorization_code 授权服务请求地址
                AccessTokenExpireTimeSpan = TimeSpan.FromSeconds(60), //access_token 过期时间,默认10秒太短

                Provider = new OpenAuthorizationServerProvider(), //access_token 相关授权服务
                AuthorizationCodeProvider = new OpenAuthorizationCodeProvider(), //authorization_code 授权服务
                RefreshTokenProvider = new OpenRefreshTokenProvider() //refresh_token 授权服务
            };
            app.UseOAuthBearerTokens(OAuthOptions); //表示 token_type 使用 bearer 方式
        }

        public void Configuration(IAppBuilder app)
        {
            // For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?LinkID=316888
            ConfigureAuth(app);

            var configuration = new HttpConfiguration();
            WebApiConfig.Register(configuration);
            app.UseWebApi(configuration);

         }

      }
}

上面的代码中,定义了access_token 授权服务请求地址和access_token 过期时间,这里设置60秒后过期。由于本篇着重讲述OAuth2.0的密码授权模式,我们直接看到类 OpenAuthorizationServerProvider的定义:

 public class OpenAuthorizationServerProvider : OAuthAuthorizationServerProvider
    {
        /// <summary>
        /// 验证 client 信息
        /// </summary>
        public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
        {
            string clientId;
            string clientSecret;
            if (!context.TryGetBasicCredentials(out clientId, out clientSecret))
            {
                context.TryGetFormCredentials(out clientId, out clientSecret);
            }
            if (string.IsNullOrEmpty(clientId) || string.IsNullOrEmpty(clientSecret))
            {
                context.SetError("PWMIS.OAuth2 invalid_client", "client or clientSecret is null or empty");
                return;
            }

            var identityRepository = IdentityRepositoryFactory.CreateInstance();
            try
            {
                if (!await identityRepository.ValidateClient(clientId, clientSecret))
                {
                    context.SetError("PWMIS.OAuth2 invalid_client", "client or clientSecret is not valid");
                    return;
                }
            }
            catch (Exception ex)
            {
                context.SetError("PWMIS.OAuth2 identity_repository_error", ex.Message );
                Log("PWMIS.OAuth2 identity_repository_error:" + ex.Message);
                return;
            }
          
            context.Validated();
        }

        /// <summary>
        /// 生成 access_token(resource owner password credentials 授权方式)
        /// </summary>
        public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
        {
            string validationCode = "";
            string sessionId = "";
            if (string.IsNullOrEmpty(context.UserName))
            {
                context.SetError("PWMIS.OAuth2 invalid_username", "username is not valid");
                return;
            }
            if (string.IsNullOrEmpty(context.Password))
            {
                context.SetError("PWMIS.OAuth2 invalid_password", "password is not valid");
                return;
            }
            if (context.Scope.Count > 0)
            {
                //处理用户会话标识和验证码
                var temp= context.Scope.FirstOrDefault(p => p.Contains("ValidationCode:"));
                if (temp != null)
                {
                    validationCode = temp.Split(\':\')[1];
                }

                var temp1 = context.Scope.FirstOrDefault(p => p.Contains("SessionID:"));
                if (temp1 != null)
                {
                    sessionId = temp1.Split(\':\')[1];
                }
            }

            IdentityService service = new IdentityService();
            try
            {
                LoginResultModel user = await service.UserLogin(context.UserName, context.Password,sessionId, validationCode);
                if (user == null)
                {
                    context.SetError("PWMIS.OAuth2 invalid_identity", "username or password is not valid");
                    return;
                }
                else  if (string.IsNullOrEmpty(user.UserName))
                {
                    context.SetError("PWMIS.OAuth2 invalid_identity", user.ErrorMessage);
                    return;
                }
            }
            catch (Exception ex)
            {
                context.SetError("PWMIS.OAuth2 identity_service_error", ex.Message );
                Log("PWMIS.OAuth2 identity_service_error:" + ex.Message);
                return;
            }
           

            var OAuthIdentity = new ClaimsIdentity(context.Options.AuthenticationType);
            OAuthIdentity.AddClaim(new Claim(ClaimTypes.Name, context.UserName));
            context.Validated(OAuthIdentity);
        }

        /// <summary>
        /// 验证 access_token 的请求
        /// </summary>
        public override async Task ValidateTokenRequest(OAuthValidateTokenRequestContext context)
        {
            if (context.TokenRequest.IsAuthorizationCodeGrantType || 
                context.TokenRequest.IsRefreshTokenGrantType || 
                context.TokenRequest.IsResourceOwnerPasswordCredentialsGrantType ||
                context.TokenRequest.IsClientCredentialsGrantType)
            {
                context.Validated();
            }
            else
            {
                context.Rejected();
            }
        }
          
    }
}
OpenAuthorizationServerProvider

 token过期时间不宜太长,比如一天,这样不安全,但也不能太短,比如10秒,这样当API访问量比较大的时候会增大刷新token的负担,所以这里设置成60秒。

3.3.1,验证客户端信息

在本类的第一个方法 ValidateClientAuthentication 验证客户端的信息,这里的客户端可能是C/S程序的客户端,也可能是访问授权服务器的网关代理服务器,OAuth2.0会验证需要生成访问令牌的客户端,只有合法的客户端才可以提供后续的生成令牌服务。

客户端信息有2个部分,一个是clientId,一个是clientSecret,前者是客户端的唯一标识,后者是授权服务器颁发给客户端的秘钥,这个秘钥可以设定有效期或者设定授权范围。为简便起见,我们的演示程序仅仅到数据库去检查下传递的这两个参数是否有对应的数据记录,使用下面一行代码:

 var identityRepository = IdentityRepositoryFactory.CreateInstance();

这里会用到一个验证客户端的接口,包括验证用户名和密码的方法一起定义了:

 /// <summary>
    /// 身份认证持久化接口
    /// </summary>
    public interface IIdentityRepository
    {
        /// <summary>
        /// 客户ID是否存在
        /// </summary>
        /// <param name="clientId"></param>
        /// <returns></returns>
        Task<bool> ExistsClientId(string clientId);
        /// <summary>
        /// 校验客户标识
        /// </summary>
        /// <param name="clientId">客户ID</param>
        /// <param name="clientSecret">客户秘钥</param>
        /// <returns></returns>
        Task<bool> ValidateClient(string clientId, string clientSecret);
        /// <summary>
        /// 校验用户名密码
        /// </summary>
        /// <param name="userName"></param>
        /// <param name="password"></param>
        /// <returns></returns>
        Task<bool> ValidatedUserPassword(string userName, string password);
    }

这样我们就可以通过反射或者简单 IOC框架将客户端验证的具体实现类注入到程序中,本例实现了一个简单的客户端和用户认证类,采用的是SOD框架访问数据库:

namespace PWMIS.OAuth2.AuthorizationCenter.Repository
{
    public class SimpleIdentityRepository : IIdentityRepository
    {
        private static System.Collections.Concurrent.ConcurrentDictionary<string, string> dictClient = new System.Collections.Concurrent.ConcurrentDictionary<string, string>();
        public async Task<bool> ExistsClientId(string clientId)
        {
            return await Task.Run<bool>(() =>
            {
                AuthClientInfoEntity entity = new AuthClientInfoEntity();
                entity.ClientId = clientId;

                OQL q = OQL.From(entity)
                    .Select(entity.ClientId)
                    .Where(entity.ClientId)
                    .END;
                AuthDbContext context = new AuthDbContext();
                AuthClientInfoEntity dbEntity = context.QueryObject<AuthClientInfoEntity>(q);
                return dbEntity != null;
            });
        }

        public async Task<bool> ValidateClient(string clientId, string clientSecret)
        {
            string dict_clientSecret;
            if (dictClient.TryGetValue(clientId, out dict_clientSecret) && dict_clientSecret== clientSecret)
            {
                return true;
            }
            else
            {
                return await Task.Run<bool>(() => {
                    AuthClientInfoEntity entity = new AuthClientInfoEntity();
                    entity.ClientId = clientId;
                    entity.ClientSecret = clientSecret;
                    OQL q = OQL.From(entity)
                        .Select(entity.ClientId)
                        .Where(entity.ClientId, entity.ClientSecret)
                        .END;
                    AuthDbContext context = new AuthDbContext();
                    AuthClientInfoEntity dbEntity = context.QueryObject<AuthClientInfoEntity>(q);
                    if (dbEntity != null)
                    {
                        dictClient.TryAdd(clientId, clientSecret);
                        return true;
                    }
                    else
                        return false;
                });
            }
            
        }

        public async Task<bool> ValidatedUserPassword(string userName, string password)
        {
            return await Task.Run<bool>(() =>
            {
                UserInfoEntity user = new UserInfoEntity();
                user.UserName = userName;
                user.Password = password;
                OQL q = OQL.From(user)
                   .Select()
                   .Where(user.UserName, user.Password)
                   .END;
                AuthDbContext context = new AuthDbContext();
                AuthClientInfoEntity dbEntity = context.QueryObject<AuthClientInfoEntity>(q);
                return dbEntity != null;
            });
        }
    }
}

AuthDbContext 类非常简单,它会自动生成验证客户端所需要的表:

namespace PWMIS.OAuth2.AuthorizationCenter.Repository
{
    public class AuthDbContext:DbContext
    {
        public AuthDbContext()
            : base("OAuth2")
        {
                    
        }


        protected override bool CheckAllTableExists()
        {
            base.CheckTableExists<AuthClientInfoEntity>();
            base.CheckTableExists<UserInfoEntity>();
            return true;
        }
    }
}

3.3.2,认证用户,生成访问令牌

生成访问令牌需要重写OWIN OAuthAuthorizationServerProvider类的 GrantResourceOwnerCredentials方法(方法的详细内容看前面【OpenAuthorizationServerProvider的定义】),方法里面使用到了IdentityService 对象,它有一个UserLogin 方法,用来实现或者调用用户认证服务: 

namespace PWMIS.OAuth2.AuthorizationCenter.Service
{
    public class IdentityService
    {
        public async Task<LoginResultModel> UserLogin(string userName, string password,string sessionId, string validationCode)
        { 
            //通过配置,决定是使用本地数据库验证登录,还是使用登录接口服务登录
            string identityLoginMode = System.Configuration.ConfigurationManager.AppSettings["IdentityLoginMode"];
            if (!string.IsNullOrEmpty(identityLoginMode) && identityLoginMode.ToLower() == "database")
            {
                var identityRepository = IdentityRepositoryFactory.CreateInstance();
                bool flag= await identityRepository.ValidatedUserPassword(userName, password);
                LoginResultModel result = new LoginResultModel();
                if (flag)
                {
                    result.ID = "123";
                    result.UserName = userName;
                    result.Roles = "";//暂时略
                }
                return result;
            }
            else
            {
                System.Diagnostics.Stopwatch sp = new System.Diagnostics.Stopwatch();
                var parameters = new Dictionary<string, string>();
                //parameters.Add("ID", "");
                parameters.Add("UserName", userName);
                parameters.Add("Password", password);
                parameters.Add("ID", sessionId);
                parameters.Add("ValidationCode", validationCode);
                //parameters.Add("Roles", "");

                string

以上是关于使用微服务架构思想,设计部署OAuth2.0授权认证框架的主要内容,如果未能解决你的问题,请参考以下文章

#私藏项目实操分享#权限设计系列「认证授权专题」微服务架构的登陆认证问题

.NET CORE开源 DDD微服务 支持 多租户 单点登录 多级缓存自动任务分布式日志授权和鉴权 网关 注册与发现 系统架构 docker部署

解决微信OAuth2.0网页授权回调域名只能设置一个的问题

解决微信OAuth2.0网页授权回调域名只能设置一个的问题

分布式微服务企业快速架构—SpringCloud分布式微服务云架构&设计思想

API网关很重要