前言
在前后端分离的项目中,前端调用后端接口时可能会遇到跨域问题,此时,我们脑袋中会冒出很多问号:
- 什么是跨域?
- 为什么会有跨域?
- 如何解决跨域?
不要害怕,下面我们将解决他们。
在企业环境中,众多部门各自开发的认证系统,对用户来说,如果每个系统都需要独立登录,无疑增加了使用的复杂性。因此,如果有一把“万能钥匙”,能够让用户无需重复登录,直接访问每个系统,那将大大提升效率和用户体验。
而本文要介绍的 SSO 就像一把万能钥匙,系统接入 SSO 后,用户只需登录一次,就可以访问所有系统,极大地简化了用户的操作步骤,提高了工作效率。
单点登录( Single Sign-On , 简称 SSO )是目前比较流行的服务于企业业务整合的解决方案之一, SSO 使得在多个应用系统中,用户只需要登录一次 就可以访问所有相互信任的应用系统。

在一个 SSO 系统里,一般会定义两类角色:
如上图流程说明,SSO 实现模式一般包括以下三个原则:
SSO 的实现一般有 CAS 和 OAuth2 两种:

CAS 和 OAuth2 都可以用于实现 SSO,但它们的重点和实现方式略有不同。
CAS 专注于提供统一登录服务,一般用于企业内部统一各个子系统的登录,而OAuth2 则更关注授权方面的问题,一般第三方系统统一外部登录。

Oauth2 一般应用在各种社交应用的登录中,以 Google 为例,其交互流程如下:
1、访问 Client,Clilent 把用户redirect 到 Google(带着 SP 的信息)
2、用户登录Google(如果已登陆,这一步可以跳过)
3、Google 把用户redirect回Clilent(带着一个授权码code )
4、Clilent 用这个授权码在后台向Google 换取 AccessToken,根据此 Token 可获取用户的信息(唯一ID、email之类的)
5、Clilent根据从Google获得的用户信息让用户继续使用。
CAS 包括两部分:
基础模式 SSO 访问流程主要有以下步骤:

1.用户通过浏览器访问应用1,应用1发现用户没有登录,于是携带上一个service参数进行重定向,让用户去CAS Server上登录。
2.浏览器自动重定向到CAS Server上,CAS Server获取用户Cookie中携带的TGC, 去校验用户是否已经登录
a. 如果未登录,则重定向到CASServer的登录页面,用户输入用户名/密码,CAS
Server会生成票据TGT,并且根据TGT签发一个票据ST,再将TGC放在用户的Cookie中,完成身份校验
b.如果已经登录,则完成身份校验(此时CASServer可以根据用户的TGC找到TGT,进而获取用户的信息)
3.CASServer完成身份校验之后,会将ST拼接在service中,302重定向返回应用 1。此过程中浏览器将首先将TGC存在Cookie中,然后携带上ST重定向到应用1。4.应用1收到浏览器传来的S之后,拿去CASServer上校验,去判断用户的登录状态,如果用户登录合法,CAS Server就会返回用户信息给应用1。
5.浏览器再去访问应用2,应用2发现用户未登录,重定向到CASServer。
6.CASServer发现此时用户实际上已经登录了,于是又重定向回应用2,同时携带上ST。
7.应用2根据ST去CASServer上校验,获取用户的登录信息。
在CAS认证成功后,会生成三种类型的票据:


上图流程简单说明:
在整个登录过程中,浏览器分别和 CAS Server、应用1、应用2 建立了会话,其中,和 CAS Server 建立的会话称之为全局会话,和应用1、应用2 建立的会话称之为局部会话;一旦局部会话成功建立,以后用户再去访问应用1、应用2 就不会经过 CAS Server 了。

CAS 的安全性仅仅依赖于 SSL 。使用的是 secure cookie 。
对于一个 CAS 用户来说,最重要是要保护它的 TGC ,如果 TGC 不慎被 CAS Server 以外的实体获得, Hacker 能够找到该 TGC ,然后冒充 CAS 用户访问 所有 授权资源。 PGT 的角色跟 TGC 是一样的。
从基础模式可以看出, TGC 是 CAS Server 通过 SSL 方式发送给终端用户,因此,要截取 TGC 难度非常大,从而确保 CAS 的安全性。
TGT 的存活周期默认为 120 分钟。
ST ( Service Ticket )是通过 Http 传送的,因此网络中的其他人可以 Sniffer 到其他人的 Ticket 。 CAS 通过以下几方面来使 ST 变得更加安全(事实上都是可以配置的):
当客户端发出一些对微服务获取资源的请求到后端,这些请求将通过 FS、 Nginx 等设施的路由和负载均衡分配并转发到各个不同的服务实例上。为了让这些设施能够正确路由与分发请求,运维人员需要手工维护这些路由规则与服务实例列表, 当有实例增减或是 IP 地址变动等情况发生的时候,也需要手工地去同步修改这些信息以保持实例信息与中间件的一致。当系统规模不断增大时,这些看似简单的维护任务会变得越来越麻烦,且配置出错概率也会增加。
很显然,上述做法并不可取,因此,我们需要一套机制来有效降低维护路由规则与服务实例列表的难度。
对于某些服务的权限校验(如用户登录状态的校验),若突然发现校验逻辑有个 BUG 需要修复,或者需要对其做一些扩展和优化,此时就不得不去每个应用里修改这些逻辑,这样的修改不仅会引起开发入员的抱怨,更会加重测试人员的负担。所以,我们也需要一套机制,能够很好地解决微服务架构中,对于微服务接口访问时各前置校验的冗余问题。
这时候就可以使用 API 网关,Gateway 就是一种网关。
由于后起之秀 Gateway 的诞生,不建议在新项目中再使用 Zuul。
当客户端发出一些对微服务获取资源的请求到后端,这些请求将通过 FS、 Nginx 等设施的路由和负载均衡分配并转发到各个不同的服务实例上。为了让这些设施能够正确路由与分发请求,运维人员需要手工维护这些路由规则与服务实例列表, 当有实例增减或是 IP 地址变动等情况发生的时候,也需要手工地去同步修改这些信息以保持实例信息与中间件的一致。当系统规模不断增大时,这些看似简单的维护任务会变得越来越麻烦,且配置出错概率也会增加。
很显然,上述做法并不可取,因此,我们需要一套机制来有效降低维护路由规则与服务实例列表的难度。
对于某些服务的权限校验(如用户登录状态的校验),若突然发现校验逻辑有个 BUG 需要修复,或者需要对其做一些扩展和优化,此时就不得不去每个应用里修改这些逻辑,这样的修改不仅会引起开发入员的抱怨,更会加重测试人员的负担。所以,我们也需要一套机制,能够很好地解决微服务架构中,对于微服务接口访问时各前置校验的冗余问题。
这时候就可以使用 API 网关,Zuul 就是一种网关。
在前面的例子中,我们使用了 Ribbon 的负载均衡功能,大大优化了远程调用时的代码:1
2String url = "http://user-service/user/" + id;
return restTemplate.getForObject(url, String.class);
但是,我们以后可能需要编写类似的重复代码,格式基本相同,无非参数不一样,有没有更优雅的方式来对这些代码再次优化呢?
而且,我们使用了 Hystrix 的熔断功能,但将熔断的方法直接嵌套在业务代码中间,会不会显得有些杂乱?这一点又能不能优化呢?
当然可以,Feign 对 Ribbon 和 Hystrix 都进行了封装,上面 2 个都是小 case 。
Feign 基于 Netflix Feign 实现,它整合了 Ribbon 与 Hystrix , 除了提供这两者的强大功能之外,它还提供了一种声明式的 Web 服务客户端定义方式。
由于后起之秀 Sentinel 的诞生,不建议在新项目中再使用 Hystrix。
在微服务架构中,存在着那么多的服务单元,若一个单元出现故障,就很容易因依赖关系而引发故障的蔓延,最终导致整个系统的瘫痪,这样的架构相较传统架构更加不稳定。
为了解决这样的问题, 产生了熔断器等一系列的服务保护机制。
在分布式架构中, 断路器模式的作用也是类似的,当某个服务单元发生故障(类似电器发生短路) 之后, 通过熔断器的故障监控(类似熔断保险丝), 向调用方返回一个错误响应, 而不是长时间的等待。这样就不会使得线程因调用故障服务被长时间占用不释放,避免了故障在分布式系统中的蔓延。
Hystrix 就是一种熔断器,具备服务降级、服务熔断、线程和信号隔离、请求缓存、请求合并以及服务监控等强大功能.。