# **网关** ## 传统的ESB企业总线 **满足ESB的主要场景**​ >一旦某个企业决定要走企业服务总线路线,他们通常会陷入几大厂商的巨型软件套件的复杂性(及成本)的深潭之中,这些厂商试图将ESB模式等同于某个特定产品。而这些套件,虽然它们往往是比较全面的适配器,但其功能却超出了一般企业的需要。当敏捷和客户需求的响应速度作为架构目标时,答案绝不会是需要花一个星期去安装的解决方案。它们是重量级的、适配器驱动的解决方案,通常需要数月才能建成具有一定价值的规模,而且协议映射和数据格式往往还需要许多编码。此外,它们主要关注联通性,而很少关心安全,性能等因素。 API网关产品随着微服务概念出现并趋于成熟之后 ,用户就会意识到这种轻量级、方便易用、廉价的替代方案能够实现更多的传统ESB平台所提供的功能,而已经在实施的传统ESB并非都取得了成功。此时一个需求分析问题就出现了,API网关能否满足我的需求?如果可以的话,为什么还要继续沿着原先那条更慢、更昂贵、更费劲的道路向相同的目标前进呢?API网关更易于配置,可易于扩展,而且从运维的角度看它更便于管理。它能为安全架构师、应用开发者、网络管理员和运维人员都带来价值;更重要的是,任何痛苦都痛不过口袋里缺钱。 ![](https://img.kancloud.cn/68/88/6888e32a4d6f4b31332dc02999bcdc2c_613x778.png) **应用形态的发展趋势** >随着苹果公司2008年发布了App Store,拥有应用商店的智能手机很快就成为了主流趋势。移动应用的开发变得司空见惯,但是应用程序本身能做的事情很少,它们需要与世界各地的数据进行交互。如果有简单高效的方法来访问其他企业提供的数据和功能,开发人员就可以开发出更强大的应用程序。除此之外,运行在浏览器中基于HTML/JavaScript开发的功能强大的单页应用,甚至是传统的MVC架构的服务器端web应用程序如今的运行形态也是不同的,这些应用通常会被部署到能够弹性伸缩的云服务器上(Amazon Elastic Beanstalk),服务器内部不会存储数据和状态,几乎每一个重要的用户交互都需要由后端的API请求来支撑。 **ESB无法支撑当今应用形态** >企业早期的web应用体系架构要么简单的数据库访问类应用,要么是通过ESB进行服务集成的面向服务的架构。如果基于ESB的面向服务的架构,无论是soap类型服务还是其他标准的服务,它们最初的主要消费者都来源于企业内部的系统,通常数据是以XML形式进行传输,对于企业内部的高速网络的影响很小,同时由于内部系统之间都是相互信任不需要考虑太多的安全因素,所以许多架构师将ESB作为面向服务架构最合理高效的最佳实践。但是对于移动应用、具有弹性云能力的高度可伸缩的web应用来说,ESB并不能满足它们的需求,因为ESB设计初衷并不是为了适应这种高并发、高性能、频繁和服务器进行交互的应用。并且大多数场景,这些应用都运行在企业的安全防线之外,需要将企业的服务通过公共媒体(如Internet)进行公开,这就意味着企业需要关心从各种各样的有效负载攻击(如XML威胁)到影响吞吐量和连接的拒绝服务攻击等一系列新的安全风险,同时还必须提供一种有效可靠的手段可以认证接入的应用程序,以便有效地控制其服务访问权限。 **api-gateway** >api-gateway是一款轻量级、高性能、易扩展的基于zuul的网关产品,提供API的统一管理服务、涵盖API发布、管理、运维的全生命周期管理。对内辅助用户简单、快速、低成本、低风险的实现微服务聚合、前后端分离、系统集成等功能;对外面向合作伙伴、开发者开放服务。通过使用API-Gateway,我们能快速帮助用户实现传统ESB面临的主要场景,又能满足新型业务场景(移动应用等)所需的高性能、安全、可靠等要求。 ![](https://img.kancloud.cn/31/6d/316d77a699b9df478e8741d809b4fa9b_650x287.png) ## 通用网关设计 ![](https://img.kancloud.cn/1e/62/1e628e93dd065657ab37b7547fb36ebc_1013x680.png) ## 软负载ZUUL ![](https://img.kancloud.cn/7a/d3/7ad38b1114e34924cfe3b392383b894b_1032x574.png) ## api-gateway在项目中的位置 ![](https://img.kancloud.cn/fe/7a/fe7ac9108d57ec9d64e8d7d55fe5763b_1006x660.png) ## API Gateway 作用 * 简化客户端调用复杂度 在微服务架构模式下后端服务的实例数一般是动态的,对于客户端而言,很难发现动态改变的服务实例的访问地址信息。因此在基于微服务的项目中为了简化前端的调用逻辑,通常会引入API Gateway作为轻量级网关,同时API Gateway中也会实现相关的认证逻辑从而简化内部服务之间相互调用的复杂度。 ![](https://img.kancloud.cn/be/30/be3023b48d1850fc3dc786eb6ef0a4e0_401x346.png) * 数据裁剪以及聚合 通常而言不同的客户端在显示时对于数据的需求是不一致的,比如手机端或者Web端又或者在低延迟的网络环境或者高延迟的网络环境。因此为了优化客户端的使用体验,API Gateway可以对通用性的响应数据进行裁剪以适应不同客户端的使用需求,同时还可以将多个API调用逻辑进行聚合,从而减少客户端的请求数,优化客户端用户体验。 * 多渠道支持 当然我们还可以针对不同的渠道和客户端提供不同的API Gateway,对于该模式的使用由另外一个大家熟知的方式叫Backend for front-end,在Backend for front-end模式当中,我们可以针对不同的客户端分别创建其BFF。 ![](https://img.kancloud.cn/37/7e/377e2cebc23913a3e3d136450f170545_688x399.png) * 遗留系统的微服务改造 对于遗留系统而言进行微服务改造通常是由于原有的系统存在或多或少的问题,比如技术债务,代码质量,可维护性,可扩展性等等。API Gateway的模式同样适用于这一类遗留系统的改造,通过微服务化的改造逐步实现对原有系统中的问题的修复,从而提升对于原有业务相应力的提升。通过引入抽象层,逐步使用新的实现替换旧的实现。 ![](https://img.kancloud.cn/0b/1a/0b1a6ddf8ef97aea17d8b96af9cb75c1_645x361.png) 在Spring Cloud体系中,Spring Cloud Zuul就是提供负载均衡,反向代理,权限认证的一个API Gateway。 # api-gateway代码分析 ## 开启ZUUL ![](https://box.kancloud.cn/d0d1f3b21523120ba677a274168f44e7_1731x534.png) ## 网关的2层超时调优 ``` #设置最大容错超时时间 hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 90000 #设置最大超时时间 ribbon: eager-load: enabled: true ServerListRefreshInterval: 10 #刷新服务列表源的间隔时间 httpclient: enabled: false okhttp: enabled: true ReadTimeout: 90000 ConnectTimeout: 90000 OkToRetryOnAllOperations: true MaxAutoRetries: 1 MaxAutoRetriesNextServer: 1 ``` ![](https://img.kancloud.cn/39/37/3937fb3c071e84009e7e7e1d70f75cd1_1032x459.png) ## 网关自定义过滤器 ![](https://img.kancloud.cn/cc/66/cc6672a18ecfb8bd0d5a0e370daa6bfc_606x609.png) * brave.servlet.TracingFilter :生成traceId * com.open.capacity.common.filter.TraceContextFilter:传递traceId * com.open.capacity.client.filter.AccessFilter:传递token * com.open.capacity.client.filter.RequestFilter::传递traceId * com.open.capacity.client.filter.ResponseFilter:响应头增加traceId ## api-gateway 构建资源服务器 ``` <!-- 资源服务器 --> <dependency> <groupId>com.open.capacity</groupId> <artifactId>security-core</artifactId> <version>${core.version}</version> </dependency> ``` [security-core源码分析](https://www.kancloud.cn/owenwangwen/open-capacity-platform/1048267) ## 资源服务器认证处理流程图 ![](https://box.kancloud.cn/7eea410bb58d317640e825e7a3e7c20e_588x703.png) ## 网关白名单 ``` security: oauth2: ignored: /test163/** , /api-auth/** , /doc.html ,/test111 ,/api-user/users-anon/login, /api-user/users/save, /user-center/users-anon/login,/document.html,**/v2/api-docs,/oauth/** ,/login.html ,/user/login,/**/**.css ,/**/**.js ,/getVersion token: store: type: redis ``` ## 认证核心代码 ``` public Authentication authenticate(Authentication authentication) throws AuthenticationException { if (authentication == null) { throw new InvalidTokenException("Invalid token (token not found)"); } String token = (String) authentication.getPrincipal(); OAuth2Authentication auth = tokenServices.loadAuthentication(token); if (auth == null) { throw new InvalidTokenException("Invalid token: " + token); } Collection<String> resourceIds = auth.getOAuth2Request().getResourceIds(); if (resourceId != null && resourceIds != null && !resourceIds.isEmpty() && !resourceIds.contains(resourceId)) { throw new OAuth2AccessDeniedException("Invalid token does not contain resource id (" + resourceId + ")"); } checkClientDetails(auth); if (authentication.getDetails() instanceof OAuth2AuthenticationDetails) { OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) authentication.getDetails(); // Guard against a cached copy of the same details if (!details.equals(auth.getDetails())) { // Preserve the authentication details from the one loaded by token services details.setDecodedDetails(auth.getDetails()); } } auth.setDetails(authentication.getDetails()); auth.setAuthenticated(true); return auth; ``` ### 授权流程 ![](https://box.kancloud.cn/53e8ffda6d12e4d48acdc6231bdafc4e_1408x773.png) #### 启用授权 ![](https://box.kancloud.cn/feb228e1dd210009a0d4f57779cdb44b_1792x355.png) ``` @Bean public OAuth2WebSecurityExpressionHandler oAuth2WebSecurityExpressionHandler(ApplicationContext applicationContext) { OAuth2WebSecurityExpressionHandler expressionHandler = new OAuth2WebSecurityExpressionHandler(); expressionHandler.setApplicationContext(applicationContext); return expressionHandler; } ``` # 网关api权限设计 ![](https://img.kancloud.cn/c1/68/c1685025219f816bb33f1840592b5599_855x351.png) 相同用户,不同应用的权限隔离 客户端模式 : 客户端A 申请的token ,可以访问/api-user/menu/current , 客户端B 申请的token,不让访问/api-user/menu/current 密码模式: 客户端模式 : 客户端A admin用户 申请的token ,可以访问/api-user/menu/current , 客户端B admin用户 申请的token,不让访问/api-user/menu/current 参考issue:[https://gitee.com/owenwangwen/open-capacity-platform/issues/IRG23]() 网关是否开启基于应用隔离,代码注释了,只是基于token的合法性校验,按建议开启是否启用api接口服务权限 OpenAuthorizeConfigManager > 游乐场买了通票,有些地方可以随便玩,有些地方另外 > 单独校验买票 > config.anyRequest().authenticated() ; > //这种通票,token校验正确访问 > config.anyRequest().access("@rbacService.hasPermission(request, authentication)"); //这种另外 > 单独校验,适用于网关对api权限校验 ``` ~~~ /** * @author 作者 owen E-mail: 624191343@qq.com * @version 创建时间:2018年2月1日 下午9:50:27 * 类说明 */ @Component public class OpenAuthorizeConfigManager implements AuthorizeConfigManager { @Autowired private List<AuthorizeConfigProvider> authorizeConfigProviders; @Autowired(required=false) private RbacService rbacService ; /* (non-Javadoc) * @see com.imooc.security.core.authorize.AuthorizeConfigManager#config(org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer.ExpressionInterceptUrlRegistry) */ @Override public void config(ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry config) { //设置访问 for (AuthorizeConfigProvider authorizeConfigProvider : authorizeConfigProviders) { authorizeConfigProvider.config(config) ; } //token正确登录 config.anyRequest().authenticated() ; if(rbacService!=null){ // 放开则全部可以不需要认证访问 config .anyRequest() .access("@rbacService.hasPermission(request, authentication)"); } } } ~~~ ``` 通过clientID隔离服务权限 ![](https://img.kancloud.cn/ad/52/ad52f219fbe2312c95e970d91b7975d9_564x443.png) ``` ~~~ //package com.open.capacity.client.service; ///** // * // */ // //import java.util.Iterator; //import java.util.List; //import java.util.Map; // //import javax.annotation.Resource; //import javax.servlet.http.HttpServletRequest; // //import org.springframework.security.core.Authentication; //import org.springframework.security.core.context.SecurityContextHolder; //import org.springframework.security.oauth2.provider.OAuth2Authentication; //import org.springframework.stereotype.Service; //import org.springframework.util.AntPathMatcher; // //import com.open.capacity.client.dao.SysClientDao; //import com.open.capacity.client.dao.SysServiceDao; //import com.open.capacity.client.oauth2.service.RbacService; // ///** // * API 级别权限认证 // * // * @author 作者 owen E-mail: 624191343@qq.com // * @version 创建时间:2017年12月4日 下午5:32:29 // * 类说明 // */ // //@Service("rbacService") //public class RbacServiceImpl implements RbacService { // // @Resource // private SysServiceDao sysServiceDao; // // @Resource // private SysClientDao sysClientDao; // // private AntPathMatcher antPathMatcher = new AntPathMatcher(); // // /** // * @param request HttpServletRequest // * @param authentication 认证信息 // * @return 是否有权限 // */ // @Override // public boolean hasPermission(HttpServletRequest request, Authentication authentication) { // // Authentication user = SecurityContextHolder.getContext() // .getAuthentication(); // // //TODO 目前都是true // boolean hasPermission = false; // // if (user != null) { // // if (user instanceof OAuth2Authentication) { // // OAuth2Authentication athentication = (OAuth2Authentication) user; // // String clientId = athentication.getOAuth2Request().getClientId(); // // Map map = sysClientDao.getClient(clientId); // // if (map == null) { // return false; // } else { // List<Map> list = sysServiceDao.listByClientId(Long.valueOf(String.valueOf(map.get("id")))); // ; // // for (Iterator<Map> it = list.iterator(); it.hasNext(); ) { // Map temp = it.next(); // // if (antPathMatcher.match( String.valueOf(temp.get("path")),request.getRequestURI())) { // return true; // } // } // return false; // } // // // } // // } // // // // // // // return hasPermission; // } // //} ~~~ ``` ![](https://img.kancloud.cn/b6/83/b683a46fc26574701dfa977e6fe3f243_916x418.png) # 源码分析 ## zuul内部代码 ![](https://img.kancloud.cn/de/dc/dedc89358d3836b8e5255e4136eaccfa_1324x754.png) ![](https://img.kancloud.cn/6f/2d/6f2d0acd62574814c9303f4414985edb_1310x361.png) ![](https://img.kancloud.cn/f0/6f/f06f9d5fce900fbac3d437e0331321cd_1244x652.png) 参考:https://www.jianshu.com/p/295e51bc1518 ## zuul基于eureka的服务发现路由 org.springframework.cloud.netflix.zuul.filters.discovery.DiscoveryClientRouteLocator ![](https://box.kancloud.cn/f750026dbf93181e86d923c36858d5f3_1515x744.png) ## 生产软负载NGINX构建ZUUL集群 ![](https://img.kancloud.cn/9d/1e/9d1e85073fa1ba4ed17e5587cede9e1f_632x349.png) ## pom核心依赖 ![](https://box.kancloud.cn/23e4d4f05c60def0982a1189c63340f2_1754x685.png)