一百七十五

ouhou999發表於2020-12-28

簡介
OpenID Connect簡稱為OIDC,已成為Internet上單點登入和身份管理的通用標準。 它在OAuth2上構建了一個身份層,是一個基於OAuth2協議的身份認證標準協議。

OAuth2實際上只做了授權,而OpenID Connect在授權的基礎上又加上了認證。

OIDC的優點是:簡單的基於JSON的身份令牌(JWT),並且完全相容OAuth2協議。

今天我們將會介紹一下OIDC的具體原理。

OpenID Connect是什麼
OpenID Connect釋出於2014年,是建立在OAuth 2.0協議之上的簡單身份層,它允許客戶端基於授權伺服器或身份提供商(IdP)進行的身份驗證來驗證終端使用者的身份,並獲得使用者的相關資訊。

OpenID Connect提供了RESTful HTTP API,並使用Json作為資料的傳遞格式。

之前我們講到了基於XML格式的SAML協議,而OpenID Connect因為其更加簡潔的資料交換格式,被越來越多的應用使用,已經成為事實上的標準。

我們看一下OpenID connect的基本流程:

RP(client)傳送一個認證請求到 OpenID Provider(OP)。

OP對End User進行認證並獲得相應的授權。

OP返回一個ID Token或者access Token給RP。

RP使用access token向UserInfo Endpoint請求使用者資訊。

UserInfo Endpoint返回相應的使用者資訊給RP。

ID Token
ID Token就像是一個使用者的身份證,它是以JWT格式存在的,並且由OP進行簽名,保證它的安全性。

獲取ID Token的方式就是向OP傳送認證請求。

因為ID Token是以JWT格式存在的,JWT可以分為三個部分,分別是Header,Payload和Signature。

這裡我們主要關注一下Payload的json內容:

{
“sub” : “alice”,
“iss” : “https://openid.flydean.com”,
“aud” : “client-12345”,
“nonce” : “n-0S6_WzA2Mj”,
“auth_time” : 1311280969,
“acr” : “c2id.loa.hisec”,
“iat” : 1311280970,
“exp” : 1311281970
}
sub = Subject Identifier:必須。iss提供的EU的唯一標識;最長為255個ASCII個字元;
iss = Issuer Identifier:必須。提供認證資訊者的唯一標識。一般是Url的host+path部分;
aud = Audience(s):必須。標識ID-Token的受眾。必須包含OAuth2的client_id;
nonce:RP傳送請求的時候提供的隨機字串,用來減緩重放攻擊,也可以來關聯ID-Token和RP本身的Session資訊。
auth_time = AuthenticationTime:EU完成認證的時間。如果RP傳送認證請求的時候攜帶max_age的引數,則此Claim是必須的。
acr = Authentication Context Class Reference:可選。表示一個認證上下文引用值,可以用來標識認證上下文類。
iat = Issued At Time:必須。JWT的構建的時間。
exp = Expiration time:必須。ID-Token的過期時間;
上面的是ID Token的標準Claims。

請求ID Token
現在我們知道了ID Token是什麼,那麼在OpenID Connect的RP客戶端如何請求一個ID Token呢?

雖然OpenID Connect並未指定應如何實際驗證使用者身份,這取決於提供者來決定。但是我們通常由Web瀏覽器來執行認證步驟。

瀏覽器將使用者重定向到認證伺服器的認證視窗,使用者輸入使用者名稱和密碼之後,通過OAuth 2.0協議請求ID token。

使用OAuth 2.0來獲取ID Token有3種方式:

Authorization Code模式

Authorization Code流程的步驟如下:

客戶端準備身份認證請求,請求裡包含所需要的引數

客戶端傳送請求到授權伺服器

授權伺服器對最紅使用者進行身份認證

授權服務得終端使用者的統一/授權

授權伺服器把終端使用者傳送回客戶端,同時帶著授權碼

客戶端使用授權碼向Token端點請求一個響應

客戶端接收到響應,響應的Body裡面包含在和ID Token和Access Token

客戶端驗證ID Token,並獲得使用者的一些身份資訊

隱式授權

上圖就是一個隱式授權的例子,和Authorization Code模式不同的是,認證伺服器返回的是一個access token片段,只有這個片段,我們是無法得到access token的。

這裡我們需要額外請求一次client resource伺服器,伺服器將會返回一個script指令碼,通過這個指令碼,我們對access token片段進行解析,得到最終的access token。

混合模式
混合模式比較少用到,它是前面兩種模式的混合,它允許從前端和後端分別獲取token值。

ID Token可以做什麼
那麼我們拿到請求得到的ID Token可以做什麼事情呢?

無狀態session,通過將token儲存在瀏覽器的cookie中,我們可以實現輕量級的無狀態會話。
伺服器端不需要儲存會話資訊,我們只需要在伺服器端對token進行驗證即可。

可以將token傳遞給第三方,因為token本身並不是敏感資訊,所以我們可以將token傳遞給其他應用程式或者後端服務。

令牌互動,我們可以通過ID Token去IdP伺服器中請求access token,從而起到了互動token的目的。

Open Connect認證碼授權的例子
這裡我們舉一個使用認證碼授權獲取到ID token的例子。

RP通過重定向到OpenID Provider的OAuth 2.0認證終端,來初始化一個使用者認證。
下面是一個重定向的例子:

HTTP/1.1 302 Found
Location: https://openid.flydean.com/login?
response_type=code
&scope=openid
&client_id=s6BhdRkqt3
&state=af0ifjsldkj
&redirect_uri=https%3A%2F%2Fclient.flydean.com%2Fcb
response_type:因為我們是認證碼模式,這裡選擇code

scope:openid表示請求的是openid。

client_id:RP的client id,OP通過這個client_id來識別是否是可識別的RP。可以提前註冊或者提前約定。

state:RP生成的一個狀態標準,主要為了防止攻擊。

redirect_uri:認證完畢之後,跳轉的連結。

在OP端,將會檢測是否已經存在一個有效的使用者session,否則將會彈出使用者登入介面,讓使用者登入。

登入成功之後,client將會重定向到redirect_uri,並帶上認證碼:

HTTP/1.1 302 Found
Location: https://client.flydean.com/cb?
code=SplxlOBeZQQYbYS6WxSbIA
&state=af0ifjsldkj
使用code獲取ID token
上面返回的code只是一箇中間產物,RP需要將code提交給OP換取ID token。

這次我們直接使用一個後端的POST請求:

POST /token HTTP/1.1
Host: openid.flydean.com
Content-Type: application/x-www-form-urlencoded
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW

grant_type=authorization_code
&code=SplxlOBeZQQYbYS6WxSbIA
&redirect_uri=https%3A%2F%2Fclient.flydean.com%2Fcb
grant_type:authorization_code表示是授權碼格式
code就是上面一步獲得的code
redirect_uri是callback url
如果成功,OP會返回一個JSON物件,帶有ID token, access token 或者 refresh token:

HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store
Pragma: no-cache

{
“id_token”: “eyJhbGciOiJSUzI1NiIsImtpZCI6IjFlOWdkazcifQ.ewogImlzc
yI6ICJodHRwOi8vc2VydmVyLmV4YW1wbGUuY29tIiwKICJzdWIiOiAiMjQ4Mjg5
NzYxMDAxIiwKICJhdWQiOiAiczZCaGRSa3F0MyIsCiAibm9uY2UiOiAibi0wUzZ
fV3pBMk1qIiwKICJleHAiOiAxMzExMjgxOTcwLAogImlhdCI6IDEzMTEyODA5Nz
AKfQ.ggW8hZ1EuVLuxNuuIJKX_V8a_OMXzR0EHR9R6jgdqrOOF4daGU96Sr_P6q
Jp6IcmD3HP99Obi1PRs-cwh3LO-p146waJ8IhehcwL7F09JdijmBqkvPeB2T9CJ
NqeGpe-gccMg4vfKjkM8FcGvnzZUN4_KSP0aAp1tOJ1zZwgjxqGByKHiOtX7Tpd
QyHE5lcMiKPXfEIQILVq0pc_E2DzL7emopWoaoZTF_m0_N0YzFC6g6EJbOEoRoS
K5hoDalrcvRYLSrQAZZKflyuVCyixEoV9GfNQC3_osjzw2PAithfubEEBLuVVk4
XUVrWOLrLl0nx7RkKU8NXNHq-rvKMzqg”
“access_token”: “SlAV32hkKG”,
“token_type”: “Bearer”,
“expires_in”: 3600,
}
其中ID token的格式是JWT。

User Info
我們獲取到的ID token裡面已經包含了一些非常有用的claims資訊。

事實上ID Token還可以包含其他的user info資訊:

比如name,profile,picture,email,gender,birthdate,phone_number,address等等有用的資訊。

我們可以在token請求的時候新增上額外的scope:

HTTP/1.1 302 Found
Location: https://openid.flydean.com/login?
response_type=code
&scope=openid%20email
&client_id=s6BhdRkqt3
&state=af0ifjsldkj
&redirect_uri=https%3A%2F%2Fclient.flydean.com%2Fcb
比如上面的例子中,我們新增了額外的email資訊,那麼OP將會在token中加入email選項。

比如:

{
“sub” : “alice”,
“email” : “alice@wonderland.net”,
“email_verified” : true,
“name” : “Alice Adams”,
“given_name” : “Alice”,
“family_name” : “Adams”,
“phone_number” : “+86 18888888888”,
“profile” : “https://flydean.com/users/alice”
}