OpenID over OAuth2.0 详解

默认分类,others 2021-07-31 1405 次浏览 次点赞


OpenID over OAuth2.0 详解

logo

需要在Thingsboard上面加OAuth Server功能。

如上是一开始的需求,但是随着深入理解,这里的OAuth Server并不正确,所以需要先科普知识。

I. OAuth2.0

对于OAuth ,参考OAuth2.0,里framework里面并没有定义OAuth Server 角色。它定义了4个角色

  • Resource Ower;

    资源拥有者;

  • Resource Server;

    资源服务器,对外提供访问接口;

  • Client

    客户端,需要访问资源的对象;

  • Authorization Server;

    授权服务器;

提示:上下文需要反复提到 Authorization 和 Athentication,这里我们需要明确定义。首先看一段Spring Security Architecture官方解释。

Application security boils down to two more or less independent problems: authentication (who are you?) and authorization (what are you allowed to do?). Sometimes people say “access control” instead of "authorization", which can get confusing, but it can be helpful to think of it that way because “authorization” is overloaded in other places.

这里Athentication 理解为认证(你是谁?),Authorization 为授权(我能干些什么?)。自然而然,这里OAuth 其实是Authorization。

尽管我们还不知道明确这角色的区分,但是我们需求更接近Authorization Server。

接下来理解OAuth2.0 的协议流程。

+--------+                               +---------------+
|        |--(A)- Authorization Request ->|   Resource    |
|        |                               |     Owner     |
|        |<-(B)-- Authorization Grant ---|               |
|        |                               +---------------+
|        |
|        |                               +---------------+
|        |--(C)-- Authorization Grant -->| Authorization |
| Client |                               |     Server    |
|        |<-(D)----- Access Token -------|               |
|        |                               +---------------+
|        |
|        |                               +---------------+
|        |--(E)----- Access Token ------>|    Resource   |
|        |                               |     Server    |
|        |<-(F)--- Protected Resource ---|               |
+--------+                               +---------------+
提示 : 详细参考 rfc6749#section-1.2 Protocol Flow

看图理解,比较简单。

  • A 客户端发起受保护源请求;
  • B 资源拥有者同意访问,返回授权码;
  • C 客户端通过该授权码继续请求;
  • D 授权服务器分配访问访问令牌;
  • E 客户端通过令牌完成保护资源的访问;
  • F 完成保护资源的访问;

对于过程B,区分不同授权类型(Grant Type),详细参考rfc6749#section-1.3. Authorization Grant,对于OAuth2.0 用以登录,也就是Thingsboard 的OAuth login功能,其实采用了这里的4.1. Authorization Code Grant

在该类型下,OAuth login完整交互流程如下。

+----------+
| Resource |
|   Owner  |
|          |
+----------+
^
|
(B)
+----|-----+          Client Identifier      +---------------+
|         -+----(A)-- & Redirection URI ---->|               |
|  User-   |                                 | Authorization |
|  Agent  -+----(B)-- User authenticates --->|     Server    |
|          |                                 |               |
|         -+----(C)-- Authorization Code ---<|               |
+-|----|---+                                 +---------------+
|    |                                           ^      v
(A)  (C)                                         |      |
|    |                                           |      |
^    v                                           |      |
+---------+                                      |      |
|         |>---(D)-- Authorization Code ---------'      |
|  Client |          & Redirection URI                  |
|         |                                             |
|         |<---(E)----- Access Token -------------------'
+---------+       (w/ Optional Refresh Token)

这里引入User-Agent角色和(B) User authenticates流程,整个流程看起来这里更为复杂。

  • 这里的User-Agent可以理解为浏览器,对于前后端分离的应用,这里指浏览器的前端应用。这也是通过浏览器调试(F12)->网络(Network)功能并不能完整抓取如上流程完整通信。对于D、E 流程,Client到Authorization Server其实是在后台完成的。
  • 而对于User authenticates 其实是上文反复提到的授权是基于认证的,也就是他会在浏览器跳转用户认证页面。

Spring Authorization Server

对于如上流程,我们从本地应用和抓包的来加深理解。

对于Spring Authorization Server ,工程包含如上Client、Authorization Sever、Resouce Sever 不同角色的测试代码包含在[samples]()。

spring-authorization-server/samples/boot/OAuth2-integration$ tree -L 1
.
├── authorizationserver
├── authorizationserver-custom-consent-page
├── client
├── README.adoc
└── resourceserver
对于该工程,这里不做详解,详细参考其README

成功运行该工程后直接从浏览器 网络调试功能(F12->Network)抓包。对于后台这里通过wireshark抓包。

这里直接从HTTP请求概述交互流程。

OAuth2.0 序列图

这里详细概述如上UML序列图的http请求及其参数,完整抓wireshark文件点击下载

提示:如下过程是通过浏览器调试网络(F12->Network)功能抓取。

oauth2/authorizition

这里省略浏览器(User Agent)发起受限资源访问,返回402未授权,直接开始浏览器发起OAuth授权请求到OAuth 客户端,OAuth客户端会返回302,直接重定向到OAuth授权服务器。

http://127.0.0.1:8080/OAuth2/authorization/messaging-client-oidc

oauth2/authorize

浏览器重定向OAuth 客户端授权请求到OAuth 授权服务器。

http://auth-server:9000/OAuth2/authorize?response_type=code&client_id=messaging-client&scope=OpenID&state=0AL57uKp9ITb6Z4bKSl7MXA2xiIC6hIBf_OmeS2sGSM%3D&redirect_uri=http://127.0.0.1:8080/login/OAuth2/code/messaging-client-oidc&nonce=Fja1QhcE00e1jzRI_2uGKuM1XOrZjbuvAgSlQeaxIuQ
keyvalue
response_typecode
client_idmessaging-client
scopeopenid
state0AL57uKp9ITb6Z4bKSl7MXA2xiIC6hIBf_OmeS2sGSM%3D
redirect_urihttp://127.0.0.1:8080/login/oauth2/code/messaging-client-oidc
nonceFja1QhcE00e1jzRI_2uGKuM1XOrZjbuvAgSlQeaxIuQ

login

OAuth授权服务器重定向到认证服务器(这里和授权服务器可以不是同一个),请求登录页面。

http://auth-server:9000/login

login

键入用户账号密码完成认证,认证成功会自动返回302,让浏览器继续进行授权请求。

疑问?为什么这里认证成功了不直接返回授权码?而是需要再进行一次授权请求?

猜测:应该是这里的授权服务和认证服务器可以是不是同一个。

http://auth-server:9000/login
keyname
usernameuser1
passwordpassword
_csrfb994b1be-3aec-4792-9a07-ba1c7e4b12d2

oauth2/authorize

因为上一次授权请求已经重定向到认证服务器完成了认证,这里直接会返回包含授权码的重定向url。

疑问?这一次授权请求和第一次请求所有参数相同,授权服务器如何确定已经认证过的?

回答:spring security 通过SecurityContext实现。

http://auth-server:9000/OAuth2/authorize?response_type=code&client_id=messaging-client&scope=OpenID&state=0AL57uKp9ITb6Z4bKSl7MXA2xiIC6hIBf_OmeS2sGSM%3D&redirect_uri=http://127.0.0.1:8080/login/OAuth2/code/messaging-client-oidc&nonce=Fja1QhcE00e1jzRI_2uGKuM1XOrZjbuvAgSlQeaxIuQ
keyvalue
response_typecode
client_idmessaging-client
scopeopenid
state0AL57uKp9ITb6Z4bKSl7MXA2xiIC6hIBf_OmeS2sGSM%3D
redirect_urihttp://127.0.0.1:8080/login/oauth2/code/messaging-client-oidc
nonceFja1QhcE00e1jzRI_2uGKuM1XOrZjbuvAgSlQeaxIuQ

login/oauth2/code

浏览器访问重定向url,将授权码发送给OAuth 客户端。

http://127.0.0.1:8080/login/OAuth2/code/messaging-client-oidc?code=fKRMDYWIgkbrhoPVD-5IIMuK5opn5d1Su2d8xVCipDovbRxsRgL6x--NqG4BH30ASGtolZWToWV1XhXO8O92zz7LhLi-iszy4vJPyiS3AQ0Kmzyj7ZtRd2LEToLfwFzC&state=0AL57uKp9ITb6Z4bKSl7MXA2xiIC6hIBf_OmeS2sGSM%3D
keyvalue
codefKRMDYWIgkbrhoPVD-5IIMuK5opn5d1Su2d8xVCipDovbRxsRgL6x--NqG4BH30ASGtolZWToWV1XhXO8O92zz7LhLi-iszy4vJPyiS3AQ0Kmzyj7ZtRd2LEToLfwFzC
state0AL57uKp9ITb6Z4bKSl7MXA2xiIC6hIBf_OmeS2sGSM%3D
提示: 接下来的流程通过wireshark抓取。

oauth2/token

OAuth 客户端通过授权码获取Access Token。

http://auth-server:9000/OAuth2/token
keyvalue
grant_typeauthorization_code
codefKRMDYWIgkbrhoPVD-5IIMuK5opn5d1Su2d8xVCipDovbRxsRgL6x--NqG4BH30ASGtolZWToWV1XhXO8O92zz7LhLi-iszy4vJPyiS3AQ0Kmzyj7ZtRd2LEToLfwFzC
redirect_urihttp://127.0.0.1:8080/login/oauth2/code/messaging-client-oidc

response

keyvalue
access_tokeneyJraWQiOiJiNDY3OTRhMy0wNDllLTRkYTYtYWIxZi1kY2IwM2E4NzM5MmMiLCJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ1c2VyMSIsImF1ZCI6Im1lc3NhZ2luZy1jbGllbnQiLCJuYmYiOjE2Mjc3MzAxNTYsInNjb3BlIjpbIm9wZW5pZCJdLCJpc3MiOiJodHR
refresh_tokennHcV1-SEeoTfZSZlf_CLf1ag2BE36V1sahiGiFp8o0048kLR86wDN9QLCADAB2PLrneJivT4JG3lliUVP1WCHdn7ywMaAZT3eSW8fIjfNFEr95R0Rcxz3qhjybgiB3zP
scopeopenid
id_tokeneyJraWQiOiJiNDY3OTRhMy0wNDllLTRkYTYtYWIxZi1kY2IwM2E4NzM5MmMiLCJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ1c2VyMSIsImF1ZCI6Im1lc3NhZ2luZy1jbGllbnQiLCJhenAiOiJtZXNzYWdpbmctY2xpZW50IiwiaXNzIjoiaHR0cDpcL1wvYXV0aC1.eyJzdWIiOiJ1c2VyMSIsImF1ZCI6Im1lc3NhZ2luZy1jbGllbnQiLCJhenAiOiJtZXNzYWdpbmctY2xpZW50IiwiaXNzIjoiaHR0cDpcL1wvYXV0aC1
token_typeBearer
expires_in299

oatuh2/jwks

OAuth 客户端获取Access Token其他信息。

http://127.0.0.1:9000/OAuth2/jwks
keyvalue
keys[{"kty":RSA},{"e":AQAB},]{"kid":b46794a3-049e-4da6-ab1f-dcb03a87392c},{"n":jZqBsnPZ5mbSwKsr7b6jZ3FNY0dh96ta_xyGTG0i0JrQ1UCJKoTlN_lIJ_jGu3OVeMko5K3pruyCAySm_vQa2qhUm1RDUxG_WZS9WjCo07bLb76K0u6f8e3EQVaJAnadmkpK4ijVqeBDIs0WyuvEnuh3Jk58H46tWwSEQYyVnyaLUP8aRSqh0GW3AInG-LMnJoCV6cFBIIKsKyMGdyjs}]

Access Token

对于Access Token,官方描述具有scope,会逾期,比较难理解。其实只需要将Access Token解码后就一目了然。

eyJraWQiOiJiNDY3OTRhMy0wNDllLTRkYTYtYWIxZi1kY2IwM2E4NzM5MmMiLCJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ1c2VyMSIsImF1ZCI6Im1lc3NhZ2luZy1jbGllbnQiLCJuYmYiOjE2Mjc3MzAxNTYsInNjb3BlIjpbIm9wZW5pZCJdLCJpc3MiOiJodHR
  • header

    {
      "kid": "b46794a3-049e-4da6-ab1f-dcb03a87392c",
      "typ": "JWT",
      "alg": "RS256"
    }
  • payload

    "{\"sub\":\"user1\",\"aud\":\"messaging-client\",\"nbf\":1627730156,\"scope\":[\"OpenID\"],\"iss\":\"htt"
  • signature

security

这里梳理整个OAuth2.0 的安全设计。

  • 首先需要说明的是整过过程可以抓包分析,是因为调试需要,我们采用的http而不是https;
  • Access Token 的请求和都是在后台完成,签名和验签都在Authorization Server自己完成,不存在私钥泄露风险;
  • client id、secret,redirect url 预分配,需要一一对应。

II. OpenID

当我们把如上测试的Authorization Server用以thingsboard login的时候,出现报错,提示请求userinfo 出错。

从而引出一个新的标准 OpenID。不难理解如上过程OAuth2.0 只是完成授权,授权后拉取用户信息,同时使用使用该用户信息在当前系统注册新用户并且跳转登录是oatuh login,都是OpenID完成约定的。

+--------+                                   +--------+
|        |                                   |        |
|        |---------(1) AuthN Request-------->|        |
|        |                                   |        |
|        |  +--------+                       |        |
|        |  |        |                       |        |
|        |  |  End-  |<--(2) AuthN & AuthZ-->|        |
|        |  |  User  |                       |        |
|   RP   |  |        |                       |   OP   |
|        |  +--------+                       |        |
|        |                                   |        |
|        |<--------(3) AuthN Response--------|        |
|        |                                   |        |
|        |---------(4) UserInfo Request----->|        |
|        |                                   |        |
|        |<--------(5) UserInfo Response-----|        |
|        |                                   |        |
+--------+                                   +--------+

在OpenID规范里,这里已经转换角色。RP作为OpenID 客户端,OP作为OpenID Provider,服务提供者。

  • (1)、(2)、(2) 已经比较熟悉了,对应OAuth2.0流程A-E流程。
  • (4)、(5) 作为OpenID流程,也就是在OAuth2.0 获取AcessToken之后主动获取用户信息。

而对于userinfo 端点点在当前的Spring Authorization Server 还不支持,我们可以使用OpenID认证的开源库测试。

提示:当前最新版本0.2.2 已经支持。

本文由 Jay 创作,采用 知识共享署名 3.0,可自由转载、引用,但需署名作者且注明文章出处,点赞4

只有地板了

  1. lijie
    lijie

    进行了以此校对,补充了交互流程的302重定向交互,修订了一些交互错误。

添加新评论