博客與筆記無法實時同步, 筆記最新鏈接
OAuth 2.0
定義:OAuth 2.0是一個開放授權標准:允許資源所有者(用戶)授權第三方應用訪問該用戶在某服務上的特定私有資源,但不提供賬號密碼給第三方應用。
安全提示:授權碼和所有令牌必須通過Tsl加密傳輸。進入OAuth 2.0官網
OAuth 2.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 ---| | +--------+ +---------------+ (OAuth 2.0協議流程) (A)客戶端發起授權請求,客戶端可以向資源所有者直接發起授權請求,建議的做法是將授權服務器作為中間人,客戶端向授權服務器發起授權請求 (B)客戶端收到授權憑證(和具體的授權類型有關) (C)客戶端通過授權憑證向授權服務器請求訪問令牌 (D)授權服務器驗證授權憑證和客戶端,如果通過驗證,返回給客戶端訪問令牌 (E)客戶端通過訪問令牌向資源服務器發出訪問請求 (F)資源服務器驗證訪問令牌有效后,返回請求的資源
授權碼模式
授權碼模式:code的生命周期必須短暫、或者只能單次使用,如果code被多次使用,授權服務器必須使該code生成的所有令牌失效。訪問令牌由client后端保存。
+----------+ | 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) (A)客戶端引導用戶代理(瀏覽器)到達授權終結點,並攜帶參數:response_type(code)、client_id、redirect_uri、scope、state (B)授權服務器通過用戶代理(瀏覽器)驗證資源所有者,並確定資源所有者是否給予客戶端授權 (C)驗證資源所有者成功,並且允許授權,授權服務器將用戶代理(瀏覽器)重定向到redirect_uri,並返回code和授權請求的state (D)客戶端向授權服務器令牌終結點請求令牌,攜帶參數:grant_type、code、redirect_uri、client_id、secret(可選) (E)授權服務器驗證code、client_id、secret是否有效,並驗證redirect_uri是否與步驟C中一致,如果驗證成功,攜帶Access Token將用戶代理(瀏覽器)重定向到redirect_uri
簡化模式
要點:不支持刷新令牌,訪問令牌編碼在Url中,所以會有暴露的風險(在Oauth2.0官網中已經不推薦使用)
+----------+ | Resource | | Owner | | | +----------+ ^ | (B) +----|-----+ Client Identifier +---------------+ | -+----(A)-- & Redirection URI --->| | | User- | | Authorization | | Agent -|----(B)-- User authenticates -->| Server | | | | | | |<---(C)--- Redirection URI ----<| | | | with Access Token +---------------+ | | in Fragment | | +---------------+ | |----(D)--- Redirection URI ---->| Web-Hosted | | | without Fragment | Client | | | | Resource | | (F) |<---(E)------- Script ---------<| | | | +---------------+ +-|--------+ | | (A) (G) Access Token | | ^ v +---------+ | | | Client | | | +---------+ (A)客戶端引導用戶代理(瀏覽器)到達授權終結點,並攜帶參數:response_type(token)、client_id、redirect_uri、scope、state (B)授權服務器通過用戶代理(瀏覽器)驗證資源所有者,並確定資源所有者是否給予客戶端授權 (C)驗證資源所有者成功,並且允許授權,授權服務器將用戶代理(瀏覽器)重定向到redirect_uri,redirect_uri的URL 錨點(fragment)部分包括了響應參數(以#hash值的方式追加):access_token、token_type、expires_in、scope、state (D)用戶代理(瀏覽器)向redirect_uri發出請求,但不包括URL 錨點,用戶代理在本地保存了fragment。 (E)redirect_uri的服務器返回頁面給瀏覽器,該頁面需要包含解碼fragment的腳本。 (F)瀏覽器接收到頁面后,執行腳本,將C中的響應參數解碼出來 (G)用戶代理(瀏覽器)將響應參數傳遞給客戶端
資源所有者密碼模式
用戶將用戶名及密碼提供給可信任的第三方應用,第三方應用向令牌終結點請求令牌。常用於第一方應用進行登陸. 優點:應用不需要存儲用戶賬號、密碼,通過刷新令牌保持持久登陸,降低密碼泄露的風險。
+----------+ | Resource | | Owner | | | +----------+ v | Resource Owner (A) Password Credentials | v +---------+ +---------------+ | |>--(B)---- Resource Owner ------->| | | | Password Credentials | Authorization | | Client | | Server | | |<--(C)---- Access Token ---------<| | | | (w/ Optional Refresh Token) | | +---------+ +---------------+ Figure 5: Resource Owner Password Credentials Flow (A) 資源所有者向客戶端提供其用戶名及密碼 (B) 客戶端攜帶用戶名、密碼向授權服務器的令牌終結點發起請求 (C) 授權服務器授權客戶端,並驗證用戶名、密碼是否有效。如果有效,返回訪問令牌
#請求token
Request:
POST /connect/token HTTP/1.1
Host: idsrv-server.com
Content-type: application/x-www-form-urlencoded
body:
{
grant_type:password
username:dd
password:dd
client_id:eshopOnVue
scope:orders(可選參數)
}
#請求刷新令牌:原刷新令牌失效、之前頒發的access_token不受影響(需要實現手動失效)
Request:
POST /connect/token HTTP/1.1
Host: idsrv-server.com
Content-type: application/x-www-form-urlencoded
body:
{
grant_type:refresh_token
refresh_token:e4364377ec69c8d5c06a49d7b74efbd2a29015ac37e9ede8e17597d348931d32
client_id:eshopOnVue
}
Respose:
{
"id_token": "eyJhbGciO.iJSUzI1NiI.sImtpZCw",
"access_token": "eyJhb.GciOiJSUz.I1NiIsIm",
"expires_in": 3600,
"token_type": "Bearer",
"refresh_token": "60e7dda6e30473ce6dc0a1656b38c174a74ef73310d"
}
#通過access_token請求用戶終結點(需要scope:profile):/connect/userinfo
客戶端憑證模式
客戶端直接使用自身的憑證向授權服務器終結點請求訪問令牌。只能用於可信的客戶端。不支持刷新令牌、無法訪問用戶資源scope(openid、profile、email等)
+---------+ +---------------+ | | | | | |>--(A)- Client Authentication --->| Authorization | | Client | | Server | | |<--(B)---- Access Token ---------<| | | | | | +---------+ +---------------+ (A) 客戶端向授權服務器令牌終結點請求訪問令牌 (B) 授權服務器對客戶端進行認證,如果成功,返回訪問令牌
Request:
POST /connect/token HTTP/1.1 #請求方式只能為post
Host: idsrv-server.com
Content-type: application/x-www-form-urlencoded #參數只能放在body里面
body:
{
grant_type:client_credentials
client_id:ClientCredentials
client_secret:iwiaXNzIjoibnVsbCIsImF1ZCI6WyJudWxsL3Jlc291cmNlcyIsIm9yZGVycyJdLCJjbGllbnRfaWQiOiJDb
scope:orders openid(可選,默認請求所有scope)
}
Response:
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store
Pragma: no-cache
{
"access_token":"MTQ0NjJkZmQ5OTM2NDE1ZTZjNGZmZjI3",
"token_type":"bearer",
"expires_in":3600
}
OpenID Connect(OIDC)
定義:它在OAuth2上構建了一個身份層,是一個基於OAuth2協議的身份認證標准協議
OIDC協議流程圖
+--------+ +--------+ | | | | | |---------(1) AuthN Request-------->| | | | | | | | +--------+ | | | | | | | | | | | End- |<--(2) AuthN & AuthZ-->| | | | | User | | | | RP | | | | OP | | | +--------+ | | | | | | | |<--------(3) AuthN Response--------| | | | | | | |---------(4) UserInfo Request----->| | | | | | | |<--------(5) UserInfo Response-----| | | | | | +--------+ +--------+ EU:End User:一個人類用戶。 RP:Relying Party ,用來代指OAuth2中的受信任的客戶端,身份認證和授權信息的消費方; OP:OpenID Provider,有能力提供EU認證的服務(比如OAuth2中的授權服務),用來為RP提供EU的身份認證信息; ID Token:JWT格式的數據,包含EU身份認證的信息。 UserInfo Endpoint:用戶信息接口(受OAuth2保護),當RP使用Access Token訪問時,返回授權用戶的信息,此接口必須使用HTTPS。 (1).RP發送一個認證請求給OP; (2).OP對EU進行身份認證,然后提供授權; (3).OP把ID Token和Access Token(需要的話)返回給RP; (4).RP使用Access Token發送一個請求到UserInfo EndPoint; (5).UserInfo EndPoint返回EU的Claims。
OIDC在OAuth之上的擴展
- [ ] Scope:openid(用來區分這是一個OIDC的Authentication請求,而不是OAuth2的Authorization請求)
- [ ] response_type:id_token,在implicit模式下請求id_token
- [ ] id_token:身份令牌,是一個授權服務器提供的包含用戶信息(由一組Cliams構成以及其他輔助的Cliams)的JWT格式的數據結構
- [ ] UserInfo Endpoint:通過id_token從用戶信息終結點獲得一組EU相關的Claims,比如可以將Claims從token移除,避免token過大,只保留sub,通過UserInfo Endpoint查詢Claims
"response_type"參數值 | OIDC授權類型 |
---|---|
code | Authorization Code Flow |
id_token token | Implicit Flow |
code id_token | Hybrid Flow |
- Authorization Code Flow:從授權終結點返回code,從令牌終結點返回token
- Implicit Flow:從授權終結點返回所有token
- Hybrid Flow:id_token返回給前端,access_token在后端保存(access_token的安全性要求比id_token)高
JSON Web Token
定義:JWT是一個定義一種緊湊的,自包含的並且提供防篡改機制的傳遞數據的方式的標准協議
JWT格式組成
JWT由3部分構成:header.payload.signature
在IdSrv中,payload中的鍵值對根據token類型和授權流程的不同有所區別
header:
{
"alg": "RS256",//簽名算法
"kid": "9dcf733a1192a6da053e64c6ee22ff87",
"typ": "JWT"//token類型
}
payload://需要傳遞的數據
{
"nbf": 1556591630,//該jwt在此之前無效
"exp": 1556595230,//該jwt在此之后無效
"iss": "http://localhost:7102",//jwt頒發者
"iat": 1516239022,//jwt頒發時間
"aud": "http://localhost:7102/resources",//jwt接收者
"client_id": "jsImplicit",
"sub": "SubjectId",//用戶唯一id
"auth_time": 1556591629,//授權時間
"idp": "local",//identityProvider
"name": "Username",
"scope": [
"openid",
"profile"
],
"amr": [
"pwd"//authenticationMethod
]
}
signature://token生成方使用私匙生成token,token消費方使用用公匙驗證token是否被修改過
RSASHA256(
base64UrlEncode(header) + "." +base64UrlEncode(payload),Public Key,Private Key
)
#RSA簽名過程
#1.將消息內容進行base64編碼:base64UrlEncode(header) + "." +base64UrlEncode(payload)
#2.對編碼后的內容進行SHA256哈希計算(不可逆)
#3.對hash值使用privateKey加密,加密后內容作為簽名
#4.token接收方通過publicKey對簽名進行解密得到hash值,並將內容進行hash計算,比較兩個hash值,確保消息未被篡改
expires_in="exp"-"nbf"
RSA非對稱加密算法
1 RSA算法特點:
1.1 公鑰私鑰對等,可以互換
1.2 私鑰需要保存在可靠方
1.3 無法從公鑰計算出私鑰(理論上從私鑰也無法計算出公鑰,例外:openssl的公鑰e固定為65537,且私鑰文件中含有額外的參數,可以從私鑰計算出公鑰)
2 RSA應用
2.1 加密:消息發送方:公鑰加密=>消息接收方:私鑰解密(用於加密傳遞消息)
##缺點:無法防止偽造消息
2.2 簽名:消息發送方:私鑰加密=>消息接收方:公鑰解密(用於消息簽名,防止消息被篡改)
##缺點:消息明文傳輸
HTTPS簡單流程
1.服務器通過非對稱算法生成私鑰公鑰
2.服務方將公鑰提供給相關機構(CA)
3.CA生成證書並頒發給服務器:
{
證書發布機構CA
證書有效期
公鑰
證書所有者
簽名(簽名方法與JWT類似,私鑰屬於CA)
...
}
4.服務器將證書發送給客戶端:
{
1.TCP三次握手
2.建立tunnel
3.client hello(包括SessionId,可以避免重新握手,並重新使用已有對話密鑰)
4.server hello
5.發送Certificate給客戶端
}
5.客戶端通過系統中內置的CA公鑰驗證證書的合法性
6.客戶端通過服務方公鑰與服務器協商對稱加密算法與密鑰
7.進行對稱加密通信
參考:https://zhuanlan.zhihu.com/p/22142170 https://zhuanlan.zhihu.com/p/27395037
IdentityServer4
授權碼模式
PKCE(Proof Key for Code Exchange)
利用不可逆算法,確保在被竊取了授權碼或其他密匙的情況下,也無法向授權服務器換取訪問令牌
PKCE流程:
1.客戶端隨機生成一串字符:
{
code_verifier=base64url(RandomString),
code_challenge=base64url(sha256(code_verifier))
}
2.客戶端攜帶code_challenge向授權服務器發起授權請求
3.授權服務器對客戶端進行認證成功后返回授權碼,並保存code_challenge
4.客戶端獲取到授權碼之后,攜帶code_verifier向令牌終結點發起請求,換取Access Token
5.授權服務器驗證授權碼,將code_verifier進行sha256計算並url編碼后與code_challenge對比,如果一致,頒發訪問令牌
授權碼模式流程
#Step 1 客戶端向授權服務器授權終結點發起請求:
GET /connect/authorize HTTP/1.1
Host: Idsrv.com,
Query String Parameters:
{
client_id: Swagger_UI
redirect_uri: http://localhost:9528/Callback
response_type: code
scope: openid profile orders
state: 668ae852a74f4923ad140d79d2f10fee
code_challenge: i2CnOeIHTBZZrAsgzEZV3-KpMTb_OCvl05ydETjrqIc
code_challenge_method: S256
}
state:客戶端隨機生成的字符串,授權服務器在重定向到redirect_uri時會原樣返回,客戶端檢查state是否相同,來防止CSRFF攻擊
#step 2 授權服務器對客戶端進行認證,認證成功,授權服務器返回302重定向並攜帶ReturnUrl參數:
Response:
{
Status Code: 302 Found,
Location: http://localhost:6102/Account/Login?ReturnUrl=%2Fconnect%2Fauthorize%2Fcallback%3Fclient_id%3DSwagger_UI%26redirect_uri%3Dhttp%253A%252F%252Flocalhost%253A9528%252FCallback%26response_type%3Dcode%26scope%3Dopenid%2520profile%2520orders%26state%3D668ae852a74f4923ad140d79d2f10fee%26code_challenge%3Di2CnOeIHTBZZrAsgzEZV3-KpMTb_OCvl05ydETjrqIc%26code_challenge_method%3DS256
}
#step 3 /Account/Login請求返回登陸頁面,並將ReturnUrl寫入登陸頁面
#step 4 用戶在登陸頁面發起登陸請求:
參數:
{
1.用戶名
2.密碼
3.ReturnUrl
}
#step 4 授權服務器驗證用戶名、密碼成功,且ReturnUrl有效:
{
1.通過HttpContext.SignInAsync為當前請求上下文頒發登陸憑證:
Set-Cookie:
{
idsrv.session:'8c9e9e80f92da2551c77dc6ab03c69ca,path=/'
idsrv:'CfDJ8IOXoUULE4dDgZ02v48m533Xg,expires=Mon,08 Jul 2019 08:38:26 GMT, path=/,httponly'
}
2.發起重定向到ReturnUrl
}
#step 5 在 /connect/authorize/callback 終結點:
{
授權服務器更新Cookie:idsrv,並重定向到redirect_uri,攜帶以下參數:
{
code: 21a9deaac4457e29f669a91bb36795c048aae5680b49ae3a1ffafa50aff0d169
scope: openid profile orders
state: 668ae852a74f4923ad140d79d2f10fee
session_state: ZcDaWfAmzNGLsXS3-1ofTvNGryU-KxeurTpPLxP6oF0.89cd4e2083165f0701dbd2181ede5b7b(會話狀態)
}
}
#step 6 在redirect_uri向令牌終結點請求訪問令牌:
POST /connect/token HTTP/1.1
Host: Idsrv.com,
Content-type: application/x-www-form-urlencoded
body:
{
client_id: Swagger_UI
code: 21a9deaac4457e29f669a91bb36795c048aae5680b49ae3a1ffafa50aff0d169
redirect_uri: http://localhost:9528/Callback
code_verifier: dde8c25afc8d42728225154fea0aa098556671d3344c423f9007f1895b47dbf2be2116c7c5f34720842383ec9a7e0a66
grant_type: authorization_code
}
#step 7 授權服務器驗證code和code_verifier,並頒發訪問令牌
刷新訪問令牌
配置new Oidc.UserManager()時 設置scope=offline_access,直接通過刷新令牌請求訪問令牌。offline_access優先於silent_redirect_uri
通過Iframe頁面進行靜默刷新:
#step 1 通過Iframe頁面向授權終結點發起請求:
GET /connect/authorize HTTP/1.1
Host: Idsrv.com,
Cookie:
{
idsrv.session=8c9e9e80f92da2551c77dc6ab03c69ca;
idsrv=CfDJ8IOXoUULE4dDgZ02v48m53JzoKhJcuEwwXcdvHRYodIZ2nTuD
}
Query String Parameters:
{
client_id: Swagger_UI
redirect_uri: http://localhost:9528/SilentCallback
response_type: code
scope: openid profile orders
state: 54207b37bb644f90800d0993d5a5c210
code_challenge: 8bOHunvRggEM9m3Hwb-8m24KIiRV9rPSbz0OOvTP7D0
code_challenge_method: S256
prompt: none
id_token_hint: eyJhbGciOiJSUzI1NiIsImtp2UFyLw
}
id_token_hint://為之前獲取的id_token
prompt:none//用來指示授權服務器是否引導用戶重新認證和同意授權
#step 2 重定向到redirect_uri,攜帶以下參數:
{
code: a956650cd653debe11989d225c2caa2619521b9176b444fbcc83d3a1663bf1ed
scope: openid profile orders
state: 54207b37bb644f90800d0993d5a5c210
session_state: SGmLH8gIy6VAPtdlT6_zQtix_VM229bPkpY0OpwQ6fc.229a9d1e0be856145c035db0aa6033cc
}
#step 3 在redirect_uri頁面執行new Oidc.UserManager().signinSilentCallback()
#step 4 向令牌終結點請求訪問令牌
登出操作:
Js客戶端mgr.signoutRedirect()
1.向IdSrv請求/connect/endsession 並攜帶以下兩個參數
id_token_hint:
post_logout_redirect_uri: 登出回調地址
2.重定向到/Account/Logout 並攜帶生成的logoutId參數
3.在Logout里清除await HttpContext.SignOutAsync() 並跳轉到post_logout_redirect_uri