微服务统一认证与授权的 Go 语言实现
各位讀者朋友鼠年大吉,祝各位新的一年身體健康,萬事如意!
最近疫情嚴(yán)重,是一個(gè)特殊時(shí)期,大家一定要注意防護(hù)。很多省份推遲了企業(yè)開工的時(shí)間,大部分的互聯(lián)網(wǎng)公司也都是下周開始遠(yuǎn)程辦公。大家可以利用在家的幾天時(shí)間學(xué)習(xí)充電,反正也出不去(🙂🙂🙂)。
今天筆者要寫得是 Go 微服務(wù)相關(guān)的組件實(shí)踐,筆者在好幾年前就接觸 Go 語言,去年開始從事 Go 微服務(wù)相關(guān)的開發(fā),在過程中也和小伙伴聯(lián)合編寫了一本 《Go 高并發(fā)與微服務(wù)實(shí)戰(zhàn)》書籍,即將出版上市。本文是截取其中的搶先版閱覽,介紹微服務(wù)統(tǒng)一認(rèn)證與授權(quán)的 Go 語言實(shí)現(xiàn)。
1 前言
統(tǒng)一認(rèn)證與授權(quán)是微服務(wù)架構(gòu)的基礎(chǔ)功能,微服務(wù)架構(gòu)不同于單體應(yīng)用的架構(gòu),認(rèn)證和授權(quán)非常集中。當(dāng)服務(wù)拆分之后,對(duì)各個(gè)微服務(wù)認(rèn)證與授權(quán)變得非常分散,所以在微服務(wù)架構(gòu)中,將集成統(tǒng)一認(rèn)證與授權(quán)的功能,作為橫切關(guān)注點(diǎn)。
2 常見的認(rèn)證與授權(quán)方案
常見的認(rèn)證與授權(quán)方案有 OAuth、分布式 Session、OpenID 和 JWT 等,下面我們將分別介紹這四種方案。
2.1 OAuth
OAuth2 相關(guān)理論的介紹主要來自于OAuth2官方文檔,相關(guān)地址為https://tools.ietf.org/html/rfc6749。
OAuth 協(xié)議的目的是為了為用戶資源的授權(quán)提供一個(gè)安全的、開放而簡(jiǎn)易的標(biāo)準(zhǔn)。官網(wǎng)中的介紹如下:
An open protocol to allow secure API authorization in a simple and standard method from web, mobile and desktop applications.
OAuth1 由于不被 OAuth2 兼容,且簽名邏輯過于復(fù)雜和授權(quán)流程的過于單一,在此不過多談?wù)?#xff0c;以下重點(diǎn)關(guān)注OAuth2認(rèn)證流程,它是當(dāng)前Web應(yīng)用中的主流授權(quán)流程。
OAuth2是當(dāng)前授權(quán)的行業(yè)標(biāo)準(zhǔn),其重點(diǎn)在于為Web應(yīng)用程序、桌面應(yīng)用程序、移動(dòng)設(shè)備以及室內(nèi)設(shè)備的授權(quán)流程提供簡(jiǎn)單的客戶端開發(fā)方式。它為第三方應(yīng)用提供對(duì)HTTP服務(wù)的有限訪問,既可以是資源擁有者通過授權(quán)允許第三方應(yīng)用獲取HTTP服務(wù),也可以是第三方以自己的名義獲取訪問權(quán)限。
角色
OAuth2 中主要分為了4種角色
- resource owner 資源所有者,是能夠?qū)κ鼙Wo(hù)的資源授予訪問權(quán)限的實(shí)體,可以是一個(gè)用戶,這時(shí)會(huì)被稱為end-user。
- resource server 資源服務(wù)器,持有受保護(hù)的資源,允許持有訪問令牌(access token)的請(qǐng)求訪問受保護(hù)資源。
- client 客戶端,持有資源所有者的授權(quán),代表資源所有者對(duì)受保護(hù)資源進(jìn)行訪問。
- authorization server 授權(quán)服務(wù)器,對(duì)資源所有者的授權(quán)進(jìn)行認(rèn)證,成功后向客戶端發(fā)送訪問令牌。
在很多時(shí)候,資源服務(wù)器和授權(quán)服務(wù)器是合二為一的,在授權(quán)交互的時(shí)候是授權(quán)服務(wù)器,在請(qǐng)求資源交互是資源服務(wù)器。但是授權(quán)服務(wù)器是單獨(dú)的實(shí)體,它可以發(fā)出被多個(gè)資源服務(wù)器接受的訪問令牌。
協(xié)議流程
首先看一張來自官方提供的流程圖:
+--------+ +---------------+| |--(1)- Authorization Request ->| Resource || | | Owner || |<-(2)-- Authorization Grant ---| || | +---------------+| || | +---------------+| |--(3)-- Authorization Grant -->| Authorization || Client | | Server || |<-(4)----- Access Token -------| || | +---------------+| || | +---------------+| |--(5)----- Access Token ------>| Resource || | | Server || |<-(6)--- Protected Resource ---| |+--------+ +---------------+這是一張關(guān)于OAuth2角色的抽象交互流程圖,主要包含以下的6個(gè)步驟:
客戶端授權(quán)類型
為了獲取訪問令牌,客戶端必須獲取到資源所有者的授權(quán)許可。OAuth2默認(rèn)定了四種授權(quán)類型,當(dāng)然也提供了用于定義額外的授權(quán)類型的擴(kuò)展機(jī)制。默認(rèn)的四種授權(quán)類型為:
- authorization code 授權(quán)碼類型
- implicit 簡(jiǎn)化類型(也稱為隱式類型)
- resource owner password credentials 密碼類型
- client credential 客戶端類型
下面對(duì)常用的授權(quán)碼類型和密碼類型進(jìn)行詳細(xì)的介紹。
授權(quán)碼類型
授權(quán)碼類型(authorization code)通過重定向的方式讓資源所有者直接與授權(quán)服務(wù)器進(jìn)行交互來進(jìn)行授權(quán),避免了資源所有者信息泄漏給客戶端,是功能最完整、流程最嚴(yán)密的授權(quán)類型,但是需要客戶端必須能與資源所有者的代理(通常是Web瀏覽器)進(jìn)行交互,和可從授權(quán)服務(wù)器中接受請(qǐng)求(重定向給予授權(quán)碼),授權(quán)流程如下:
+----------+| Resource || Owner || |+----------+^|(2)+----|-----+ Client Identifier +---------------+| -+----(1)-- & Redirection URI ---->| || User- | | Authorization || Agent -+----(2)-- User authenticates --->| Server || | | || -+----(3)-- Authorization Code ---<| |+-|----|---+ +---------------+| | ^ v(1) (3) | || | | |^ v | |+---------+ | || |>---(4)-- Authorization Code ---------' || Client | & Redirection URI || | || |<---(5)----- Access Token -------------------'+---------+ (w/ Optional Refresh Token)密碼類型
密碼類型(resource owner password credentials)需要資源所有者將密碼憑證交予客戶端,客戶端通過自己持有的信息直接向授權(quán)服務(wù)器獲取授權(quán)。在這種情況下,需要資源所有者對(duì)客戶端高度可信任,同時(shí)客戶端不允許保存密碼憑證。這種授權(quán)類型適用于能夠獲取資源所有者的憑證(credentials)(如用戶名和密碼)的客戶端。授權(quán)流程如下:
+----------+| Resource || Owner || |+----------+v| Resource Owner(1) Password Credentials|v+---------+ +---------------+| |>--(2)---- Resource Owner ------->| || | Password Credentials | Authorization || Client | | Server || |<--(3)---- Access Token ---------<| || | (w/ Optional Refresh Token) | |+---------+ +---------------+令牌刷新
客戶端從授權(quán)服務(wù)器中獲取的訪問令牌(access token)一般是具備失效性的,在訪問令牌過期的情況下,持有有效用戶憑證的客戶端可以再次向授權(quán)服務(wù)器請(qǐng)求訪問令牌,但是如果不持有用戶憑證的客戶端可以通過和上次訪問令牌一同返回的刷新令牌(refresh token)向授權(quán)服務(wù)器獲取新的訪問令牌。
2.2 分布式 Session
2.2.1 什么是 Session,什么是 Cookie?
HTTP 協(xié)議是無狀態(tài)的協(xié)議。一旦數(shù)據(jù)交換完畢,客戶端與服務(wù)器端的連接就會(huì)關(guān)閉,再次交換數(shù)據(jù)需要建立新的連接。這就意味著服務(wù)器無法從連接上跟蹤會(huì)話。
會(huì)話,指用戶登錄網(wǎng)站后的一系列動(dòng)作,比如瀏覽商品添加到購物車并購買。會(huì)話(Session)跟蹤是 Web 程序中常用的技術(shù),用來跟蹤用戶的整個(gè)會(huì)話。常用的會(huì)話跟蹤技術(shù)是 Cookie 與 Session。
Cookie 實(shí)際上是一小段的文本信息。客戶端請(qǐng)求服務(wù)器,如果服務(wù)器需要記錄該用戶狀態(tài),就使用 response 向客戶端瀏覽器頒發(fā)一個(gè) Cookie。客戶端會(huì)把 Cookie 保存起來。
當(dāng)瀏覽器再請(qǐng)求該網(wǎng)站時(shí),瀏覽器把請(qǐng)求的網(wǎng)址連同該 Cookie 一同提交給服務(wù)器。服務(wù)器檢查該 Cookie,以此來辨認(rèn)用戶狀態(tài)。服務(wù)器還可以根據(jù)需要修改 Cookie 的內(nèi)容。
Session 是另一種記錄客戶狀態(tài)的機(jī)制,不同的是 Cookie 保存在客戶端瀏覽器中,而 Session 保存在服務(wù)器上。客戶端瀏覽器訪問服務(wù)器的時(shí)候,服務(wù)器把客戶端信息以某種形式記錄
在服務(wù)器上。這就是 Session。客戶端瀏覽器再次訪問時(shí)只需要從該 Session 中查找該客戶的狀態(tài)就可以了。
每個(gè)用戶訪問服務(wù)器都會(huì)建立一個(gè) session,那服務(wù)器是怎么標(biāo)識(shí)用戶的唯一身份呢?事實(shí)上,用戶與服務(wù)器建立連接的同時(shí),服務(wù)器會(huì)自動(dòng)為其分配一個(gè) SessionId。
簡(jiǎn)單來說,Cookie 通過在客戶端記錄信息確定用戶身份,Session通過在服務(wù)器端記錄信息確定用戶身份。
2.3 OpenID
某些站點(diǎn)看到允許以 OpenID 的方式登陸,如使用 Facebook 賬號(hào)或者 Google 賬號(hào)登陸站點(diǎn)。
OpenID 和 OAuth 很像。但本質(zhì)上來說它們是截然不同的兩個(gè)東西:
- OpenID: 只用于 身份認(rèn)證(Authentication),允許你以 同一個(gè)賬戶 在 多個(gè)網(wǎng)站登陸。它僅僅是為你的 合法身份 背書,當(dāng)你以 Facebook 賬號(hào)登陸某個(gè)站點(diǎn)之后,該站點(diǎn) 無權(quán)訪問 你的在 Facebook 上的 數(shù)據(jù)。
- OAuth: 用于 授權(quán)(Authorisation),允許 被授權(quán)方 訪問 授權(quán)方 的 用戶數(shù)據(jù)。
2.4 JWT
JWT,JSON Web Token,作為一個(gè)開放的標(biāo)準(zhǔn),通過緊湊(compact,快速傳輸,體積小)或者自包含(self-contained,payload中將包含用戶所需的所有的信息,避免了對(duì)數(shù)據(jù)庫的多次查詢)的方式,定義了用于在各方之間發(fā)送的安全JSON對(duì)象。
為什么要介紹JWT,因?yàn)镴WT可以很好的充當(dāng)在上一節(jié)介紹的訪問令牌(access token)和刷新令牌(refresh token)的載體,這是Web雙方之間進(jìn)行安全傳輸信息的良好方式。當(dāng)只有授權(quán)服務(wù)器持有簽發(fā)和驗(yàn)證JWT的secret,那么就只有授權(quán)服務(wù)器能驗(yàn)證JWT的有效性以及發(fā)送帶有簽名的JWT,這就唯一保證了以JWT為載體的token的有效性和安全性。
JWT的組成
JWT格式一般如下:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiY2FuZyB3dSIsImV4cCI6MTUxODA1MTE1NywidXNlcklkIjoiMTIzNDU2In0.IV4XZ0y0nMpmMX9orv0gqsEMOxXXNQOE680CKkkPQcs
它由三部分組成,每部分通過.分隔開,分別是:
- Header 頭部
- Payload 有效負(fù)荷
- Signature 簽名
接著我們對(duì)每一部分進(jìn)行詳細(xì)的介紹。
Header
頭部通常由兩部分組成:
- typ 類型,一般為jwt。
- alg 加密算法,通常是HMAC SHA256或者RSA。
一個(gè)簡(jiǎn)單的頭部例子如下:
{"alg": "HS256""typ": "JWT" }然后這部分JSON會(huì)被Base64Url編碼用于構(gòu)成JWT的第一部分:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
Playload
有效負(fù)載是JWT的第二部分,是用來攜帶有效信息的載體,主要是關(guān)于用戶實(shí)體和附加元數(shù)據(jù)的聲明,由以下三部分組成:
- Registered claims 注冊(cè)聲明,這是一組預(yù)定的聲明,但并不強(qiáng)制要求,提供了一套有用的、能共同使用的聲明。主要有iss(JWT簽發(fā)者),exp(JWT過期時(shí)間),sub(JWT面向的用戶),aud(接受JWT的一方)等。
- Public claims 公開聲明 公開聲明中可以添加任何信息,一般是用戶信息或者業(yè)務(wù)擴(kuò)展信息等。
- Private claims 私有聲明 被JWT提供者和消費(fèi)者共同定義的聲明,既不屬于注冊(cè)聲明也不屬于公開聲明。
一般不建議在payload中添加任何的敏感信息,因?yàn)锽ase64是對(duì)稱解密的,這意味著payload中的信息的是可見的。
一個(gè)簡(jiǎn)單的有效負(fù)荷例子:
{"name": "cang wu","exp": 1518051157,"userId": "123456" }這部分JSON會(huì)被Base64Url編碼用于構(gòu)成JWT的第二部分:
eyJuYW1lIjoiY2FuZyB3dSIsImV4cCI6MTUxODA1MTE1NywidXNlcklkIjoiMTIzNDU2In0
Signature
要?jiǎng)?chuàng)建簽名,必須需要被編碼后的頭部、被編碼后的有效負(fù)荷、一個(gè)secret,最后通過在頭部的定義的加密算法alg加密生成簽名,生成簽名的偽代碼如下:
HMACSHA256(base64UrlEncode(header) + "." +base64UrlEncode(payload),secret)用到的加密算法為HMACSHA256
secret是保存在服務(wù)端用于驗(yàn)證JWT以及簽發(fā)JWT,所以必須只由服務(wù)端持有,不該流露出去。
一個(gè)簡(jiǎn)單的簽名如下:
IV4XZ0y0nMpmMX9orv0gqsEMOxXXNQOE680CKkkPQcs
這將成為JWT的第三部分。
最后這三部分通過.分割,組成最終的JWT,如下所示:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiY2FuZyB3dSIsImV4cCI6MTUxODA1MTE1NywidXNlcklkIjoiMTIzNDU2In0.IV4XZ0y0nMpmMX9orv0gqsEMOxXXNQOE680CKkkPQcs
3 授權(quán)服務(wù)器
3.1 整體架構(gòu)
經(jīng)過以上的簡(jiǎn)單介紹,我們已經(jīng)了解了目前常見的統(tǒng)一認(rèn)證與鑒權(quán)的方案,接下來我們將基于 OAuth2 協(xié)議和 JWT 實(shí)現(xiàn)一套簡(jiǎn)單的認(rèn)證和授權(quán)系統(tǒng)。系統(tǒng)主要由兩個(gè)服務(wù)組成,授權(quán)服務(wù)器和資源服務(wù)器,它們之間的交互圖 11-4 所示:
客戶端想要訪問資源服務(wù)器中用戶持有的資源信息,首先需要攜帶用戶憑證向授權(quán)服務(wù)器請(qǐng)求訪問令牌。授權(quán)服務(wù)器在驗(yàn)證過客戶端和用戶憑證的有效性后,它將返回生成的訪問令牌給客戶端。接著客戶端攜帶訪問令牌向資源服務(wù)器請(qǐng)求對(duì)應(yīng)的用戶資源,在資源服務(wù)器通過授權(quán)服務(wù)器驗(yàn)證過訪問令牌有效后,將返回對(duì)應(yīng)的用戶資源。
很多時(shí)候,授權(quán)服務(wù)器和資源服務(wù)器是合二為一,即可以頒發(fā)訪問令牌,也對(duì)用戶資源受限訪問;也可以將它們的職責(zé)劃分得更加詳細(xì),授權(quán)服務(wù)器主要負(fù)責(zé)令牌的頒發(fā)和令牌的驗(yàn)證,而資源服務(wù)器負(fù)責(zé)對(duì)用戶資源進(jìn)行保護(hù),僅允許持有有效訪問令牌的請(qǐng)求訪問受限資源。
授權(quán)服務(wù)器的主要職責(zé)有頒發(fā)訪問令牌和驗(yàn)證訪問令牌,對(duì)此我們需要對(duì)外提供兩個(gè)接口:
- /oauth/token 用于客戶端攜帶用戶憑證請(qǐng)求訪問令牌
- /oauth/check_token 用于驗(yàn)證訪問令牌的有效性,返回訪問令牌對(duì)應(yīng)的客戶端和用戶信息。
一般來講,每一個(gè)客戶端都可以為用戶申請(qǐng)?jiān)L問令牌,因此一個(gè)有效的訪問令牌是和客戶端、用戶綁定的,這表示某一用戶授予某一個(gè)客戶端訪問資源的權(quán)限。
我們接下來實(shí)現(xiàn)的授權(quán)服務(wù)器主要包含以下模塊,如圖 11-5 所示:
- ClientDetailsService,用于提供獲取客戶端信息;
- UserDetailsService,用于獲取用戶信息;
- TokenGrant,用于根據(jù)授權(quán)類型進(jìn)行不同的驗(yàn)證流程,并使用 TokenService 生成訪問令牌;
- TokenService,生成并管理令牌,使用 TokenStore 存儲(chǔ)令牌;
- TokenStore,負(fù)責(zé)令牌的存儲(chǔ)工作。
鑒于篇幅所限,我們的授權(quán)服務(wù)器僅提供密碼類型獲取訪問令牌,但是提供了簡(jiǎn)便的可擴(kuò)展的機(jī)制,讀者可以根據(jù)自己的需要進(jìn)行擴(kuò)展實(shí)現(xiàn)。
3.2 用戶服務(wù)和客戶端服務(wù)
用戶服務(wù)和客戶端服務(wù)的作用類型,都是根據(jù)對(duì)應(yīng)的唯一標(biāo)識(shí)加載用戶和客戶端信息,用于接下來的用戶信息和客戶端信息的校驗(yàn)。我們定義的用戶信息和客戶端信息結(jié)構(gòu)體如下:
type UserDetails struct {// 用戶標(biāo)識(shí)UserId int// 用戶名 唯一Username string// 用戶密碼Password string// 用戶具有的權(quán)限Authorities []string } // 驗(yàn)證用戶名和密碼是否匹配 func (userDetails *UserDetails)IsMatch(username string, password string) bool {return userDetails.Password == password && userDetails.Username == username }type ClientDetails struct {// client 的標(biāo)識(shí)ClientId string// client 的密鑰ClientSecret string// 訪問令牌有效時(shí)間,秒AccessTokenValiditySeconds int// 刷新令牌有效時(shí)間,秒RefreshTokenValiditySeconds int// 重定向地址,授權(quán)碼類型中使用RegisteredRedirectUri string// 可以使用的授權(quán)類型AuthorizedGrantTypes []string }// 驗(yàn)證 clientId 和 ClientSecret 是否匹配 func (clientDetails *ClientDetails) IsMatch(clientId string, clientSecret string) bool {return clientId == clientDetails.ClientId && clientSecret == clientDetails.ClientSecret }除了它們具備的基本信息,還提供了 #IsMatch 方法用于驗(yàn)證賬號(hào)信息和密碼是否匹配的 方法。由于我們的信息都是明文存儲(chǔ)的,所以直接比較信息是否相等即可,也可以根據(jù)項(xiàng)目的需求,在其中使用一些加密算法,避免敏感信息明文存儲(chǔ)。
UserDetailsService 和 ClientDetailService 服務(wù)都僅提供一個(gè)方法,用于根據(jù)對(duì)應(yīng)的標(biāo)識(shí)加載信息,接口定義如下所示:
type UserDetailsService interface {// 根據(jù)用戶名加載用戶信息GetUserDetailByUsername(username string)(*UserDetails, error) }type ClientDetailService interface {// 根據(jù) clientId 加載客戶端信息GetClientDetailByClientId(clientId string) (*ClientDetails, error) }用戶信息和客戶端信息可以來源多處,我們可以從數(shù)據(jù)庫中、緩存中甚至通過 RPC 的方式從其他用戶微服務(wù)中加載。
小結(jié)
本文主要介紹了微服務(wù)架構(gòu)中的統(tǒng)一認(rèn)證與授權(quán)相關(guān)概念,以及授權(quán)服務(wù)器實(shí)現(xiàn)涉及到的結(jié)構(gòu)體和服務(wù)接口。TokenGrant 令牌生成器和 TokenService 令牌服務(wù)以及其他的實(shí)現(xiàn)將會(huì)在下篇介紹。
推薦閱讀
微服務(wù)合集
訂閱最新文章,歡迎關(guān)注我的公眾號(hào)
總結(jié)
以上是生活随笔為你收集整理的微服务统一认证与授权的 Go 语言实现的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ORACLE 10g下载地址
- 下一篇: 设计 色彩 构图 创意_我们可以从时尚的