SSO英文全称Single SignOn,单点登录。SSO是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。它包括可以将这次主要的登录映射到其他应用中用于同一个用户的登录的机制。它是目前比较流行的企业业务整合的解决方案之一。
一、 实现机制
当用户第一次访问应用系统1的时候,因为还没有登录,会被引导到认证系统中进行登录;根据用户提供的登录信息,认证系统进行身份校验,如果通过校验,应该返回给用户一个认证的凭据--ticket;用户再访问别的应用的时候就会将这个ticket带上,作为自己认证的凭据,应用系统接受到请求之后会把ticket送到认证系统进行校验,检查ticket的合法性。如果通过校验,用户就可以在不用再次登录的情况下访问应用系统2和应用系统3了。
二、使用SSO的好处
- 方便用户 用户使用应用系统时,能够一次登录,多次使用。用户不再需要每次输入用户名称和用户密码,也不需要牢记多套用户名称和用户密码。单点登录平台能够改善用户使用应用系统的体验。
- 方便管理员 系统管理员只需要维护一套统一的用户账号,方便、简单。相比之下,系统管理员以前需要管理很多套的用户账号。每一个应用系统就有一套用户账号,不仅给管理上带来不方便,而且,也容易出现管理漏洞。
- 简化应用系统开发 开发新的应用系统时,可以直接使用单点登录平台的用户认证服务,简化开发流程。单点登录平台通过提供统一的认证平台,实现单点登录。因此,应用系统并不需要开发用户认证程序。
三、SSO实现方案
常见的跨域登录问题
1、对于同一个根域下的登录问题
如果我们的站点有不止一个业务,那么他们可能部署在不同的机器上,也往往需要不同的域名进行区分。但是所有的业务又都是依赖于一套账户体系,那么我们这时候需要通过一次登录解决所有站点的登录问题,那么我们这个时候可以使用一个最笨的方法:那就是一次登录成功,将Cookie写到根域下,那么这样所有的站点就能实现,同一个根域下的Cookie共享,自然实现了“单点登录”。
2、对于多个根域下的登录问题
如果是多个根域名,那么这种情况下上面的机制就不能实现“单点登录”了。因为之所以上面可以实现“单点登录”的效果。是因为浏览器和Http协议的支持。但是对于跨根域的站点之间进行Cookie的共享是比较复杂的。
- 方法1:登录成功之后将Cookie回写到多个域名下。
这种办法可能十分简单,你可以通过后端的response写,也可以用前端js去写,但是必须有对所有需要“单点登录”的站点进行逐一的写入。用脚想这种办法也是行不通的,因为你需要维护一个站点的列表,维护工作十分复杂,同时对于增加站点也会特别痛苦。对于Cookie的销毁也是十分复杂的,因为还是要对所有域名下的Cookie进行删除。也就是说将原来需要做的工作增加了n倍。对于小型站点这种办法是可取的。
- 方法2:jsonp
搞过前端的可能都知道用jsonp可以做跨域的请求,而我们解决的就是多个域下的统一登录的问题,好像很顺理成章的样子。但是,登录是Server端做的吧?我们在Client端做跨域的处理,这怎么看也不是很合理。同时这种办法需要很大的维护成本,每一次请求都要去固定的域下取相应的Cookie之后再做请求。想想维护有头疼。
- 方法3 :引入一个中间态的Server
这种办法算是一个简化版的SSO,实现思想也十分的“狡猾”。但是对于小网站做跨域登录的处理却十分的有用,具体思路如下:
首先,我们有两个域名要实现单点登录,同时我们需要一个中间的Server。
- 我们有一个系统域名为xulingbo.net,当我们登录的时候访问xulingbo.net/wp-login进行登录,登录成功之后将Cookie回写到xulingbo这个域名下。
- 我们还有一个系统域名为javaWeb.com,当我们访问inside-javaWeb的时候,我们没有Cookie,那么请求跳转到中间系统jump。此时需要将当前域名带到参数中便于jump校验。这个jump系统是在xulingbo域下的即:jump.xulingbo.net。这时候就能拿到之前写在xulingbo域下的Cookie。
- jump系统在收到了xulingbo域下的Cookie之后,取出xulingbo域下的Cookie,并redirect请求jump.inside-javaWeb.net,这个接口也是在jump系统中,请求后jump系统将Cookie回写到inside-javaWeb域名下,这样就实现了简易的单点登录。 如下图所示:
但是这种方式不是很灵活,对于数据传输的安全性没有保障,并且在销毁Cookie的时候无能为力,只能全部遍历的销毁。
- 方法4:基于CAS的SSO系统
CAS全称为:Central Authentication Service,是一款不错的针对web应用的单点登录框架,包括java,.net,PHP,Prel,Apache,uPortal,Ruby等。。实现的机制不算复杂但是思想十分灵巧。用CAS也可以快速实现单点登录。盗图一张说明sso单个域的登录和验证流程:
CAS主要分为CAS Client 和CAS Server ,其中Client主要是内嵌在需要SSO登录站点的拦截器或过滤器上。
- 首先浏览器向站点1发起请求。
- 站点1发现当前请求没有合法的Cookie,那么重定向到CAS Server上,也就是SSO Server。
- CAS Server展示登录界面,要求用户登录。
- 用户登录后,会写CAS Server的Cookie到浏览器,同时生产ticket,利用一个302跳转到CASClient。这样能保证用户无感知。
- CAS Client利用生成的ticket发送到CAS Server进行验证,验证通过后,站点1生成自己的Cookie并回写到用户浏览器,然后进行登录成功的跳转。
这样就能保证当前浏览器在站点1的域名下,有站点1的Cookie,同时当前浏览器也有CAS Server的Cookie。 接下来看下站点2的登录:
站点2,在进行登录时和站点1初次登录流程一致,但是在访问CAS Server的时候,由于当前浏览器已经有了CAS Server的Cookie,那么直接校验通过返回ticket。 ticket通过302跳转跳转到CAS Client上,之后的流程就和站点1是一样的了。如果此时认证失败,那么需要重新走一次登录的过程。
注意的问题: 1、CAS Server的Cookie劫持问题,如果CAS Server的Cookie被劫持掉,那么就相当于拿到了一切,所以必须要用HTTPS实现这个过程。 2、ticket的使用,ticket只能被使用一次,一次校验后立即失效。同时需要有时效性,一般5分钟。最后ticket生成规则要随机,不能被碰撞出来。 3、对于各自系统自己的Session,也可以依赖于SSO,这样就能保证所有的Session规则一致,便于集中控制。
注:运营系统也是采用这种方式处理的 基于当前现状,单点登录系统可以有一下处理
- 调用Soa服务实现登录登出 缺点:得自己实现判断用户是否登录、session是否过期;缺乏权限控制,得自己实现 优点:实现简单
- 自己在web服务实现登录登录功能 安全性偏低
- 使用Spring Security + Oauth2实现登录登录功能 优点:企业级,安全性高 提供了多种安全验证方式 可以处理复杂的权限控制
四、Spring Security 介绍
- Spring Security是什么?
Spring security 是一个强大的和高度可定制的身份验证和访问控制框架,它提供了基于javaEE的企业应有个你软件全面的安全服务。它是保护基于Spring的应用程序的事实上的标准。主要作用是“认证”和“授权”(或者访问控制)。 在身份验证层,Spring Security 的支持多种认证模式。
- 核心功能 1、认证(你是谁) 2、授权(你能干什么) 3、攻击防护(防止伪造身份)
- 什么是Spring Security验证?
- 提示用户输入用户名和密码进行登录。
- 该系统 (成功) 验证该用户名的密码正确。
- 获取该用户的环境信息 (他们的角色列表等).
- 为用户建立安全的环境。
- 用户进行,可能执行一些操作,这是潜在的保护的访问控制机制,检查所需权限,对当前的安全的环境信息的操作。
验证流程介绍
- 用户名和密码进行组合成一个实例UsernamePasswordAuthenticationToken (一个Authentication接口的实例, 我们之前看到的).
- 令牌传递到AuthenticationManager实例进行验证。
- 该AuthenticationManager完全填充Authentication实例返回成功验证。
- 安全环境是通过调用 SecurityContextHolder.getContext().setAuthentication(…), 传递到返回的验证对象建立的。
五、OAUTH2
- 名词定义
- Third-party application:第三方应用程序,本文中又称"客户端"(client)
- HTTP service:HTTP服务提供商,本文中简称"服务提供商"
- Resource Owner:资源所有者,本文中又称"用户"(user)。
- User Agent:用户代理,通常就是指浏览器。
- Authorization server:认证服务器,即服务提供商专门用来处理认证的服务器。
- Resource server:资源服务器,即服务提供商存放用户生成的资源的服务器。它与认证服务器,可以是同一台服务器,也可以是不同的服务器。
- OAuth的思路
OAuth在"客户端"与"服务提供商"之间,设置了一个授权层(authorization layer)。"客户端"不能直接登录"服务提供商",只能登录授权层,以此将用户与客户端区分开来。"客户端"登录授权层所用的令牌(token),与用户的密码不同。用户可以在登录的时候,指定授权层令牌的权限范围和有效期。 "客户端"登录授权层以后,"服务提供商"根据令牌的权限范围和有效期,向"客户端"开放用户储存的资料。
- 运行流程
(A)用户打开客户端以后,客户端要求用户给予授权。 (B)用户同意给予客户端授权。 (C)客户端使用上一步获得的授权,向认证服务器申请令牌。 (D)认证服务器对客户端进行认证以后,确认无误,同意发放令牌。 (E)客户端使用令牌,向资源服务器申请获取资源。 (F)资源服务器确认令牌无误,同意向客户端开放资源。 不难看出来,上面六个步骤之中,B是关键,即用户怎样才能给于客户端授权。有了这个授权以后,客户端就可以获取令牌,进而凭令牌获取资源。
结合上图,使用oauth2保护你的应用,可以分为简易的分为三个步骤
- 配置资源服务器 Authorization Service.
- 配置认证服务器 Resource Service.
- 配置spring security
所有获取令牌的请求都将会在Spring MVC controller endpoints中进行处理,并且访问受保护 的资源服务的处理流程将会放在标准的Spring Security请求过滤器中(filters)。 下面是配置一个授权服务必须要实现的endpoints: -AuthorizationEndpoint:用来作为请求者获得授权的服务,默认的URL是/oauth/authorize. -TokenEndpoint:用来作为请求者获得令牌(Token)的服务,默认的URL是/oauth/token.
- 客户端的授权模式
客户端必须得到用户的授权(authorization grant),才能获得令牌(access token)。OAuth 2.0定义了四种授权方式。
- 授权码模式(authorization code)
- 简化模式(implicit)
- 密码模式(resource owner password credentials)
- 客户端模式(client credentials)
授权模式讲解
- 授权码模式
授权码模式(authorization code)是功能最完整、流程最严密的授权模式。它的特点就是通过客户端的后台服务器,与"服务提供商"的认证服务器进行互动。
它的步骤如下:
(A)用户访问客户端,后者将前者导向认证服务器。
(B)用户选择是否给予客户端授权。
(C)假设用户给予授权,认证服务器将用户导向客户端事先指定的"重定向URI"(redirection URI),同时附上一个授权码。
(D)客户端收到授权码,附上早先的"重定向URI",向认证服务器申请令牌。这一步是在客户端的后台的服务器上完成的,对用户不可见。
(E)认证服务器核对了授权码和重定向URI,确认无误后,向客户端发送访问令牌(access token)和更新令牌(refresh token)。
下面是上面这些步骤所需要的参数。 A步骤中,客户端申请认证的URI,包含以下参数:
- --response_type:表示授权类型,必选项,此处的值固定为"code"
- --client_id:表示客户端的ID,必选项
- --redirect_uri:表示重定向URI,可选项
- --scope:表示申请的权限范围,可选项
- --state:表示客户端的当前状态,可以指定任意值,认证服务器会原封不动地返回这个值。
demo: GET /authorize?response_type=code&client_id=s6BhdRkqt3&state=xyz &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1 Host: server.example.com 复制代码
C步骤中,服务器回应客户端的URI,包含以下参数: --code:表示授权码,必选项。该码的有效期应该很短,通常设为10分钟,客户端只能使用该码一次,否则会被授权服务器拒绝。该码与客户端ID和重定向URI,是一一对应关系。 --state:如果客户端的请求中包含这个参数,认证服务器的回应也必须一模一样包含这个参数。
HTTP/1.1 302 Found Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA &state=xyz 复制代码
D步骤中,客户端向认证服务器申请令牌的HTTP请求,包含以下参数:
- --grant_type:表示使用的授权模式,必选项,此处的值固定为"authorization_code"。
- --code:表示上一步获得的授权码,必选项。
- --redirect_uri:表示重定向URI,必选项,且必须与A步骤中的该参数值保持一致。
- --client_id:表示客户端ID,必选项。
E步骤中,认证服务器发送的HTTP回复,包含以下参数:
- --access_token:表示访问令牌,必选项。
- --token_type:表示令牌类型,该值大小写不敏感,必选项,可以是bearer类型或mac类型。
- --expires_in:表示过期时间,单位为秒。如果省略该参数,必须其他方式设置过期时间。
- --refresh_token:表示更新令牌,用来获取下一次的访问令牌,可选项。
- --scope:表示权限范围,如果与客户端申请的范围一致,此项可省略。
常见的第三方登录也是采用这种方法,先请求得到code,然后再通过code去获取令牌。
- 简化模式 简化模式(implicit grant type)不通过第三方应用程序的服务器,直接在浏览器中向认证服务器申请令牌,跳过了"授权码"这个步骤,因此得名。所有步骤在浏览器中完成,令牌对访问者是可见的,且客户端不需要认证。
它的步骤如下: (A)客户端将用户导向认证服务器。
(B)用户决定是否给于客户端授权。
(C)假设用户给予授权,认证服务器将用户导向客户端指定的"重定向URI",并在URI的Hash部分包含了访问令牌。
(D)浏览器向资源服务器发出请求,其中不包括上一步收到的Hash值。
(E)资源服务器返回一个网页,其中包含的代码可以获取Hash值中的令牌。
(F)浏览器执行上一步获得的脚本,提取出令牌。
(G)浏览器将令牌发给客户端。
下面是上面这些步骤所需要的参数。
A步骤中,客户端发出的HTTP请求,包含以下参数:
- -response_type:表示授权类型,此处的值固定为"token",必选项。
- -client_id:表示客户端的ID,必选项。
- -redirect_uri:表示重定向的URI,可选项。
- -scope:表示权限范围,可选项。
- -state:表示客户端的当前状态,可以指定任意值,认证服务器会原封不动地返回这个值。
下面是一个例子:
GET /authorize?response_type=token&client_id=s6BhdRkqt3&state=xyz &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1 Host: server.example.com 复制代码
C步骤中,认证服务器回应客户端的URI,包含以下参数:
- -access_token:表示访问令牌,必选项。
- -token_type:表示令牌类型,该值大小写不敏感,必选项。
- -expires_in:表示过期时间,单位为秒。如果省略该参数,必须其他方式设置过期时间。
- -scope:表示权限范围,如果与客户端申请的范围一致,此项可省略。
- -state:如果客户端的请求中包含这个参数,认证服务器的回应也必须一模一样包含这个参数。
下面是一个例子:
HTTP/1.1 302 Found Location: http://example.com/cb#access_token=2YotnFZFEjr1zCsicMWpAA &state=xyz&token_type=example&expires_in=3600 复制代码
- 密码模式
密码模式(Resource Owner Password Credentials Grant)中,用户向客户端提供自己的用户名和密码。客户端使用这些信息,向"服务商提供商"索要授权。 在这种模式中,用户必须把自己的密码给客户端,但是客户端不得储存密码。这通常用在用户对客户端高度信任的情况下,比如客户端是操作系统的一部分,或者由一个著名公司出品。而认证服务器只有在其他授权模式无法执行的情况下,才能考虑使用这种模式。
它的步骤如下:
(A)用户向客户端提供用户名和密码。
(B)客户端将用户名和密码发给认证服务器,向后者请求令牌。
(C)认证服务器确认无误后,向客户端提供访问令牌。
B步骤中,客户端发出的HTTP请求,包含以下参数:
-grant_type:表示授权类型,此处的值固定为"password",必选项。
-username:表示用户名,必选项。
-password:表示用户的密码,必选项。
-scope:表示权限范围,可选项。
下面是一个例子:
POST /token HTTP/1.1 Host: server.example.com Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW Content-Type: application/x-www-form-urlencoded grant_type=password&username=johndoe&password=A3ddj3w 复制代码
C步骤中,认证服务器向客户端发送访问令牌,下面是一个例子。
HTTP/1.1 200 OK Content-Type: application/json;charset=UTF-8 Cache-Control: no-store Pragma: no-cache { "access_token":"2YotnFZFEjr1zCsicMWpAA", "token_type":"example", "expires_in":3600, "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA", "example_parameter":"example_value" } 复制代码
其它的认证介绍参考下面参考地址。
- 客户端模式 客户端模式(Client Credentials Grant)指客户端以自己的名义,而不是以用户的名义,向"服务提供商"进行认证。严格地说,客户端模式并不属于OAuth框架所要解决的问题。在这种模式中,用户直接向客户端注册,客户端以自己的名义要求"服务提供商"提供服务,其实不存在授权问题。
它的步骤如下:
(A)客户端向认证服务器进行身份认证,并要求一个访问令牌。
(B)认证服务器确认无误后,向客户端提供访问令牌。
A步骤中,客户端发出的HTTP请求,包含以下参数:
-granttype:表示授权类型,此处的值固定为"clientcredentials",必选项。
-scope:表示权限范围,可选项。
POST /token HTTP/1.1 Host: server.example.com Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW Content-Type: application/x-www-form-urlencoded grant_type=client_credentials 复制代码
认证服务器必须以某种方式,验证客户端身份。 B步骤中,认证服务器向客户端发送访问令牌,下面是一个例子。
HTTP/1.1 200 OK Content-Type: application/json;charset=UTF-8 Cache-Control: no-store Pragma: no-cache { "access_token":"2YotnFZFEjr1zCsicMWpAA", "token_type":"example", "expires_in":3600, "example_parameter":"example_value" } 复制代码
附上我自己写的一个spring-security+oauth2项目,可参考使用,反正我自己的项目用上这个了。github.com/jiezaizone/…
另一种单点登录方式:Keycloak单点登录