如何实现SSO单点登录

原创 吴就业 76 0 2020-12-26

本文为博主原创文章,未经博主允许不得转载。

本文链接:https://www.wujiuye.com/article/44f7bb2c94274802a2fc78de2fe779e3

作者:吴就业
链接:https://www.wujiuye.com/article/44f7bb2c94274802a2fc78de2fe779e3
来源:吴就业的网络日记
本文为博主原创文章,未经博主允许不得转载。

本篇文章写于2020年12月26日,从公众号|掘金|CSDN手工同步过来(博客搬家),本篇为原创文章。

写作本篇时,笔者就职于广州洋葱集团。原标题:《实现SSO单点登录的思考》

随着公司业务的发展,子系统越来越多,实现SSO单点登录的需求就愈加迫切。

我们一些子系统中都有使用Redis存储Session,这最初是为了解决应用集群部署时的Session共享问题,却也为应用之间共享Session提供了支持,但单靠应用之间共享Session是无法实现单点登录的。

在单应用中,当用户登录系统后,用户的登录状态被存储在服务端的Session中,并通过响应头的Cookie字段将SessionId传递给浏览器存储,后续请求服务端时,浏览器会自动在请求头加上Cookie,所以才能保持用户的登录状态。

使用Redis共享Session,集群之间可以共享Session的原理与服务重启后依然保持登录状态的原理相同。

在应用重启后,由于浏览器还是携带cookie发起请求,如果Session未过期,那么从Redis获取到的Session就能继续使用。

由此可见,虽然应用之间可通过Redis共享Session,但要求浏览器请求每个应用都能带上相同Cookie才能实现单点登录。

然浏览器却不支持不同域名之间Cookie共享,服务端也不能操控客户端浏览器访问不同域名下的站点都带上相同的SessionId。要实现单点登录我们只能另辟蹊径。

虽然浏览器不支持不同域名之间共享Cookie,但同一个主域名的不同子域名应用间可通过配置Cookie为主域名方式实现Cookie共享,前提是所有子系统都共用一个主域名。这种方案可取也不可取,短期而言可取,长期而言不可取。

如果只是实现web应用之间相互跳转,由用户在应用a点击按钮调整到应用b,也可以这样实现:当用户在应用a点击跳转应用b时,在跳转链接上带上SessionId,应用b根据SessionId读取用户信息再写入Session

先不讨论安全性如何,这种方式的弊端也很明显,用户不能直接在浏览器输入应用B的域名跳转,而只能通过应用A跳转到应用B,要返回应用A也只能从应用B点击按钮跳转回应用A

虽然通过点击按钮方式实现应用之间互相跳转不是一个好的计策,但至少通过在跳转链接上携带SessionId共享登录状态这个思路是可取的。

根据这个思路,我们是否可以实现不通过点击按钮方式也能让浏览器自动带上SessionId呢?

可以,但要通过重定向实现。

当用户在系统A登录后,直接在浏览器上修改域名访问系统B时,系统B检查到用户未登录后将请求重定向到系统A,系统A检查到请求从系统B重定向过来,并且用户已经登录,那么可将SessionId拼接到重定向链接上,再重定向回系统B,系统B获取到系统ASessionId,根据SessionIdRedis查询用户信息,再写入系统BSession中,这样就能实现自动携带SessionId跳转。

只不过,这种方式要求每个系统都实现一遍这样的功能,并且每个系统也都要提供登录功能。

为了简化实现,以及后续的新系统不再重复实现登录功能,我们应该考虑将登录功能抽离为一个独立的应用,后续可将菜单授权等功能都迁移到该系统上,其它系统不再提供登录功能。

将登录功能抽离为独立应用之后,实现SSO单点登录流程梳理如下:

SSO抽离为一个独立的应用,独立的域名,提供登录页面,要求其它应用不再提供登录页面,都必须通过SSO登录。

其它应用在接收到请求时,首先根据session判断是否已经登录了,如果未登录则重定向到SSO登录页面,并且在重定向链接带上是哪个应用跳转过来的,当用户在SSO登录成功后重定向回原来的应用。

浏览器重定向到SSO登录页面时,浏览器会存储SSOcookie,用户在SSO登录成功后,SSO存储用户的登录状态。SSO生成一个token,重定向回原应用,在重定向链接上带上token

原应用检查请求携带token,这时需要访问SSO验证token并获取用户信息,SSO验证成功后返回用户信息,原应用将用户信息存储到Session中,验证成功后再重定向到首页。

如果用户此时通过在浏览器输入应用B的域名访问应用B,由于应用B检查到Session没有用户信息(未登录),于是重定向到SSO应用。

因为用户在SSO登录过了,重定向请求SSO应用时浏览器会带上cookie,所以SSO应用发现用户已经登录,于是生成一个token并重定向回应用B

应用B接收重定向请求,从请求中获取到token,接着访问sso应用验证token并获取用户信息,在获取用户信息成功后再写入Session,最后重定向到首页。

根据梳理的流程,总结每个应用需要实现的功能:

SSO应用:

其它应用:

需要注意的是,假设SSO设置的session过期时间为一个小时,如果用户在SSO登录后跳转回应用A,一个小时不操作后再跳转应用B,此时会因为SSOsession已经过期导致无法同步登录状态,用户就得要重新登录,所以SSOsession过期时间应该根据需要合理设置,不应该设置太短。

在前后端分离的系统上实现这一流程并不容易,实际实现比本文描述的步骤还要多。我们通过封装SDK的方式,尽可能将繁琐的步骤封装起来,让其它应用对接SSO时仅需要依赖一个jar包,并添加少量的配置。

SDK通过Servlet提供的过滤器拦截所有请求:

实际对接流程如下:

最后留下一道思考题:如何同步退出登录状态?

当用户在应用A退出登录时,只有应用ASSO知道用户退出登录了,但其它应用却不得而知。

并不是实现不了,而是在寻找最优的方式。最简单的方式就是除SSO之后,将其它应用的Session过期时间配置尽可能短,宁愿多重定向几次。

最后,由于每个应用都用了Shiro实现接口权限校验,也用了Shiro的注解,所以权限校验的实现,我们在SDK适配了Shiro的注解,但完全弃用了Shiro

#后端

声明:公众号、CSDN、掘金的曾用名:“Java艺术”,因此您可能看到一些早期的文章的图片有“Java艺术”的水印。

文章推荐

浅谈面向文件编程(文件读写)的重要性

在我印象中,似乎很少有关于文件操作的面试题,而大多数面试题都围绕着多线程、网络编程、RPC、数据库,但其实掌握文件操作也同等重要。

InheritableThreadLocal异步传递数据实现原理

继《反向理解ThreadLocal,或许这样更容易理解》,本篇介绍InheritableThreadLocal异步传递数据的实现原理。

替换Shiro框架后,上线就Bug了,异步线程获取不到Session

我们将原有项目的登录授权功能从Shiro切换到接入SSO单点登录服务并非一帆风顺,因为系统多了,总有一些让我们预想不到的骚操作。

如何并行消费Kafka拉取的数据库Binlog,提升吞吐量

本篇介绍如何并行消费Kafka拉取的数据库Binlog,以及使用Kafka订阅Binlog字段值获取防坑指南(阿里云DTS)。

深入浅出反应式编程原理,反应式编程入门

反应式编程不适用于业务开发,特别是复杂业务系统的开发,这或许就是反应式编程从推出到现在依然不温不火的原因吧。

如何使用Kafka订阅数据库的实时Binlog

订阅Binlog的目的在于,实现实时的缓存更新、处理复杂逻辑数据实时同步到Elasticsearch或其它库-表等业务场景,本篇介绍如何使用Kafka订阅数据库的实时Binlog。