OAuth2(二)——实现

目录

OAuth客户端

向授权服务器注册 OAuth 客户端

使用授权码许可类型获取令牌

发送授权请求

处理授权响应

使用 state 参数添加跨站保护

使用令牌访问受保护资源

刷新访问令牌

OAuth 受保护资源

解析 HTTP 请求中的 OAuth 令牌

根据数据存储验证令牌

根据令牌提供内容

不同的权限范围对应不同的操作

不同的权限范围对应不同的数据结果

不同的用户对应不同的数据结果

额外的访问控制

OAuth 授权 服务器

管理 OAuth 客户端注册

对客户端授权

授权端点

客户端授权

令牌颁发

对客户端进行身份认证

处理授权许可请求

支持刷新令牌

增加授权范围的支持

OAuth2的4种授权模式

授权码模式(authorization code)

隐式授权模式/简化模式(implicit)

优点:

缺点:

适用场景

密码模式(resource owner password credentials)

优点:

缺点:

应用场景:

客户端凭据模式(client credentials)


OAuth客户端

向授权服务器注册 OAuth 客户端

OAuth 客户端(一般是一个应用)由一个称为“客户端标识符”的特殊字符串来标识(一般:client_id),每个客户端的标识符必须唯一,因此,客户端标识符几乎总是由授权服务器来分配。还需要一个客户端凭据(一般用client_secret标识),用于与授权服务器交互时对自身进行身份认证。

客户端 需要知道授权端点令牌端点的位置,除此之外不需要知道有关服务器的任何其他信息。

使用授权码许可类型获取令牌

OAuth 客户端要从授权服务器获取令牌,需要资源拥有者以某种形式授权。我们 将使用一种被称为授权码许可类型的交互式授权形式,由客户端将资源拥有者重定向授权服务器授权端点。然后,服务器通过 redirect_uri 将授权码返回 给客户端。最后,客户端将收到的授权码发送到授权服务器的令牌端点,换取 OAuth 访问令牌, 再进行解析和存储。

 

发送授权请求

为了启动授权流程,需要将用户重定向至授权服务器的授权端点,并在授权端点的 URL 中 包含所有适当的查询参数。

处理授权响应

并从 code 参数中读取授权服务器返回 的授权码。请记住,授权服务器通过重定向让浏览器向客户端发起请求,而不是直接响应客户端 请求。拿到这个授权码,并使用 HTTP POST 方法将其直接发送至令牌端点。

 

为什么在这个请求中包含 redirect_uri?毕竟此处是不需要执行重定向的。根据 OAuth 规范,如果在授权请求中指定了重定向 URI,那么令牌请求中也必须包含该重定向 URI。 这可以防止攻击者使用被篡改的重定向 URI 获取受害用户的授权码,让并无恶意的客户端将受 害用户的资源访问权限关联到攻击者账户。

还需要添加一些请求头来标识这是一个 HTTP 表单格式的请求,并使用 HTTP 基本认证对客 户端进行身份认证。在 HTTP 基本认证中,Authorization 头部是一个 Base64 编码的字符串, 编码的内容是拼接后的用户名和密码,以冒号分隔。OAuth 2.0 要求将客户端 ID 作为用户名,将 客户端密钥作为密码,但使用之前应该先对它们分别进行 URL 编码。

 
 

使用 state 参数添加跨站保护

客户端接受收到的 code 值,会试图将其发送给授权服务器。这意味着攻击者可能会用客户端向授权服务器暴力搜索有效的授权码,浪费客户端和授权服务器资源,而且还有可能导致客户端获取一个从未请求过的令牌。

可以使用一个名为 state 的可选 OAuth 参数来缓解这个问题,将该参数设置为一个随机值,并在应用中用一个变量保存它。在丢弃旧的访问令牌之后,我们会创建一个 state 值。
 

使用令牌访问受保护资源

现在已经有了一个访问令牌,户端要做的就是使用令牌向受保护资源发出调用请求,有 3 个合法的位置可以用于携带

令牌。在客户端中,使用 HTTP Authorization 头部来传递令牌,这是规范推荐尽可能使用的 方法。
 

我们得到的这种访问令牌叫作 bearer 令牌,它意味着无论是谁,只要持有该令牌就可以向 受保护资源出示OAuth bearer 令牌使用规范明确给出了发送令牌值的 3 种方法:

使用 HTTP Authorization 头部;

使用表单格式的请求体参数;

使用 URL 编码的查询参数。

使用 Authorization 头部是这 3种方法中最灵活和最安全的,但是对于某些客户端来说, 使用起来很困难。一个健壮的 OAuth 客户端或服务端库应该完整地提供这 3 种方式,以适应 不同情况。

 

刷新访问令牌

现在已经可以使用访问令牌访问受保护资源了,但是如果访问令牌过期了怎么办呢?还要再 次劳烦用户为客户端应用授权吗?

OAuth 2.0 提供了一种在无须用户参与的情况下获取新访问令牌的方法: 刷新令牌。这是一 项很重要的功能,因为用户在初次授权完成之
 
后不会一直在场,而 OAuth 经常要在这样的情况下使用
 

OAuth 受保护资源

 

解析 HTTP 请求中的 OAuth 令牌

解析 HTTP 请求中的 OAuth 令牌。首先,尝试从请求中获取 Authorization 头部(如果请求中包含),然后检查它是否包含 OAuth bearer 令牌,将头部中的 bearer 关键字与后跟的空格去掉,获取令牌值。接下来,要处理通过表单参数传递的令牌,表单参数在请求主体中。最后一种方法是通过查询参数传递令牌。

OAuth bearer 令牌使用规范规定,在使用 HTTP Authorization 头部传递令牌时,HTTP 头的值以关键字 Bearer 开头,后跟一个空格,再跟令牌值本身。而且,OAuth 规范还规定 Bearer 关键字不区分大小写。此外,HTTP 规范还规定了 Authorization 头部关键字本身不区分大小 写。

根据数据存储验证令牌

根据令牌提供内容

很多 API 设计中, 不同的操作需要不同的访问权限。还有一些 API 会根据授权者不同而返回不同的结果,或者根据 不同权限返回某一部分信息。

不同的权限范围对应不同的操作

不同的权限范围对应不同的数据结果

不同的用户对应不同的数据结果

额外的访问控制

 

OAuth 授权 服务器

管理 OAuth 客户端注册

OAuth 服务器需要为每一个客户端分配唯一的客户端标识符。

对客户端授权

OAuth 协议要求授权服务器提供两个端点:授权端点,运行在前端信道上;令牌端点,运行 在后端信道上。
 

授权端点

授权端点是一个前端信道端点,客户端会 将用户浏览器重定向至该端点,以发出授权请求。

首先,需要确定发出请求的是哪一个客户端。客户端会在请求中以 client_id 传递其标识符。

接下来,需要检查客户端是否存在。

最后,如果客户端通过检查,则需要渲染出一个页面来请求用户授权。用户需要与这个页面 交互,并向授权服务器提交授权决策,这将需要浏览器向授权服务器再发送一个 HTTP 请求。

客户端授权

令牌颁发

生成的授权码通过客户端的重定向 URI 返回来了。客户端拿到授权码 之后,向授权服务器的令牌端点发出一个 POST 请求。这属于后端信道通信,是在客户端和授权 服务器之间进行的,不需要用户浏览器参与。

对客户端进行身份认证

处理授权许可请求

首先,应该检查 grant_type 参数,以确保收到的许可类型是我们所支持的。验证授权码是否有效(需要验证是否颁发给此客户端),如果有效生成令牌(需要存储)。

支持刷新令牌

增加授权范围的支持

通过scope(以空格分隔的字符串)返回权限范围。

 

OAuth2的4种授权模式

授权码模式(authorization code)

授权码模式(authorization code)是功能最完整、流程最严密的授权模式,code保证了token的安全性,即使code被拦截,由于没有app_secret,也是无法通过code获得token的。

1、用户访问客户端(一个应用),客户端通过用户代理(一般指浏览器)向认证服务器请求授权码;

客户端:http://localhost:8882/  访问 受限资源 被重定向

Request URL: http://localhost:8882/securedPage
Request Method: GET
Status Code: 302 
Remote Address: [::1]:8882
Referrer Policy: no-referrer-when-downgrade

定向到本服务login:

Location: http://localhost:8882/login
Pragma: no-cache
Set-Cookie: JSESSIONID=4E3CFEF7BABEBDE1ACF8F66AF70291ED; Path=/; HttpOnly
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block

 本服务login又被重定向到授权端点。

Request URL: http://localhost:8882/login
Request Method: GET
Status Code: 302 
Remote Address: [::1]:8882
Referrer Policy: no-referrer-when-downgrade
Location: http://localhost:8881/auth/oauth/authorize?client_id=cloud&redirect_uri=http://localhost:8882/login&response_type=code&state=PGNoFC
Pragma: no-cache
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block

授权端点会重定向到授权服务的login

Request URL: http://localhost:8881/auth/oauth/authorize?client_id=cloud&redirect_uri=http://localhost:8882/login&response_type=code&state=PGNoFC
Request Method: GET
Status Code: 302 Found
Remote Address: [::1]:8881
Referrer Policy: no-referrer-when-downgrade
Location: http://localhost:8881/auth/login
Pragma: no-cache
Set-Cookie: JSESSIONID=n1kUeYp97i90b7D3dtlDn7PevVnyEZo8Hhxqi3tN; path=/auth
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block

  在此步会产生授权服务器的SESSIONID(JSESSIONID=n1kUeYp97i90b7D3dtlDn7PevVnyEZo8Hhxqi3tN)

携带参数:

lient_id: cloud
redirect_uri: http://localhost:8882/login
response_type: code
state: PGNoFC

2、用户同意授权(资源所有者操作);

Cookie: JSESSIONID=n1kUeYp97i90b7D3dtlDn7PevVnyEZo8Hhxqi3tN; JSESSIONID=4E3CFEF7BABEBDE1ACF8F66AF70291ED
Host: localhost:8881
Referer: http://localhost:8882/

 执行一个POST LOGIN请求,又重定向到授权端点。

Request URL: http://localhost:8881/auth/login
Request Method: POST
Status Code: 302 Found
Remote Address: [::1]:8881
Referrer Policy: no-referrer-when-downgrade
Location: http://localhost:8881/auth/oauth/authorize?client_id=cloud&redirect_uri=http://localhost:8882/login&response_type=code&state=PGNoFC
Pragma: no-cache
Set-Cookie: JSESSIONID=rAkHws2uu-SFx_eFmnGysNrC4h2jotWlemLnAaS8; path=/auth
X-Content-Type-Options: nosniff

 cookie 重新设置。

3、认证服务器通过用户代理返回授权码给客户端;

equest URL: http://localhost:8881/auth/oauth/authorize?client_id=cloud&redirect_uri=http://localhost:8882/login&response_type=code&state=PGNoFC
Request Method: GET
Status Code: 303 See Other
Remote Address: [::1]:8881
Referrer Policy: no-referrer-when-downgrade

 携带授权码返回客户端

Request URL: http://localhost:8882/login?code=tNQKUP&state=PGNoFC
Request Method: GET
Status Code: 302 
Remote Address: [::1]:8882
Referrer Policy: no-referrer-when-downgrade

4、客户端携带授权码向认证服务器请求访问令牌(AccessToken);

5、认证服务器返回访问令牌;

6、客户端携带访问令牌向资源服务器请求资源;

7、资源服务器返回资源。

隐式授权模式/简化模式(implicit)

implicit模式(隐式模式)和授权码模式(authorization_code)访问差不多,相比之下,少了一步获取code的步骤,而是直接获取token。

授权码许可流程中各个步骤的关键是不同组件之间保持信息隔离。通过这种方式,浏览器接触不到只应由客户端掌握的信息,客户端也无法得知浏览器的状态。但是如果把客户端放在浏览器内部运行会怎么样呢?

客户端无法对浏览器隐藏任何秘密,因为浏览器对客户端的任何动作都了如指掌。在这种情况下,通过浏览器向客户端传递仅用于换
取令牌的授权码就没有任何实际意义了,因为这个额外的保密层没有起到任何作用。

隐式许可类型没有使用这个额外的保密层,而是直接从授权端点返回令牌。因此隐式许可类型只使用前端信道和授权服务器通信。这种授权许可流程对内嵌在网站上的JavaScript 应用非常有用,这些应用需要在不同安全域中进行经过授权的、可能受限的会话共享。

客户端向授权服务器的授权端点发送请求时,使用的方式与授权码流程相同,只不过 response_type 参数的值为 token,而不是 code。这样会通知授权服务器直接生成令牌,而 不是生成一个用于换取令牌的授权码。

优点:

  • 简单

缺点:

  • 不能存储获取refresh_token的必要新,不能获取refresh_token
  • token暴露风险

适用场景

应用只有页面,没有后台管理,只能适用第三方验证。例如问卷调查,评论。

为web浏览器应用设计

密码模式(resource owner password credentials)

也是拿凭据(授权服务的凭据)获取token,与隐式授权模式的区别,登录界面是客户端提供的,客户端会持有凭据

支持refresh token。

优点:

  • 不需要多次请求转发,额外开销,同时可以获取更多的用户信息。(拿到账号密码了)

缺点:

  • 局限性,认证服务器和应用方必须有超高的信赖。

应用场景:

  • 自家公司搭建的认证服务器

  • 遗留项目升级为oauth2的适配方案

客户端凭据模式(client credentials)

如果没有明确的资源拥有者,或对于客户端软件来说资源拥有者不可区分,该怎么办?这是 一种相当常见的场景,比如后端系统之间需要直接通信,但是它们并不一定代表某个特定用户。

这是一种最简单的模式,只要client请求,我们就将AccessToken发送给它。这种模式是最方便但最不安全的模式。因此这就要求我们对client完全的信任,而client本身也是安全的。不支持refresh token,主要是没有必要。

因此这种模式一般用来提供给我们完全信任的服务器端服务。在这个过程中不需要用户的参与

示例: https://gitee.com/demon75520/spring-security-oauth-demo#spring-security-oauth-demo

猜你喜欢

转载自blog.csdn.net/demon7552003/article/details/107643886