Oauth2和SSO登录疑难点

2812
6 分钟阅读
Oauth2和SSO登录疑难点
"AI摘要: 本文介绍了OAuth2.0和单点登录的实现方法,以及它们在现代应用中的使用。文章首先解释了SSO(Single Sign-On)的概念及其核心流程,接着讨论了单点注销的功能和挑战。此外,还探讨了JWT(JSON Web Tokens)作为认证登录方式的优势与局限性,包括其无状态特性带来的权限更新问题。最后,文章详细描述了微信、飞书等第三方授权平台的实现原理及安全性考量。"

如今,大多数网站和应用都支持第三方登录或单点登录,我已很久没有遇到需要输入账号和密码的系统了。约一年前,在维护团队的支撑系统时,我首次接触到了Spring Boot实现飞书第三方登录的源码。面对复杂的Spring Security配置,我不得不四处寻找流程的走向,同时通过各大网站学习OAuth 2.0的原理。为了更深入理解这套当前广泛使用的登录方案,我觉得有必要做一个总结笔记,以免日后对此一问三不知。

SSO

SSO即单点登录,在登录同一家公司的各个系统时,只需一次登录就可以访问所有网站,避免了多次输入账号密码的情况。

单点登录

SSO的实现有很多方法,最常见的就是设置一个独立的认证服务器,从一个简洁的流程图可以帮助我们理解基于认证中心的SSO的原理:

  1. 用户访问系统1

  2. 系统1重定向到SSO服务器

  3. 用户在SSO服务器上认证

  4. SSO服务器生成token并重定向回系统1

  5. 系统1验证token并允许访问

  6. 用户访问系统2

  7. 系统2检查SSO token,无需再次登录

从这个流程中,我们可以看出SSO的核心是通过一个认证中心服务器来管理用户的身份信息。当用户首次登录时,认证服务器会生成一个token,并将其分发给其他应用系统。这样,用户在访问其他系统时就无需再次输入凭证,大大提高了用户体验和安全性。

单点注销

单点注销(Single Sign-Out)是单点登录系统中不可或缺的一部分。它确保当用户从一个应用注销时,所有关联的应用也同时注销。实现单点注销的主要挑战在于如何有效地在所有参与的应用之间同步注销状态。通常,这可以通过中央认证服务器发送注销请求到所有已登录的应用来实现。

踢人下线

踢人下线是单点登录系统中的一个重要功能,通常用于管理员强制终止某个用户的会话。

虽然之前维护的系统中并没有这个需求,但是基于JWT实现踢人下线还是一个很有意思的问题。基于Session的用户登录场景中,只需要在redis中删除用户的session就可以强制用户无法访问系统。在JWT登录场景中,后端只能从JWT中解析出用户信息和过期时间,而后端自己又没有存储过所有用户的JWT,所以无法实现踢人下线。

现实场景中,我们会使用两种不太好的处理办法:

  1. 将JWT存储数据库和Redis:如果需要让某个 JWT 失效就直接从 Redis 中删除这个 JWT 即可。

  2. 黑名单机制: Redis 维护一个黑名单,如果想让某个 JWT 失效的话就直接将这个 JWT 加入到 黑名单 即可。

由于后端存储了JWT,相比session直接丢失了自己的优势,所以并不太好,但是似乎也没有更优的方案。

总结:JWT 其中一个很重要的优势是无状态,但实际上,我们想要在实际项目中合理使用 JWT 做认证登录的话,也还是需要保存 JWT 信息。

权限更新

虽然我当时升级支撑系统权限认证为RBAC模式时并没有做完,但是如果继续实现,确实会遇到权限无法立刻更新的问题:)。

简单来说,当系统在后端修改用户的权限时,基于JWT的登录方案无法直接实现权限的立刻更新,通常需要等到有效期过后才可以。这种情况和踢人下线本质是一样的,都是JWT的无状态属性作孽。如果想要实现即使更新,永远不能避免由服务端来把控用户信息,也就是将用户权限信息存储在Redis中,每次用户请求都会去Redis中实时查询用户的权限信息。

总结:JWT不是万能的,为了实现实时更新,避免不了用户端存储用户信息,但是这个Cookie+分布式Session也能实现,这个时候JWT的优势可能就只剩下移动端了。

OAuth2

微信授权登录/飞书授权登录/QQ授权登录/Google授权登录就是具体的OAuth2登录实现,能够帮助用户在免输入账号密码的情况下登录系统,提升用户体验。

OAuth2的实现涉及了四个角色:

  1. 资源所有者(Resource Owner):通常是指用户;

  2. 授权服务器(Authorization Server):通常是指微信开放平台/飞书开放平台等;

  3. 客户端(Client):通常是指前端;

  4. 资源服务器(Resource Server):通常是用户想要访问的应用程序/后端;

用户(资源所有者)授权客户端访问其资源,授权服务器颁发访问令牌给客户端,客户端使用这个令牌访问资源服务器上的资源。

通用的OAuth2的登录流程如图:

组里的支撑系统的流程类似:

  1. 进入支撑系统,发现未登录,跳转到飞书授权页面

  2. 通过手机飞书扫码确认,此时飞书授权页面会返回一个授权码code并跳转会组里的支撑系统

  3. 组里的支撑系统使用得到的授权码code和自己保留的appid、appsecret请求飞书平台换取access_token

  4. 组里的支撑系统用access_token去飞书平台换取用户的信息(手机号,openId等)

  5. 组里的支撑系统返回自己的token给前端

这里有一个安全上的小考点:为什么OAuth2不直接返回token给客户端,而是返回一个授权码code给服务端?

  1. 如果客户端直接拿到access_token, 那么黑客可能通过客户端拿到access_token,向微信开放平台/飞书开放平台发送请求,从而获取到用户的信息,比如前端页面的源码,app的反编译

  2. 在注册第三方登录时,第三方平台会生成appid和appsecret,这两个信息一样不能放在客户端/前端,只能放在服务端以确保安全。