其实从《spring-security进阶1—第三方登陆原理1【从常理出发来思考一下oauth2协议】》那篇文章起到利用spring social开发QQ登陆,微信登陆我一直都在与授权码模式打交道,下面这幅图在我的博客里也已经出现过好几次了,这就是Oauth2协议中授权码的主要流程.
由于上篇文章《【Spring Security OAuth2】— 实现标准的OAuth服务提供商》刚实现了一个标准的服务提供商,并走了一遍授权码模式,因此我对该模式也有了更加深刻的认识,这里用我自己的话再叙述一遍授权码的整体流程:
(1)用户访问某个网站(即图中的第三方应用Client)
(2)该网站会将用户导向认证服务器 —> 带着client-id,redirect-uri等参数
(3)用户进行授权,比如输入用户名+密码或扫码等
(4)授权成功后,认证服务器会将带着授权码根据redirect-uri回调该网站
(5)该网站再拿着授权码、client-id、client-secret等去请求认证服务器获取accessToken
(6)然后第三方应用就可以拿着accessToken去资源服务器请求相应接口并获取数据了
注意1:
这里要注意,授权码模式要求第三方应用必须要有一个服务器 ,有兴趣的可以好好想想这是为什么。
上篇文章在实现标准的服务提供商时,也走了一遍密码模式,其原理大致如下图(省去了去资源服务器获取数据的过程),这里不再过多叙述。
相信很多人都会觉得授权码模式比密码模式更安全,那密码模式存在的意义是什么呢?这个问题其实已经困扰了我很久很久了,这一段时间,吃饭、睡觉感觉我一直都在想这个问题。下面是我思考后得出的结论,当然有可能不对,欢迎读者前来指正。
我觉得这很好理解,比如在第三方登陆的场景中,服务提供商为QQ、微信、微博等,第三方应用是某个网站如CSDN。假如使用了密码模式,以QQ第三方登陆为例,我们需要将QQ的用户名+密码给CSDN
,然后CSDN拿着我们QQ的用户名+密码以及CSDN自己在QQ注册的client-id、client-secret等信息去QQ换取accessToken,然后拿着获取到的accessToken再去QQ请求QQ的用户信息。
但这肯定是不行的,因为我们并不想把QQ的用户名+密码给CSDN!!!
服务提供商和第三方应用是一家时—> 老师给的答案是app可以使用密码模式,但web项目不行
我反复看了老师本门课程的视频,但是感觉并没有找到很好的解释,于是又买了老师的另一门课程,大致有了一点思路。
首先来看下面这种前后端分离的系统架构:
其实我们部门现在就使用的这种架构。
这种架构下html渲染的大致流程如下:
(1)浏览器端发起请求
(2)Nginx服务器拦截到请求并将静态资源返回给浏览器
(3)浏览器就会加载到html页面+css样式+js,然后js会发送ajax请求
(4)Nginx服务器拦截到js发送的ajax请求并转发给后端的API Server获取数据(一般都是json数据)
(5)js接到数据后,拿着数据去渲染html页面
(6)这样我们就能看到一个完整的html页面了
该架构存在的问题:
(1)搜索引擎优化(SEO)会有问题 — 搜索引擎中的爬虫一般只能识别静态的html资源,很难识别js动态渲染出的内容
(2)页面渲染在浏览器内完成,如果业务复杂,加载的数据量比较大,可能会对浏览器造成一定的压力,从而导致应用卡顿,用户体验差 — 这条感触颇深
(3)开发时前端依赖于后端,想联调时可能要模拟一些数据,或者在本地配置Nginx来请求后端服务,因为前端并没有一个真正提供服务的Server
比较理想的前后端分离架构如下:
在这种架构下浏览器+NodeJS前端服务器统称为前端。其html渲染的大致流程如下(上图中1、2、3):
(1)浏览器端发起请求给NodeJS
(2)NodeJS发起请求给API Server获取数据
(3)NodeJS在服务器端将页面渲染好然后返回给浏览器
当然这种架构也可以实现上面那种架构中页面渲染的流程(上图中的4、5)。
因此相比于上面那种架构来说这种架构:
(1)由于NodeJS可以将渲染好的页面给浏览器,所以搜索引擎的爬虫就可以爬取到完整的html页面,因此可以较好的实现SEO的需求,当然如果有些页面不想做SEO,也可以使用上面架构中html页面的渲染方式
(2)由于渲染动作放在了服务器端,浏览器压力变小,应用的用户体验会更好
(3)由于利用NodeJS前端服务器可以写服务供浏览器中的js去调用,前端开发可以完全不依赖于后端,前后端都开发完后再做最后的联调,从而实现真正的前后端分离式开发
(4)我觉得还有一个不得不提的点是授权码模式需要第三方应用有一个服务器
,而这种架构下正好有个NodeJS前端服务器 — 这是多么巧,又多么神奇的事情 ☺☺☺
如果web项目使用密码模式的话,比较好的一个架构大概可能是下面这个样子的(这里没考虑服务之间的调用
):
具体流程大致为:
(1)用户在浏览器输入用户名+密码给前端服务器,然后前端服务器拿着用户名+密码、client-id、client-secret等信息通过网关去请求认证服务器获取token(对应图中1、2、3、4这四步)
(2)前端服务器获取到token后将token存起来,然后之后的每次请求都会带着该token(对应图中第5步)
(3)网关会对每次请求进行拦截,并拿着token去认证服务器进行校验(将校验token这一步放在网关里可以避免每个微服务都写一遍校验token的代码,对应图中第6、7两步)
(4)当校验成功后,网关就会将请求转发给具体的服务,然后前端就可以获取到该服务相应接口的数据了(对应图中第8步)
密码模式在这种架构下的缺点
:
(1)如果公司门户系统较多(也就是说存在多个客户端应用时),这时候就要每个客户端都要处理一遍登陆的逻辑,这肯定不是我们想看到的效果,因为一旦校验逻辑改变每个客户端可能都会受到影响;且这种情况下每个前端应用都会接触到用户的用户名和密码这无形之中其实也影响了系统的安全性。
(2)浏览器是前端应用的代理,用户名+密码填写在浏览器端,我们无法保证浏览器的安全性 — 这条有待进一步理解。
因此web项目里比较理想的认证授权模式其实是使用授权码模式,其架构大致如下:
这里不再对过程进行叙述,相信大家也肯定都能看懂。在这种架构下,登陆(也就是原来说的授权)将只在认证服务器上进行,从而避免了密码模式带来的问题,同时还实现了SSO(有兴趣的可以想想为什么???
以后的博客会对此进行更加详细的解释和说明)。
由于我并没有开发过app项目,但仍想从自己的理解去说一下这个问题。我能想到的原因大概有以下几点(都是自己瞎琢磨的,如不正确,欢迎指正!!!
):
(1)app不像浏览器,其安全性可能更高,在app客户端应用 <—> 授权服务器之间是可以相互信任的;
(2)app项目不会涉及单点登陆问题,也就是说一个应用和另一个应用之间没有关系,我想大家肯定知道即使淘宝和天猫账户是一个,在app端如果下载了这两个应用,也必须得各自登陆。 如果按照3.2.1中第2个架构进行开发的话,这样其实会实现单点登陆的效果的,这可能并不符合app的设计原则。
简化模式的大致流程如下:
即用户登陆时,前端页面将用户引导到认证授权服务器,服务器验证通过后直接将token发给前端页面。
之所以有这种授权模式是因为有些客户端应用根本就没有前端服务器,就是一堆html+css+js,因此只能将token给到浏览器里,但浏览器是存在安全风险的,这种模式一般都不会用了。
客户端授权模式大致流程如下:
这种授权模式不需要用户参与,客户端请求到的令牌是针对客户端应用的,不是针对用户的。
客户端授权模式和简化授权模式我们一般不会使用
web项目中一般使用授权码模式
手机app项目中可以使用密码模式