Spring Authorization Server 是一个框架,它提供了 OAuth 2.1 和 OpenID Connect 1.0 规范以及其他相关规范的实现。它建立在 Spring Security 之上,为构建 OpenID Connect 1.0 身份提供者和OAuth2授权服务器产品提供了一个安全、轻量级和可定制的基础。说白了,Spring Authorization Server 就是一个认证(授权)服务器。
官方文档
1. 发展
因为随着网络和设备的发展,原先的 OAuth 2.0 已经不能满足现今的需求了,OAuth 社区对 OAuth 2.0 中的几种授权模式进行了取舍和优化,并增加一些新的特性, 于是推出了 OAuth 2.1,而原先的 Spring Security OAuth 2.0 使用的是 OAuth 2.0 协议,为满足新的变化,Spring Security 团队重新写了一套叫 Spring Authorization Server 的认证授权框架来替换原先的 Spring Security OAuth 2.0。从官网中可以看到,原先的 Spring Security OAuth 2.0 已从 Spring Security 目录下被移除,接着是多出 Spring Authorization Server 作为单独项目。
2. OAuth2
2.1 OAuth2是什么
- “Auth” 表示 “授权” Authorization
- “O” 是 Open 的简称,表示 “开放”
- 连在一起就表示 “开放授权”,OAuth2是一种开放授权协议。
2.2 OAuth2的角色
OAuth 2协议包含以下角色:
- 资源所有者(Resource Owner):即用户,资源的拥有人,想要通过客户应用访问资源服务器上的资源。
- 客户应用(Client):通常是一个Web或者无线应用,它需要访问用户的受保护资源。
- 资源服务器(Resource Server):存储受保护资源的服务器或定义了可以访问到资源的API,接收并验证客户端的访问令牌,以决定是否授权访问资源。
- 授权服务器(Authorization Server):负责验证资源所有者的身份并向客户端颁发访问令牌。
2.3 OAuth2的使用场景
1. 开放系统间授权
- 社交登录
在传统的身份验证中,用户需要提供用户名和密码,还有很多网站登录时,允许使用第三方网站的身份,这称为"第三方登录"。所谓第三方登录,实质就是 OAuth 授权。用户想要登录 A 网站,A 网站让用户提供第三方网站的数据,证明自己的身份。获取第三方网站的身份数据,就需要 OAuth 授权。最常用的就是用微信、QQ、微博等信息去登录一些其他网站,比如:用微信登录iconfont,用QQ登录千库网等。 - 开放API
对接过微信、支付宝等接口的同学应该都知道。一般都会先登录拿到token,然后带token去请求接口。这个过程就是OAuth2的使用,只不过OAuth2服务器由微信或者支付宝提供。
2. 现代微服务安全
像政府政务部门,多个部门若整合在一起,当登陆后可以获取不同信息
3. 企业内部多应用
企业一般有ERP系统,还有生产业务系统等,当我们进行SSO单点登录后,可以访问不同系统。
2.4 OAuth2的四种授权模式
- 授权码模式(authorization-code)
- 简化模式(隐藏式)(implicit)
- 密码模式(password)
- 客户端模式(client credentials)
注意,不管哪一种授权方式,第三方应用申请令牌之前,都必须先到系统备案,说明自己的身份,然后会拿到两个身份识别码:客户端 ID(client ID)和客户端密钥(client secret)。这是为了防止令牌被滥用,没有备案过的第三方应用,是不会拿到令牌的。
2.4.1 授权码模式
授权码(authorization code),指的是第三方应用先申请一个授权码,然后再用该码获取令牌。
这种方式是最常用,最复杂,也是最安全的,它适用于那些有后端的 Web 应用,一般是2家不同企业对接。授权码通过前端传送,令牌则是储存在后端,而且所有与资源服务器的通信都在后端完成。这样的前后端分离,可以避免令牌泄漏。
// 第一步:A前端请求B授权码
https://b.com/oauth/authorize?
response_type=code&
client_id=CLIENT_ID&
redirect_uri=CALLBACK_URL&
scope=read
// 第二步:B返回授权码到A的前台地址
https://a.com/callback?code=AUTHORIZATION_CODE
// 第三步:A前端发送code到A后台,A后台去B请求令牌
https://b.com/oauth/token?
client_id=CLIENT_ID&
client_secret=CLIENT_SECRET&
grant_type=authorization_code&
code=AUTHORIZATION_CODE&
redirect_uri=CALLBACK_URL
// 第四步:B返回令牌数据
{
"access_token":"ACCESS_TOKEN",
"token_type":"bearer",
"expires_in":2592000,
"refresh_token":"REFRESH_TOKEN",
"scope":"read"
}
2.4.2 简化模式
有些 Web 应用是纯前端应用,没有后端。这时就不能用上面的方式了,必须将令牌储存在前端。RFC 6749 就规定了第二种方式,允许直接向前端颁发令牌。这种方式没有授权码这个中间步骤,所以称为(授权码)"隐藏式"(implicit)。
这种方式把令牌直接传给前端,是很不安全的。因此,只能用于一些安全要求不高的场景,并且令牌的有效期必须非常短,通常就是会话期间(session)有效,浏览器关掉,令牌就失效了。
// 第一步:A 提供一个链接,要求用户跳转到 B 网站,授权用户数据给 A 网站使用
https://b.com/oauth/authorize?
response_type=token&
client_id=CLIENT_ID&
redirect_uri=CALLBACK_URL&
scope=read
// 第二步:用户跳转到 B 网站,登录后同意给予 A 网站授权。这时,B 网站就会跳回redirect_uri参数指定的跳转网址,并且把令牌作为 URL 参数,传给 A 网站。
https://a.com/callback#token=ACCESS_TOKEN
// 注意,令牌的位置是 URL 锚点(fragment),而不是查询字符串(querystring),这是因为 OAuth 2.0 允许跳转网址是 HTTP 协议,因此存在"中间人攻击"的风险,而浏览器跳转时,锚点不会发到服务器,就减少了泄漏令牌的风险。
2.4.3 密码模式
密码模式一般是企业内部应用使用的,比如前台登陆,应该使用这个最多了,我们在开发自己的系统时,也是用的这个。
区别于 简化模式,密码模式 需要带 账号和密码,还有需要将client ID和client security放入请求头:
第一步:用账号密码直接请求token
// header
Authorization: Basic Base64.encode(client ID:client security)
// 请求
https://b.com/oauth/token?
grant_type=password&
username=USERNAME&
password=PASSWORD&
scope=read
第二步: 拿到token
{
"access_token":"ACCESS_TOKEN",
"token_type":"bearer",
"expires_in":7200,
"refresh_token":"REFRESH_TOKEN"
}
2.4.4 客户端模式
凭证式(client credentials):也叫客户端模式,适用于没有前端的命令行应用,即在命令行下请求令牌。
这种方式给出的令牌,是针对第三方应用的,而不是针对用户的,即有可能多个用户共享同一个令牌。由于不涉及用户,一般是服务器对服务器。
另外,当有refresh_token
时,我们可以用refresh_token
定时更新token,防止过期。
3. Spring Authorization Server模式
上面说了,Spring Authorization Server 提供了 OAuth 2.1 和 OpenID Connect 1.0 规范以及其他相关规范的实现。与2.0相比,有一些不同:
- OAuth 2.1去掉了OAuth2.0中的密码模式、简化模式,
- OAuth 2.1增加了设备授权码模式
- OAuth 2.1对授权码模式增加了PKCE扩展
3.1 客户端模式
OAuth2.0 的客户端模式在 OAuth 2.1 中被保留了下来,客户端模式更形象的理解,应该叫“合作方模式”更为贴切,因为整个数据交互过程中,至始至终都只有服务器提供方和服务调用方,全程都未有用户的参与。客户端模式交互过程,见上文 OAuth2.0 客户端模式的讲解。
3.2 授权码模式
授权码模式交互过程,见上文 OAuth2.0 授权码模式的讲解。这里要说的是授权码模式如何拓展 PKCE。
在授权码模式的交互工程中,有一个环节比较薄弱,这个环节就是认证服务器返回授权码的过程中,如果恶意程序截取到授权码,那么恶意程序就可以进而通过授权码窃取令牌了。为了减轻这种攻击,官方增加PKCE扩展,先来看一下官方的交互图。
- 客户端通过“/oauth2/authorize”地址向认证服务器发起获取授权码请求的时候增加两个参数,即
code_challenge
和code_challenge_method
,其中,code_challenge_method 是加密方法(例如:S256 或 plain),code_challenge
是使用code_challenge_method
加密方法加密后的值。
- 客户端通过“/oauth2/authorize”地址向认证服务器发起获取授权码请求的时候增加两个参数,即
- 认证服务器给客户端返回授权码,同时记录下
code_challenge
、code_challenge_method
的值。
- 认证服务器给客户端返回授权码,同时记录下
- 客户端使用 code 向认证服务器获取
Access Token
的时候,带上code_verifier
参数,其值为步骤1加密前的初始值。
- 客户端使用 code 向认证服务器获取
- 认证服务器收到步骤3的请求时,将
code_verifier
的值使用code_challenge_method
的方法进行加密,然后将加密后的值与步骤A中的code_challenge
进行比较,看看是否一致。
上面交互过程中,恶意程序如果在2处截获授权码后,使用授权码向认证服务器换取 Access Token,但由于恶意程序没有code_verifier
的值,因此在认证服务器无法校验通过,从而获取Access Token
失败。
- 认证服务器收到步骤3的请求时,将
3.3 设备授权码模式
设备授权码模式,是一种为解决不便在当前设备上进行文本输入而提供的一种认证授权模式,例如:智能电视、媒体控制台、数字相框、打印机等。大家也可以脑补一下一些扫码登录的情形。使用设备授权码模式,有以下要求。
(1) 该设备已连接到互联网。
(2) 设备能够支持发出 HTTPS 请求。
(3) 设备能够显示或以其他通信方式将 URI 和 Code 发给用户。
(4) 用户有辅助设备(如个人电脑或智能手机),他们可以从中处理请求。
设备授权码登录官网交互图如下。
- 客户端带上包含客户端信息的参数向认证服务器(地址:/oauth2/device_authorization)发起授权访问。
- 认证服务器给客户端返回设备码、用户码及需要用户验证用户码的 URI。
- 客户端指示用户需要在另一设备进行访问授权的URI和用户码。
- 用户根据URI打开页面,输入用户码和确认授权,向认证服务器发起认证请求。
- 客户端在完成步骤3 之后就开始带上客户端信息和设备码向认证服务器轮询获取令牌信息。
- 认证服务器收到客户端使用设备码获取令牌信息的请求后,检查用户是否已提交授权确认,如果用户已提交授权确认,则返回令牌信息。
3.4 拓展授权模式
OAuth2.1 也提供拓展授权模式的操作实现。虽然 OAuth2.1 移除了密码模式(password),但是通过拓展授权模式可以实现密码模式。在实际应用中,客户端、认证服务器、资源服务器往往都是同一家公司的产品,那么这个时候,使用账号、密码进行登录的情形也比较常见,此时就需要通过拓展授权模式来实现账号、密码登录了。可以查看自定义授权模式官网文档地址
4. OpenID Connect 1.0
OpenID Connect 1.0 是 OAuth 2.0 协议之上的一个简单的身份层。其实就是客户端向认证服务器请求认证授权的时候,多返回一个 id_token,该 id_token 是一串使用 jwt 加密过的字符串。
ID Token(身份令牌)和 Access Token(访问令牌)是两个不同的令牌,它们在身份验证和访问控制的流程中发挥不同的作用。我现在只使用Access Token(访问令牌)