微服务实战(十五)看完不信你还不懂单点登录(SSO,CAS,OAuth,Jwt,Spring Security,Shiro)

前言 本篇主要就单点登录的概念、各类相关技术(比如CAS、OAUTH、JWT、SpringSecurity)的用途以及思路进行整体介绍。
单点登录(SSO)
单点登录,顾名思义,就是在一个点登录。微服务实战(十五)看完不信你还不懂单点登录(SSO,CAS,OAuth,Jwt,Spring Security,Shiro)
文章图片

我们看看它的定义,单点登录SSO(Single Sign On ,以前我总记成 SOS)。百科上的解释是:“多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。”
其实我感觉这不够精准,并没有把“单点”解释到位,所谓单点登录,应是在多个应用系统环境下,用户只需要在统一的一个单点进行登录,然后就可以畅通无阻地访问该应用系统群组里的每个应用。

举个例子,像我们熟知的购物网站,虽然给人们的感觉这就是“一个”网站,但是实际上,它是一个系统集群,每个频道都是一个独立的系统。
微服务实战(十五)看完不信你还不懂单点登录(SSO,CAS,OAuth,Jwt,Spring Security,Shiro)
文章图片

而每个系统的登录入口,其实都是指向同一个地址,而且只要在这个地址登录过后,然后再访问这个网站的下属系统(比如在金融模块页面登录后,再进入首页或者 秒杀、优惠券等系统),你都已经是已登录状态咯!(如下图)
微服务实战(十五)看完不信你还不懂单点登录(SSO,CAS,OAuth,Jwt,Spring Security,Shiro)
文章图片

概括:单点登录不是一种具体的技术,而是一种问题解决方案,即在多个相关而又独立的系统环境下,如何避免每个系统节点都要登录;而解决方法各有不同,有多种技术实现,像CAS 就可以视作为实现单点登录的一种技术框架。

CAS(Central Authentication Service)
https://apereo.github.io/cas
起源:CAS 是 Yale 大学发起的一个开源项目,在 2004 年 12 月正式成为 JA-SIG 的项目。
CAS是实现单点登录的技术框架,由客户端(CAS Client)和服务端(CAS Server)两部分组成,服务端部署后变身成为一个“登录页面”(当然还有它背后的逻辑处理);客户端要内嵌到具体应用中,用于和服务端的“登录页面”打交道。
多个系统通过CAS搭建好单点登录后,用户只需要在CAS的登录界面(界面和登录逻辑可以在CAS中自定义)登录,即可在其他应用系统里免登录进入。
在CAS实现的单点登录系统中,是这样的登录流程:

场景:有两个系统 : 小A和小B,他们通过 CAS(老C)搭建了单点登录系统。
用户要访问小A: "嗨,我的老伙计,我可以登录吗?"
小A心想:"这特么谁啊?",然后问老C :“这有个人要登录,我手头没记录,你接待一下”
老C表示不想说话,然后反手就丢了一个登录页面给用户
用户输入完账号密码,老C验证通过,“是你小子啊!发你一张通行证ST,你去找小A吧”
用户又屁颠屁颠地找到小A,“我这次有通行证ST了”
小A一看,这怕是假的吧,把这个通行证拿回给老C验证
老C给小A说:“是真的,你给他登录吧”
最后小A给用户贴了个标签(cookies),之后一段时间内,用户就可以拿着这个标签直接登录小A了。
当用户要登录小B时,又重蹈覆辙,不认识,打发到老C那去,老C一看,这货我记得啊!直接就发了去小B的另一张ST通行证,当然,小B也是拿着通行证再给老C验证,验证通过后,也给用户贴了标签( cookies),用户又可以在小B登录了。

以下是CAS的完整流程:
微服务实战(十五)看完不信你还不懂单点登录(SSO,CAS,OAuth,Jwt,Spring Security,Shiro)
文章图片


OAuth(Open Authorization)
OAuth是一个关于授权(authorization)的开放网络标准,目前使用较广的是它的2.0版本,即 OAuth2。
微服务实战(十五)看完不信你还不懂单点登录(SSO,CAS,OAuth,Jwt,Spring Security,Shiro)
文章图片

OAuth主要用来给其他系统授权功能或者数据,按照一般的逻辑,当你去使用某个系统的功能或者数据时,第一步肯定是需要登录对吧,登录之后,功能才能开放给你使用。那如果系统里的功能,希望用户不登录,也能使用呢?
比如像我们经常在很多微信公众号的页面应用里,看到弹出需要授权的确认框,而一旦确认之后,该应用就获取到了你的微信头像、昵称等数据。 -- 这其实就是通过OAuth标准去实现的。
等等,感觉还是没明白,不是授权给其他系统么?是的,其实微信公众号的页面应用对于微信确实属于其他应用,虽然都运行微信APP里,但是页面应用是部署在开发者自己的服务器上的,只是在微信浏览器中通过链接去访问。 换句话说, 微信是通过OAuth 标准,将微信用户的数据,授权给了开发者发布的微信公众号页面应用。
我们用上面这个 微信通过OAuth授权给H5应用 的例子来实际理解一下:
微服务实战(十五)看完不信你还不懂单点登录(SSO,CAS,OAuth,Jwt,Spring Security,Shiro)
文章图片

首先,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)。
第一步,用户访问授权链接 ,具体场景就是 :用户在某公众号内的底部点击了某个菜单进入H5应用(对应 A,B)
https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect
appid 为h5应用要挂接的微信公众号的标识
REDIRECT_URI 才是要访问的H5应用的地址
这一步中,通过appid,微信服务器就识别出这个appid是合法的应用(已在微信中认证为有效的开发者),然后将页面重定向到 REDIRECT_URI ,参数中附带生成好的授权码。
第二步,页面重定向至 REDIRECT_URI/?code=CODE&state=STATE (对应C)
此时授权码就发送给了 H5应用(H5应用通过URL参数接收 code)
第三步,H5应用获取code后,请求以下链接获取access_token:
https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code
(对应D,E)
H5应用拿到授权码后,在自己的后端去调用微信API获取access_token
如果参数均合法,微信API将会返回
{ "access_token":"ACCESS_TOKEN", "expires_in":7200, "refresh_token":"REFRESH_TOKEN", "openid":"OPENID", "scope":"SCOPE" }

实现OAuth授权之后,
拿着access_token, H5应用就可以调用微信API以及 openid,去获取操作者微信用户的数据(比如昵称,头像 等)

关于OAuth2.0的协议细节:
http://www.ruanyifeng.com/blog/2014/05/oauth_2_0.html
关于微信公众号授权文档:
https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html

Jwt(JSON Web Tokens)
JWT 全称 JSON Web Tokens ,是一个非常轻巧的规范。这个规范允许我们使用 JWT 在用户和服务器之间传递安全可靠的信息,是目前最流行的跨域认证解决方案。
微服务实战(十五)看完不信你还不懂单点登录(SSO,CAS,OAuth,Jwt,Spring Security,Shiro)
文章图片

Header 部分是一个 JSON 对象,描述 JWT 的元数据
Payload 部分也是一个 JSON 对象,用来存放实际需要传递的数据。
Signature 部分是对前两部分的签名,防止数据篡改。
我们可以简单粗暴地把jwt看做是一种数据结构,主要用来存储登录用户认证相关的数据。为什么要用jwt来存这份数据呢?咱慢慢一个个地捋一下吧。
这要从传统的用户登录说起,在前后不分离的大背景下,用户登录主要靠 session (cookies)实现,即后端验证完账号密码等信息后,将这个用户登录的凭证信息存入后端的一个专门内存空间:session,并把sessionID存入用户的cookies,由于前后端未分离,该用户在整个session有效期内发起请求,后端都能根据用户cookies里的sessionID获取到对应的用户信息(认为该用户一直是登录状态)。
随着前后端分离,并且后端集群化的流行,session机制的问题逐渐显现:
  • 因为session是在每个后端各自在内存中存储,所以多个后端难以实时共享session数据,用户请求到后端,通过负载均衡,可能某些请求能获取到对应session,有些请求获取不到。
  • 前端扩展到移动端,更多是采用了无状态的http请求,故而session+cookies的机制难以适应目前的完全前后端分离的需求。
而jwt则允许服务端将登录交互所需数据放到前端存储,前端在验证后的陆续请求中,都附带jwt token。以此来实现前后端分离场景下的无状态请求的认证问题; 另外由于放在前端存储,后端只需要负责处理token的验证以及从数据库中查找对应实际用户,所以后端也无需进行session共享。
JWT入门:http://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html

Spring Security/Shiro
Spring Security 和 Shiro 都是权限管理的技术实现框架,主要区别在于具体技术细节,Shiro相对轻量级,而Spring Security的功能更加全面。我们暂且以Spring Security作为权限管理的代表框架来进行下面的介绍吧。
何为权限管理呢?(官网文档中将其称为为 “认证”和“授权”两个部分)
我们在使用系统的本质是使用系统中的具体功能,而每个功能又可以细分成 查询,新增,修改等各种操作,权限管理就是对于系统中的功能以及操作进行统一的管理,控制操作者对于功能或操作的使用权限。
在 Spring Security中,我们按照框架的思路,也是去做“认证”和“授权”具体的实现。
(以最简化的代码可能更说得清楚微服务实战(十五)看完不信你还不懂单点登录(SSO,CAS,OAuth,Jwt,Spring Security,Shiro)
文章图片

  • 认证
我们需要提供给框架 用户权限结构,即 用户-角色 的对应关系,
比如下面这段小代码,向框架提供了 两个账号:user (角色为USER)和admin (角色为 USER以及 ADMIN)
@Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth .inMemoryAuthentication() .withUser("user").password("password").roles("USER").and() .withUser("admin").password("password").roles("USER", "ADMIN"); }

当然,实际使用中,肯定不会直接把账号密码配置在这儿
Spring Security 支持多种验证的配置方法:https://www.springcloud.cc/spring-security-zhcn.html#jc-form
  • 授权
我们还需要给配置好的账号赋予具体的权限,
比如下面的小代码,定义了:
"/resources/**", "/signup", "/about"这三类接口URl是所有用户都可以访问的
/admin/**这类接口只能是 ADMIN 角色才可访问
/db/**这类接口ADMIN角色并且同时是DBA角色 才可以访问
protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/resources/**", "/signup", "/about").permitAll() .antMatchers("/admin/**").hasRole("ADMIN") .antMatchers("/db/**").access("hasRole('ADMIN') and hasRole('DBA')") .anyRequest().authenticated() .and() // ... .formLogin(); }


以上就是权限管理框架所负责的工作了,本质上就是将系统中 用户角色权限之间的关系 以及 如何进行访问控制 等问题从具体业务中抽离出来,单独使用一个框架去管理,当然框架本身的功能远不止这么简单,我们先只要对它的作用以及范围有个初步的认识。

Spring Security 入门:https://www.cnblogs.com/lenve/p/11242055.html

区别与联系
好了,讲了这么多,我们把这些知识点串起来
微服务实战(十五)看完不信你还不懂单点登录(SSO,CAS,OAuth,Jwt,Spring Security,Shiro)
文章图片

1、单点登录(SSO) 是一个多系统登录问题的解决方案,具体可以由CAS技术框架实现,实现效果是:部署一个独立的登录系统,在此系统登录成功后,所有集成进来的业务系统可免登录进入。
2、OAUTH 是一个授权开放协议,主要用于将系统内部的功能或者数据授权给外部系统调用或者使用。OAUTH也可以实现单点登录的效果,像我们常见的大型网站除了账号密码登录之外,还提供了 QQ登录、微信登录、微博登录,实际上是通过OAUTH获取到了openid (比如:微信openid) 直接在系统中创建了一个和openid绑定的账号,间接实现了单点登录。
3、Jwt 是一种Token的数据格式标准,主要用于前后端分离(面向http无状态),不借助cookies情况下的前后端用户身份标识的传递(默认不加密但可进行非对称加密)。另外在 单点登录/OAUTH 的集成过程中可以考虑将其中需要传递的相关信息以Jwt的形式代替。
4、Spring Security/Shiro 是权限管理框架,关注 用户-角色-权限 关系,以及权限控制逻辑。主要用于系统内部的权限管理,如果系统要接入单点登录,需要和内部已有的权限管理框架结合。
微服务和单点登录
在微服务架构下,和上述技术点有哪些相关性呢?
1、后端微服务通过网关统一提供接口,前端有多个:网关可以把分散的微服务组装成一套大的接口集,但是前端拆分为多个系统,这样其实用户在使用时,每个系统还是需要重复登录,所以还是需要用单点登录将不同的系统进行集成。
(可以考虑使用CAS框架,或者OAUTH授权的模式实现统一登录)
2、后端微服务通过网关统一提供接口,前端只有1个:即无需使用像CAS的单点登录框架,但是每个微服务之间如何进行用户身份的认证呢?有以下几种思路:
  • 单独开发一个微服务专门做用户认证,可以结合Jwt来作为认证信息的传递,而所有需要做用户认证的微服务可用Feign、rpc等远程调用方式去调用该认证服务。
  • 单独开发一个认证的API,作为JAR包提供给其他微服务进行引用

其中场景1里也需要考虑场景2的问题。

【微服务实战(十五)看完不信你还不懂单点登录(SSO,CAS,OAuth,Jwt,Spring Security,Shiro)】

    推荐阅读