点击上方“芋道源码”,选择“设为星标”
管她前浪,还是后浪?
能浪的浪,才是好浪!
每天 10:33 更新文章,每天掉亿点点头发...
源码精品专栏
原创 | Java 2021 超神之路,很肝~
中文详细注释的开源项目
RPC 框架 Dubbo 源码解析
网络应用框架 Netty 源码解析
消息中间件 RocketMQ 源码解析
数据库中间件 Sharding-JDBC 和 MyCAT 源码解析
作业调度中间件 Elastic-Job 源码解析
分布式事务中间件 TCC-Transaction 源码解析
Eureka 和 Hystrix 源码解析
Java 并发源码
来源:网络
一、身份验证介绍
二、界面登录身份验证如何设计
三、接口调用身份验证如何设计
四、为什么需要SSO登录方式
身份验证通常是进入系统的第一道大闸,要求用户出具登录此系统的身份证明。其实在实际情况下,很多人开发的系统都没有身份验证功能或者就只有普通用户名和密码验证功能,这样的系统其实都是不完善的,也是不符合安全标准的,所以作为这么大众化的系统,我们有必要做详细的学习和了解。
身份验证分类
对于身份验证,我们分两种情况来看待,界面登录验证和接口调用验证,这两种方式所使用的身份验证方法很多时候是不一样的。
1、界面登录验证
指用户通过前端(包含浏览器端和移动端)来进入系统,用户一旦登录成功后,用户后续的操作便是以此身份来执行,常规的做法是服务端生成相应的Session,前端生成Cookie。常见的界面登录解决方案有SSO、Ldap、OAuth、Basic、Kerberos等。下面我们说说这几个解决方案。
1.1、Basic
普通用户名密码登录,通常把用户名和密码以明文或者加密后的方式存储在数据库中,当用户在前端输入用户名密码后,后端进行验证,通过后会生成相应的Session信息,这样就算验证成功了。
这是最基本的一种身份验证方式,在开源软件的身份验证功能里面通常都会带有这种方式。
1.2、Ldap
LDAP(Light Directory Access Portocol),它是基于X.500标准的轻量级目录访问协议。
目录数据库是一个为查询、浏览和搜索而优化的数据库,它成树状结构组织数据,类似文件目录一样。目录数据库和关系数据库不同,它有优异的读性能,但写性能差,并且没有事务处理、回滚等复杂功能,不适于存储修改频繁的数据。所以目录天生是用来查询的,就好象它的名字一样。
LDAP目录服务是由目录数据库和一套访问协议组成的系统。
1.3、OAuth
OAuth 2.0 是一个行业的标准授权协议。OAuth 2.0 专注于简化客户端开发人员,同时为 Web 应用程序,桌面应用程序,手机和客厅设备提供特定的授权流程。
它的最终目的是为第三方应用颁发一个有时效性的令牌 token。使得第三方应用能够通过该令牌获取相关的资源。常见的场景就是:第三方登录。当你想要登录某个论坛,但没有账号,而这个论坛接入了如 QQ、Facebook 等登录功能,在你使用 QQ 登录的过程中就使用的 OAuth 2.0 协议。
1.4、Kerberos
Kerberos 是一个网络认证的框架协议,其设计的初衷便是通过密钥系统为 Client 和 Server 应用程序之间提供强大的认证服务。在使用 Kerberos 认证的集群中,Client 不会直接和 Server 进行认证,而是通过 KDC(Key Distribution Center)来完成互相的认证。
Kerberos是一种单点登录解决方案,使用场景之一是在大数据(Hadoop)集群中,用来作为大数据安全的基础保障。
1.5、SSO
SSO英文全称是Single Sign On,即单点登录。在多个系统中,用户只需要在其中一个系统中登录过了,就不需要在其他系统中再登录了。
SSO登录方式也是现在最流行的验证方式,在公司内部会有很多套系统,什么Wiki、Gitlab、Jira等等,如果大家在进入每个系统前都去输入用户名密码搞一次,这是挺烦人的,同时也浪费了很多时间和精力,如果用SSO只需要登录一次就好了,岂不是大快人心。
SSO代表一种概念,并不是一种具体的解决方案,最常见的解决方案是CAS,这个我们在后面的章节会单独讲解。CAS可以和Ldap一起构成完整的登录解决方案。
2、接口调用验证
指用户直接通过调用系统的RESTful API等方式来发起操作,常见的接口调用验证方案有HMAC、JWT、证书等。
2.1、HMAC
HMAC英文全称是Hash-based Message Authentication Code,即基于Hash的消息的认证码。
我特地在网上搜索了HMAC的原理,但绝大部分要么解释的不清不楚,自己也搞不清楚在说什么,要么就是曲解了真正的意思。我这里用尽量通俗的话来让大家明白。
如果你调用过一些云厂商(比如腾讯云、阿里云、七牛云)的公共接口的话,你可能会有印象,他们会给你两个Key,一个叫AccessKey,另一个叫SecretKey,有的厂商会用UserName替代AccessKey,其实是一个意思,UserName或者AccessKey都表示某个特定的用户,而SecretKey表示这是要保密的,不能被别人知道的,在请求数据被传输的时候是不会包含SecretKey的。
一般把请求的参数放在一起作为一个字符串,然后用SecretKey做密钥,采用一种哈希算法(有HMAC-MD5、HMAC-SHA256、HMAC-SHA512等)把这个字符串做哈希加密,然后把加密后的字符串作为一个参数,并把AccessKey也作为一个参数,连同原始请求数据一起传递给接受者。
接受者根据AccessKey知道了是那个用户发送的请求,查询出对应的SecretKey,然后拿着这个SecretKey把传递过来的数据按照客户端的方式在加密一次,比对加密后的字符串是否一致,如果一致表示数据没有被篡改,最后返回结果。
HMAC的设计是用来防止请求数据在传输途中被人篡改,同时也可以防止密钥泄漏,它并不能防止数据被别人截获后看到原始数据,要防止数据被人截获后看到原始数据,就需要Https、SSL这样的传输加密措施。
2.2、JWT
JWT全称是JSON Web Token,也是比较常见的身份验证方式,这种方式服务端部保存Session信息,所有用户信息都在客户端。下面简单说下JWT的流程。
用户在客户端把带有用户名和密码的请求发送给服务端后,服务端生成一串字符串(Token),还有一个密钥,然后把Token返回给客户端。
用户接受到Token,字符串由三段字符串组成:Header.Payload.Signature,Header里面包含签名算法(比如HMAC-SHA256),Payload包含过期时间、签发时间、编号等信息,Signature是对Header和Payload的签名加密。
用户随后的请求都会带着这个Token,这样服务端就知道这个用户是谁了。
细细思考下,就会发现一旦用户发送的信息被别人截获,那么坏人就可以随意的向服务端发送请求了。
再细细的想象,由JWT的实现思路,我们可以简化下流程,Token信息其实不需要那么多,只要一串字符串就可以了,服务端保存这个Token,用户每次发送请求都带着这个Token,这样也能达到一样的效果。
推荐下自己做的 Spring Boot 的实战项目:
https://github.com/YunaiV/ruoyi-vue-pro
设计原理
相信有些用过MVC框架的同学对接过SSO登录系统,比如在Django中对接CAS单点登录系统。我们要说的架构设计和MVC框架不太一样,因为MVC框架是前后端一体的,而微服务中的架构都是前后端分离的。
MVC框架
请求先到达后端服务接口,以此进行身份验证、错误处理、权限控制、动作执行等过程,然后把结果填充到HTML/JS模块,最后把HTML/JS内容返回给用户浏览器。
微服务框架
前端服务和后端服务都有独立的服务地址(IP+端口),请求先到达前端服务,然后前端服务再去后端服务请求需要渲染的元素和内容。
身份验证作为API网关里面的一个组件,可以以模块的方式运行,也可以以微服务的方式运行,这里我们推荐以微服务的方式运行。它包含了界面登录模块和接口调用验证模块。我们先说界面登录模块的架构设计。
架构图
流程:
用户在浏览器访问前端服务后,前端服务会把请求转发到API网关。
网关把请求转发到身份验证服务,验证服务查看请求头中的Token信息是否有效,发现无效,请求被重定向到CAS Server。
用户在正确输入用户名密码后,CAS Server返回令牌并把请求重定向到前端服务。
前端服务把请求发送给API网关,网关再发送给身份验证服务,验证服务确认令牌有效后生成Session信息,并把生成的Token信息返回给前端服务。
前端服务随后的请求的请求头中都会带着Token信息直到浏览器窗口关闭,身份验证服务校验Token信息成功,然后把请求转发给后端服务,处理完后返回结果。
关键点设计
1、跨域访问
API网关对接的服务和网关本身的服务通常是不在一个域名下面的,比如工单系统前端服务的域名是frontend.gongdan.company.com,网关里面的身份验证服务的域名是authentication.apigw.company.com,前端服务每次请求中带有的Token信息能不能放在Cookie中?
答案是不能的,因为Cookie中的信息是无法跨域访问的,我们可以把Token信息放在请求头中或者POST Body中。
为了实现跨域访问,网关的接受请求模块的响应头中还需要设置Access-Control-Allow-Origin=*,这样用户请求才能被正常处理。
2、缓存
如果前端服务的每次请求都会被网关转发到身份验证服务去校验Session信息,那么这个时间周期就比较长,为了提高性能,我们可以在API网关里面设计一个缓存,用于存放已通过验证用户的Session信息,这样请求时间周期就短了。
有人可能要问了,缓存里面的Session信息如何更新呢?
如果请求里面的Token信息在缓存中查询不到的话,网关再去身份验证服务拉取一份最新的Session信息。
3、CAS Client
界面登录模块中实现了CAS Client,其中包括把用户的请求重定向到CAS Server,和CAS Server的令牌认证以及退出操作。
推荐下自己做的 Spring Cloud 的实战项目:
https://github.com/YunaiV/onemall
这里介绍一种最常用的设计方案。
Token
为用户生成一个单独的Token,即一长串字符串,Token和用户是一对一的关系,每个Token还对应多个微服务里面的多个接口,Token设置一定的过期时间,过期后自动更新。如下图所示,
用户可以通过带有用户名密码的请求,调用相应接口获取Token,也可以手动查询获取。
用户拿到Token后,在随后的调用中带着这个Token就可以调用相应的接口了。
上面的设计只是一种参考方案,如果你的需求和这个设计不匹配,你可以自行改动设计。
随着新的业务网站不断的增加,用户在每个应用系统中都有独立的账号,这样就造成在访问不同的应用系统时,需要记录对应的用户名和密码,多个用户名密码极易记混,如果忘记或记错了某一个业务网站的用户名或密码就无法进行登录,耽误工作,影响工作效率,随着局内信息化进程的推进还会有新的应用系统产生,如果不引入单一用户登录的解决方案,全公司工作人名特别是承担审批权限的各级领导很难记清各类应用系统的用户名和密码,严重影响由信息化带来快捷性和高效性。
因为后面的章节会围绕SSO来详细说明应该如何设计SSO登录解决方案,所以下面我先给大家说说SSO登录的原理。我们就拿使用最广泛的CAS解决方案来说吧。
SSO之CAS介绍
CAS是SSO的一种具体解决方案,也是其中最流行的一种。CAS(Central Authentication Service)是耶鲁大学的一个开源项目,旨在为web应用系统提供一种可靠的单点登录解决方案。
CAS有CAS Client和CAS Server两个组件构成,CAS Server就是中心认证服务端,用户的访问只要通过它的认证就可以不用登录直接在访问其他系统了。
CAS和Ldap的区别:
CAS是一种单点登录解决方案,解决的是当用户要登录多个系统时,只需要在任意系统登录一次就可以在其他系统中免登录。它不包含底层的数据存储,也不包含数据访问协议。
Ldap是目录访问协议,它通常和目录数据库一起构成登录解决方案。所以你可以理解成CAS是在Ldap之上的一种单点登录解决方案。
CAS中的关键术语
ST(Service Ticket) :服务票据,服务的唯一标识码,由TGT生成ST,返回给用户,接着拿着生成的ST去访问service,service又会把ST拿到CAS系统去验证,验证通过后才允许用户访问该资源。
TGC( Ticket Granting Cookie) :CAS系统用来识别用户身份的凭证,通常设置有效期,过期无效。
TGT(Ticket Grangting Ticket) :授权票据,获取这TGT之后才能申请服务票据(ST),用户如果在CAS系统认证成功之后,就会生成TGC写入浏览器,同时也生成一个TGT,TGT对象的id就是cookie值。之后每次请求过来通过此cookie来从缓存获取TGT,就不用提交身份认证信息(Credentials)。
Session:各个应用系统会创建自己的session表示是否登录,而这里的每个session都是ST验证通过之后组装生成的。
用户登录流程
用户首次访问系统A:
用户浏览器访问系统A中受限资源,此时系统A进行登录检查,发现未登录,然后系统A检查访问的URL中是否带有令牌(ST),发现没有令牌(因为是首次登录)。
系统A将用户请求重定向到认证中心(CAS Server),认证中心进行获取全局票据(TGT)操作,没有(因为是首次登录),所以用户需要进行登录操作(输入用户名和密码)。
认证中心呈现登录页面,用户登录,登录成功后,认证中心给用户浏览器设置Cookie(TGC),然后认证中心重定向请求到系统A,并附上认证通过令牌(ST),此时认证中心同时生成了全局票据(TGT)。
此时再次进行登录检查,发现未登录,然后再次获取令牌操作,此时可以获得令牌(ST),系统A拿着令牌和请求url与认证中心通信,验证令牌有效,认证中心返回成功,证明用户已登录。
系统A生成相应的session(用于下次用户访问时验证),然后将受限资源返给用户。
已登录用户首次访问系统B:
浏览器访问另一系统B需登录受限资源,此时进行登录检查(session检查),发现未登录,然后系统B检查访问的URL中是否带有令牌(ST),发现没有令牌。
系统B将请求重定向到认证中心,认证中心进行获取全局票据(TGT)操作,可以获得(因为之前已经登录过)。
认证中心发放令牌(ST),并携带该令牌重定向到系统B。
系统B再次进行登录检查,发现未登录,然后再次获取令牌操作,此时可以获得令牌(ST),系统B拿着令牌和请求url与认证中心通信,验证令牌有效,认证中心返回成功,证明用户已登录。
系统B生成相应的session(用于下次用户访问时验证),然后将受限资源返回给用户。
用户登出:
用户向系统A发起登出请求动作,系统A清除本地session,同时清除用户浏览器中相应的cookie。
系统A将用户请求重定向到认证中心的登出接口,认证中心根据获取到的票据(TGT)清除本地session,同时清除用户浏览器中相应的cookie(TGC)。
认证中心把用户请求重定向回系统A,这时由于系统A中已经没有了session,所以请求会被重定向到认证中心的登录页面。至此用户已经完全的退出系统了。
说明:
图中无箭头的线代表重定向,可以看出每张图中都有两次重定向操作。这里请大家注意,重定向和转发是不一样的,重定向时服务端会给用户返回30X,请求头的Location中带有重定向后的地址,用户会向这个地址再次发起一次请求。而转发则是服务端直接把请求发送给目标地址,所以重定向有两次请求,而转发只有一次请求。
CAS Server对外一共有三个接口,分别是登录接口(通常是登录页面的形式呈现),认证接口(认证令牌和请求URL是否有效),以及登出接口。
CAS Client是比较轻量级的,一般都是放在各个系统中去实现,主要要实现令牌(ST)获取;与CAS Server的认证;本地session的生成和存储;用户浏览器cookie的生成。
由于不同的开发框架很多都没有自带的CAS登录模块,而且它们在登录模块实现上都不太一样,所以大家对不同的开发框架都需要自己去实现相应的CAS登录模块,比如Django框架和Tornado框架就需要对它们分别实现。这一点比较麻烦,后面我会说到如何在微服务架构中做一个通用的统一登录系统,这样就可以一劳永逸了。
- END -欢迎加入我的知识星球,一起探讨架构,交流源码。加入方式,长按下方二维码噢:
已在知识星球更新源码解析如下:
最近更新《芋道 SpringBoot 2.X 入门》系列,已经 101 余篇,覆盖了 MyBatis、Redis、MongoDB、ES、分库分表、读写分离、SpringMVC、Webflux、权限、WebSocket、Dubbo、RabbitMQ、RocketMQ、Kafka、性能测试等等内容。
提供近 3W 行代码的 SpringBoot 示例,以及超 4W 行代码的电商微服务项目。
获取方式:点“在看”,关注公众号并回复 666 领取,更多内容陆续奉上。
文章有帮助的话,在看,转发吧。
谢谢支持哟 (*^__^*)