简介
Spring Authorization Server 是 Spring 官方推出的、用于构建授权服务器的新一代框架,由Spring Security OAuth升级而来,支持最新的 OAuth 2.1 规范,并内置了对 OpenID Connect (OIDC) 的支持。与 Spring Boot、Spring Security 5.x+ 及 Spring Cloud 有着天生的亲和力。通过简单的依赖和配置,就能快速搭建一个生产就绪的授权服务器,大大降低了开发复杂度。
核心功能
- SSO(Single Sign On)单点登录
- 搭建授权服务器
- 身份认证中心
OAuth2协议
要更深入理解Spring Authorization Server,首先要清晰认识OAuth2的各个职责及模式。不然很容易越做越晕,不知道自己要干什么。
简介
OAuth 2.0是一种开放授权协议。它的核心目的是让你(作为资源的拥有者)能够安全地授权某个第三方应用(比如一个新上线的网站或手机App)访问你在另一个大平台(如微信、微博、Google)上存储的个人信息(如头像、昵称、好友列表等),而无需将你的平台账号和密码告诉这个第三方应用。
💡 一个生活化的比喻
为了更好地理解,我们可以用一个“小区快递”的比喻:
- 你(用户) = 业主(资源所有者),拥有家里的所有包裹(受保护的资源)。
- 你家(放包裹的地方) = 资源服务器。
- 快递员(想送你快递的人) = 第三方应用(客户端) ,他的目标是把包裹送到你家。
- 物业中心 = 授权服务器,负责管理门禁权限。
传统(不安全)的方式:你直接把家里的门禁密码告诉快递员。这样,快递员虽然能进门,但也知道了你的密码,拥有了和你一样的权限,这显然风险很高。
OAuth 2.0(安全)的方式:
-
快递员在门禁系统上按下“请求进入”的按钮(这相当于引导你到授权页面)。
-
你接到系统的电话或App通知,确认是某位快递员,并选择授权他“仅本次进入小区,且只能到你家门口”(这设定了权限范围)。
-
物业中心核实后,生成一个有时效性的数字门禁码(例如有效期1小时)发给快递员。这个码就是 访问令牌(Access Token) 。
-
快递员用这个临时门禁码进入小区,将包裹送到你家门口。时间一到,门禁码自动失效。
-
如果快递员需要多次上门(比如安装大家电),物业还可能给他一个“刷新令牌”(Refresh Token),让他在临时门禁码过期后,能申请一个新的,而不用每次都麻烦你重新授权。
🌟 核心优势
通过这个比喻,可以看出OAuth 2.0的精髓在于令牌(Token) 代替了 密码(Password) ,这带来了三大安全优势:
- 权限受限:令牌可以限定权限范围(比如只能读取头像,不能发布内容)。
- 时效性强:令牌是短期的,过期自动失效。
- 权责分离:令牌可被单独撤销,且不会影响你的主密码。你可以随时让某个应用的令牌失效,而不必修改平台密码。
角色
通过以上的例子我们发现,在OAuth2体系中,涉及到三个核心角色:
| 角色 | 英文 | 核心职责 | 生活化比喻(延续快递的例子) |
|---|---|---|---|
| 授权服务器 | Authorization Server | 1. 认证用户身份(让你登录) 2. 获取用户授权(问你“是否同意?”) 3. 颁发令牌(发放访问令牌和刷新令牌) | 物业中心/门禁系统 - 核实你的业主身份。 - 确认你同意授权。 - 生成并发放临时门禁码(令牌)。 |
| 客户端 | Client | 1. 发起授权请求(引导你去授权服务器) 2. 获取访问令牌(从授权服务器处拿到令牌) 3. 访问受保护资源(拿着令牌去资源服务器取数据) | 快递员 - 向物业申请进入许可。 - 从物业拿到临时门禁码。 - 凭门禁码进入小区,把包裹送到你家。 |
| 资源服务器 | Resource Server | 1. 存放用户资源(存储你的数据,如头像、邮件) 2. 验证访问令牌(检查令牌是否有效、有权限) 3. 返回请求的资源(验证通过后,提供数据) | 你的家和单元楼 - 里面放着你的包裹(资源)。 - 楼门禁系统会验证快递员的门禁码是否有效。 - 验证通过,开门让他把包裹送到你家门口。 |
对应到我们传统的前后端分离项目中,这三种角色一般为:
- 授权服务器: 独立的认证服务,如使用
Spring Authorization Server集成开发的微服务,或是Keylock这种开源方案独立部署,提供认证接口或登录页面。 - 客户端:前端项目,用户登录系统后,前端检测到未登录(无令牌),向授权服务器发起授权请求,授权成功后最终拿到令牌(token)
- 资源服务器:后端项目,使用Spring Security或全局拦截器拦截请求,检测其有没有携带有效的访问令牌,有则放行正常访问接口,没有则返回未授权异常(一般为401)
以上为最标准且经典的一套OAuth2体系,然而在大部分项目中,前端的话语权一般较轻,若前端作为客户端这一角色,意味着前端要完成请求授权服务器授权接口、获取授权码(授权码模式)、用授权码交换令牌这些操作,这对前端开发人员有着较高的要求,虽然也有现成的一些库可使用,但终究是不如spring-security-oauth2-client这种后端库来得成熟,因此,也有另一种架构就是后端兼任上客户端的职责:
- 授权服务器: 同上
- 客户端 + 资源服务器:后端项目,由后端集成
spring-security-oauth2-client或是自行实现client逻辑,前端按经典逻辑来就可以,无需复杂改造。
而这种架构也有一些缺点:首先是如果多个客户端和授权服务器对接,每个客户端都要做一遍重复工作;其次是后端不像前端与浏览器环境直接交互,这会带来很多跨域问题。
在此基础之上,可以进一步优化,抽离出一个代理客户端出来,即代替所有前端以及后端,来向授权服务器发起授权请求以及交换令牌,以及将令牌带到浏览器,指挥重定向,作为一个调度中心,在这种架构下,三种角色又分别对应:
- 授权服务器: 同上
- 客户端:代理服务器,作为所有业务系统的中转和代理
- 资源服务器:后端项目,仅校验令牌(token)即可
而我们一般在做授权服务器的时候不光希望其能授权,更希望其作为一个认证中心,所以授权服务器加代理服务器的职责又可以都做到同一个服务上,成为认证中心服务,本系列也是以这样的架构为背景而进行的一系列实践。
搞懂这些的话,再去网上看其他的讲Spring Authorization Server的博客才不会晕头转向,因为每个人的设计思路不一定是一样的,要根据场景选择合适的模式。
授权模式
清楚了要选择的架构后,再来看另一个核心的问题,上面反复提到授权这个概念,那么客户端是如何向授权服务器完成授权的?在OAuth2体系中,常规的授权模式有以下几种:
| 授权模式 | 核心流程简述 | 安全性 | 典型应用场景 |
|---|---|---|---|
| 授权码模式 | 1. 客户端引导用户至授权服务器 2. 用户同意授权,服务器返回授权码 3. 客户端用授权码向服务器换取访问令牌 | ⭐⭐⭐⭐⭐ (最安全,令牌不暴露于浏览器) | 有后端服务器的传统Web应用 |
| 简化模式 | 1. 客户端引导用户至授权服务器 2. 用户同意授权后,授权服务器直接将访问令牌返回给客户端(通常通过URL片段) | ⭐⭐ (令牌暴露在前端,有泄露风险) | 无后端的纯前端应用(如单页应用SPA) |
| 密码模式 | 用户将用户名和密码直接交给客户端,客户端以此向授权服务器申请令牌 | ⭐ (需高度信任,用户凭证可能泄露) | 仅限于官方或高度信任的内部应用(如同一公司产品) |
| 客户端模式 | 客户端直接使用自身凭证(如Client ID和Secret)向授权服务器申请令牌,无用户参与 | ⭐⭐⭐ (用于服务器间通信,不涉及用户授权) | 微服务间API调用、机器对机器通信 |
我们重点来了解这其中最重要最常用的授权码模式,其核心流程如下:
拆解来说,首先客户端向授权服务器发起授权请求:
GET /authorize?response_type=code&client_id=CLIENT_ID&redirect_uri=REDIRECT_URI&state=STATE&scope=SCOPE167
授权服务器接收到该请求后,会发起重定向到授权服务器的登录页面,用户在该页面输入用户名和密码(常规情况)点击登录后,将会产生一个授权码(code),并将其携带并重定向回客户端,客户端再用这个授权码去请求授权服务器令牌接口:
POST /token HTTP/1.1 Host: authorization-server.com Content-Type: application/x-www-form-urlencoded grant_type=authorization_code&code=AUTHORIZATION_CODE&redirect_uri=REDIRECT_URI&client_id=CLIENT_ID&client_secret=CLIENT_SECRET167
授权服务器校验该授权码后,会向客户端返回令牌:
HTTP/1.1 200 OK Content-Type: application/json { "access_token": "ACCESS_TOKEN", "token_type": "Bearer", "expires_in": 3600, "refresh_token": "REFRESH_TOKEN", "scope": "read" }
到这里授权流程算是结束了。
可以发现,在这个过程中,浏览器会进行客户端 -> 登录页(授权服务器)-> 客户端 这样的重定向过程,这与单点登录的流程不谋而合,也就是说,当使用OAuth2的授权码模式进行认证时,自然而然就实现了单点登录。这也是容易混淆的一点,即 OAuth2 ≠ 单点登录,而是其中的授权码模式恰好能实现单点登录。
我们知道,单点登录仅适用于浏览器环境,如果你还有APP,那这种授权模式就行不通了,后面我们还会讲到如何去扩展授权模式,比如使用手机号+验证码来获得token,这里先有个概念即可,就是说OAuth2不仅可以做单点登录,还可以做各种各样定制化的授权,适用于浏览器、APP各种使用场景。
还有一点就是,现在主流网站都有使用OAuth2协议,如Github、Gitee、微博、微信等,你可以在各种网站的登录页发现可以使用其他社交网站登录的功能,这正是借助授权码模式实现的。
此时,被授权的页面是作为客户端,要去跳转出去的网站又作为授权服务器;如果你的站点也使用OAuth2协议,后面也可以很方便和第三方系统进行单点对接,这正是OAuth2的核心价值所在。
总结
借这篇文章总结和归纳了下Spring Authorization Server及OAuth2协议里的一些核心概念,这在我们正式开搞之前是很重要的,包括现在经常会借助AI查询一些问题,首先要清楚自己开发的功能是涉及到什么角色,自己要去干什么,后面的文章我将展示如何借助Spring Authorization Serve从零来构建一个认证中心服务,包含一个多功能登录页(类似知乎这种):包含帐号密码登录、手机验证码登录、第三方网站登录,再加上各种授权接口:如APP手机验证码授权。
题外话
在决定使用Spring Authorization Server做认证中心之前,也考虑过和Keycloak的对比选型,后者开箱即用功能很强大,但是定制性还是差了一些,而且要想用的好的话也要比较全面的去学习一下它的官方文档,给后面的人交接的复杂性也会增加,最后还是决定选用Spring Authorization Server,能与Spring Cloud生态无缝集成。