2,系统安全:采用内外网隔离的方案,一些功能需要直接暴露在公网,这需要付出额外的成本,比如带宽租用和安全设施;另外一些功能部署在内网,这样能够提供更大的安全保证。
3,易于维护:每一个服务职责都比较单一,所以每一个服务都足够小,那么开发维护就更容易,比如要更新一个功能,只需要更新一个服务而不用所有服务器都暂停;另一方面也更加容易监控服务器的负载,如果发现某一个服务器负载太大可以增加服务器来分散负载。
4,第三方接入:现在系统越来越复杂,内部的系统很可能需要跟第三方的系统对接,一起协同工作;或者整个系统一部分是。NET开发的,一部分又是Java平台开发的,两个平台部署的环境有很大差异,没法部署在一起;或者虽然同是ASP.NETMVC,但是一个是MVC3,一个是MVC5,所以需要分别独立部署。
以上就是各个服务需要分开部署的原因,而这样做的结果就是我们常说的分布式计算了,这是自然需求的结果,不是为了分而才分。
客户端直接访问后端服务,对后端的服务会形成比较强的依赖。有架构经验的朋友都知道,解决依赖的常见手段就是添加一个中间层,客户端依赖于这个中间层而不是直接依赖于服务层。这样做有几个很大的好处:
另一方面,当后端服务部署为多个独立的进程/服务器后,客户端直接访问这些服务,将是一个更加较复杂的问题,负载均衡,主备切换,灰度发布等运维功能更难操作,除此之外,还有下面两个比较重要的问题:
所以,为了解决客户端对后端服务层的依赖,并且解决后端服务太多以后引起的问题,我们需要在客户端和后端服务层之间添加一个中间层,这个中间层就是我们的服务代理层,也就是我们后面说的服务网关代理(WebAPIGatewayProxy),它作为我们所有Web访问的入口站点,这就是上图所示的WebPort。有了网关代理,后台所有的WebAPI都可以通过这个统一的入口提供对外服务的功能,而对于后端不同服务地址的路由,由网关代理的路由功能来实现,所以这个代理功能很像Nginx这样的反向代理,只不过,这里仅仅代理WebAPI,而不是其它Web资源。
经过上面的设计,我们发现这个架构有几个特点:
微服务最早由MartinFowler与JamesLewis于2014年共同提出,微服务架构风格是一种使用一套小服务来开发单个应用的方式途径,每个服务运行在自己的进程中,并使用轻量级机制通信,通常是HTTPAPI,这些服务基于业务能力构建,并能够通过自动化部署机制来独立部署,这些服务使用不同的编程语言实现,以及不同数据存储技术,并保持最低限度的集中式管理。所以我们这个架构是基本符合微服务思想的,它的诞生背景也是要解决其它传统单体软件项目现在遇到的问题一样的,是在比较复杂的实际需求环境下自然而然的一种需求,不过好在它没有过多的“技术债务”,所以设计实施起来比较容易。下面我们来详细看看这个架构是如何落地的。
简单来说,密码模式的步骤如下:
上面这个步骤只是说明了令牌的获取过程,也就是我们常说用户登陆成功的过程。当用户登陆成功之后,客户端得到了一个访问令牌,然后再使用这个令牌去访问资源服务器,具体说来还有如下后续过程:
下面是流程图:
仅仅从这两个词的名词定义可能不太容易分辨,我们用实际的例子来说明他们的区别:
PS:大家觉得好,先点个赞支持下,谢谢!
克隆我这个DEMO到本地,下面开始我们OAuth2.0如何落地的正式讲解。
首先看到解决方案视图,先逐个做下简单说明:
编号
角色
程序集名称
说明
1
PWMIS.OAuth2.AuthorizationCenter
2
资源服务器
Demo.OAuth2.WebApi
提供API资源ASP.NETWebAPI+OWIN
3
客户端
Demo.OAuth2.ConsoleTest
控制台测试程序,测试令牌申请等功能
Demo.OAuth2.WinFormTest
4
API代理网关
Demo.OAuth2.Port
用户的Web入口,本测试程序入口ASP.NETMVC5.0
5
认证服务器
Demo.OAuth2.IdentityServer
Demo.OAuth2.Mvc
6
其它
PWMIS.OAuth2.Tools
提供OAuth2.0协议访问的一些有用的工具类
然后就可以看到下面的界面:
点击确定,进入了业务操作页面,如下图:
如果能够看到这个页面,我们的OAuth2.0演示程序就成功了。下面我们来看看各个程序集项目的构建过程。
首先添加一个MVC5项目PWMIS.OAuth2.AuthorizationCenter,然后添加如下包引用:
Microsoft.AspNet.MvcMicrosoft.Owin.Host.SystemWebMicrosoft.Owin.Security.OAuthMicrosoft.Owin.Security.Cookies然后在项目根目录下添加一个OWin的启动类Startup:
varidentityRepository=IdentityRepositoryFactory.CreateInstance();这里会用到一个验证客户端的接口,包括验证用户名和密码的方法一起定义了:
namespacePWMIS.OAuth2.AuthorizationCenter.Repository{publicclassAuthDbContext:DbContext{publicAuthDbContext():base("OAuth2"){}protectedoverrideboolCheckAllTableExists(){base.CheckTableExists
看到OAuthClient.cs文件的OAuthClient类的GetToken方法:
首先看到PWMIS.OAuth2.Tools.TokenManager文件的CreateToken生成令牌的方法:
然后看TakeToken方法,它首先尝试获取一个当前用户的令牌,如果令牌快过期,就尝试刷新令牌:
项目Demo.OAuth2.Port在本解决方案里面有3个作用:
这里我们着重讲解第3点功能,网关代理功能另外详细介绍。
如果是ASP.NETMVC5,我们可以拦截API请求的DelegatingHandler处理器,我们定义一个AuthenticationHandler类继承它来处理:
下面我们以某个比较老的管理系统来举例,它基于ASP.NETMVC3定制开发,扩展了一些底层的东西,所以没法升级到兼容支持ASP.NETWebAPIMVC5。
首先我们来看看代理的配置文件ProxyServer.config:
需要注意的是,路由项目的匹配不是匹配到该项目后就结束,而是会尝试匹配所有路由项目,进行多次匹配和替换,直到不能匹配为止,所以代理配置文件对于路由项目的顺序很重要,也不宜编写太多的路由配置项目。
目前,支持的路由项目的API前缀地址,有/api,/api2,api3/三大种,更多的匹配前缀需要修改代理服务的源码。
首先定义一个拦截器ProxyRequestHandler,它继承自WebAPI的DelegatingHandler,可以在底层拦截对API调用的消息,在重载的SendAsync方法内实现访问请求的处理:
publicclassProxyRequestHandler:DelegatingHandler{///
if(!matched){returnawaitbase.SendAsync(request,cancellationToken);}如果匹配到,那么进入GetNewResponseMessage方法进一步处理请求:
最后,就是我们真正的代理请求访问的方法ProxyReuqest了:
privateasyncTask
privateHttpClientGetHttpClient(UribaseAddress,HttpRequestMessagerequest,boolsessionRequired){if(sessionRequired){//注意:应该每个浏览器客户端一个HttpClient实例,这样才可以保证各自的会话不冲突varclient=getSessionHttpClient(request,baseAddress.Host);setHttpClientHeader(client,baseAddress,request);returnclient;}else{stringkey=baseAddress.ToString();if(dictHttpClient.ContainsKey(key)){returndictHttpClient[key];}else{lock(sync_obj){if(dictHttpClient.ContainsKey(key)){returndictHttpClient[key];}else{varclient=getNoneSessionHttpClient(request,baseAddress.Host);setHttpClientHeader(client,baseAddress,request);dictHttpClient.Add(key,client);returnclient;}}}}}上面的代码,根据URL请求的基础地址(被代理访问的目标主机地址)为字典的键,获取或者添加一个HttpClient对象,创建新HttpClient对象使用下面这个方法:
privateHttpClientgetNoneSessionHttpClient(HttpRequestMessagerequest,stringhost){HttpClientclient=newHttpClient();client.DefaultRequestHeaders.Connection.Add("keep-alive");returnclient;}这个方法主要作用是为新创建的HttpClient对象添加长连接请求标头。
另外,还需要解决DNS缓存问题,在ServicePointManager类进行设定,每一分钟刷新一次。
//定期清除DNS缓存varsp=ServicePointManager.FindServicePoint(baseAddress);sp.ConnectionLeaseTimeout=60*1000;//1分钟最后,修改默认的并发连接数为512,如下:
我们的入口网站(WebPort)一般都是支持会话的,有时候,需要在资源服务器或者认证服务器保持用户的会话状态,提供有状态的服务。前面我们说明实现代理访问使用了HttpClient对象,默认情况下同一个HttpClient对象与服务器交互是可以保持会话状态的,在代理请求的时候,将原始请求的Cookie值附加到代理请求的HttpCliet的CookieContainer对象即可。然而为了优化HttpClient的访问效率,我们对同一个被代理访问的资源服务器使用了同一个HttpClient对象,而不是对同一个浏览器的请求使用同一个HttpClient对象。实际上,并不需要这样做,只要确保当前HttpClient对象的Cookie能够发送到被代理的资源服务器即可,针对每个请求线程创建一个HttpClient对象实例是最安全的做法。
回到前面的GetHttpClient方法,看到下面代码:
if(sessionRequired){//注意:应该每个浏览器客户端一个HttpClient实例,这样才可以保证各自的会话不冲突varclient=getSessionHttpClient(request,baseAddress.Host);setHttpClientHeader(client,baseAddress,request);returnclient;}在getSessionHttpClient方法中,将原始请求的Cookie值一一复制到新的请求上去。CookieContainer里面的Cookie跟HttpRequestMessage请求头里面的Cookie根本就不是一回事,需要一个个的转换:
privateHttpClientgetSessionHttpClient(HttpRequestMessagerequest,stringhost){CookieContainercc=newCookieContainer();HttpClientHandlerhandler=newHttpClientHandler();handler.CookieContainer=cc;handler.UseCookies=true;HttpClientclient=newHttpClient(handler);//复制CookiesvarheaderCookies=request.Headers.GetCookies();foreach(varchvinheaderCookies){foreach(variteminchv.Cookies){Cookiecookie=newCookie(item.Name,item.Value);cookie.Domain=host;cc.Add(cookie);}}returnclient;}我们知道对于ASP.NET来说,服务器支持会话是因为服务器给客户端发送了一个名字为ASP.NET_SessionId的Cookie,只要这个Cookie发送过去了,被代理的服务器就不会再为“客户端”生成这个会话ID,并且会使用这个会话ID,在当前服务器(资源服务器)维护自己的会话状态。
注意:虽然WebPort跟被代理的服务器使用了一样的SessionID,但它们的会话状态并不相同,只不过看起来访问两个服务器的客户端(浏览器)是同一个而已。
这样,我们就间接的实现了资源服务器“会话状态”的代理。
默认情况下,我们并不会对所有请求使用有会话状态的代理,而是使用优化了连接请求的代理,如果需要启用代理会话状态的功能需要设置SessionRequired为true,具体请参考下面的【5.2,代理获取验证码的API】
\PWMIS.OAuth2\Demo.OAuth2.Port\Views\Logon\Index.cshtml
由于验证服务器(地址:【localhost:50697】)验证码功能是使用Session存储的,所以需要在代理配置文件(ProxyServer.config)中的代理路由配置项目添加会话支持,
指定SessionRequired属性为true,如下所示:
看到示例的认证服务器项目Demo.OAuth2.Mvc,在控制器LoginController添加一个Action,随机生成6位数字验证码,然后存储在当前服务器的会话状态中:
在OAuthClient工具类中,我们封装了一个可以包含验证码的请求生成验证码的方法:
如果你打算在你的软件项目中也使用OAuth2.0的密码认证方案,PWMIS.OAuth2.0可以作为一个样例解决方案,你可以直接使用,做好API的代理配置即可,不论你的API是不是.NET开发的。
PWMIS.OAuth2.0是一个开源项目,可以直接在你项目使用。如果有问题,请在本文回帖留言,感谢大家支持!