日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

感性认识JWT

發(fā)布時間:2024/3/13 编程问答 41 豆豆
生活随笔 收集整理的這篇文章主要介紹了 感性认识JWT 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

常見的認(rèn)證機(jī)制

今天我么聊一聊JWT。

關(guān)于JWT,相信很多人都已經(jīng)看過用過,他是基于json數(shù)據(jù)結(jié)構(gòu)的認(rèn)證規(guī)范,簡單的說就是驗證用戶登沒登陸的玩意。這時候你可能回想,哎喲,不是又那個session么,分布式系統(tǒng)用redis做分布式session,那這個jwt有什么好處呢?

請聽我慢慢訴說這歷史!

最原始的辦法--HTTP BASIC AUTH

HTTP BASIC auth,別看它名字那么長那么生,你就認(rèn)為這個玩意很高大上。其實原理很簡單,簡單的說就是每次請求API的時候,都會把用戶名和密碼通過restful API傳給服務(wù)端。這樣就可以實現(xiàn)一個無狀態(tài)思想,即每次HTTP請求和以前都沒有啥關(guān)系,只是獲取目標(biāo)URI,得到目標(biāo)內(nèi)容之后,這次連接就被殺死,沒有任何痕跡。你可別一聽無狀態(tài),正是現(xiàn)在的熱門思想,就覺得很厲害。其實他的缺點還是又的,我們通過http請求發(fā)送給服務(wù)端的時候,很有可能將我們的用戶名密碼直接暴漏給第三方客戶端,風(fēng)險特別大,因此生產(chǎn)環(huán)境下用這個方法很少。

Session和Cookie

session和cookie老生常談了。開始時,都會在服務(wù)端全局創(chuàng)建session對象,session對象保存著各種關(guān)鍵信息,同時向客戶端發(fā)送一組sessionId,成為一個cookie對象保存在瀏覽器中。

當(dāng)認(rèn)證時,cookie的數(shù)據(jù)會傳入服務(wù)端與session進(jìn)行匹配,進(jìn)而進(jìn)行數(shù)據(jù)認(rèn)證。

此時,實現(xiàn)的是一個有狀態(tài)的思想,即該服務(wù)的實例可以將一部分?jǐn)?shù)據(jù)隨時進(jìn)行備份,并且在創(chuàng)建一個新的有狀態(tài)服務(wù)時,可以通過備份恢復(fù)這些數(shù)據(jù),以達(dá)到數(shù)據(jù)持久化的目的。

缺點

這種認(rèn)證方法基本是現(xiàn)在軟件最常用的方法了,它有一些自己的缺點:

  • 安全性。cookies的安全性不好,攻擊者可以通過獲取本地cookies進(jìn)行欺騙或者利用cookies進(jìn)行CSRF攻擊。

  • 跨域問題。使用cookies時,在多個域名下,會存在跨域問題。

  • 有狀態(tài)。session在一定的時間里,需要存放在服務(wù)端,因此當(dāng)擁有大量用戶時,也會大幅度降低服務(wù)端的性能。

  • 狀態(tài)問題。當(dāng)有多臺機(jī)器時,如何共享session也會是一個問題,也就是說,用戶第一個訪問的時候是服務(wù)器A,而第二個請求被轉(zhuǎn)發(fā)給了服務(wù)器B,那服務(wù)器B如何得知其狀態(tài)。

  • 移動手機(jī)問題。現(xiàn)在的智能手機(jī),包括安卓,原生不支持cookie,要使用cookie挺麻煩。

Token認(rèn)證(使用jwt規(guī)范)

token 即使是在計算機(jī)領(lǐng)域中也有不同的定義,這里我們說的token,是指 訪問資源的憑據(jù) 。使用基于 Token 的身份驗證方法,在服務(wù)端不需要存儲用戶的登錄記錄。大概的流程是 這樣的:

  • 客戶端使用用戶名跟密碼請求登錄

  • 服務(wù)端收到請求,去驗證用戶名與密碼

  • 驗證成功后,服務(wù)端會簽發(fā)一個 Token,再把這個 Token 發(fā)送給客戶端

  • 客戶端收到 Token 以后可以把它存儲起來,比如放在 Cookie 里

  • 客戶端每次向服務(wù)端請求資源的時候需要帶著服務(wù)端簽發(fā)的 Token

  • 服務(wù)端收到請求,然后去驗證客戶端請求里面帶著的 Token,如果驗證成功,就向客戶端返回請求的數(shù)據(jù)

  • Token機(jī)制,我認(rèn)為其本質(zhì)思想就是將session中的信息簡化很多,當(dāng)作cookie用,也就是客戶端的“session”。

    好處

    那Token機(jī)制相對于Cookie機(jī)制又有什么好處呢?

    • 支持跨域訪問: Cookie是不允許垮域訪問的,這一點對Token機(jī)制是不存在的,前提 是傳輸?shù)挠脩粽J(rèn)證信息通過HTTP頭傳輸.

    • 無狀態(tài):Token機(jī)制本質(zhì)是校驗, 他得到的會話狀態(tài)完全來自于客戶端, Token機(jī)制在服務(wù)端不需要存儲session信息,因為 Token 自身包含了所有登錄用戶的信息,只需要在客戶端的cookie或本地介質(zhì)存儲狀態(tài)信息.

    • 更適用CDN: 可以通過內(nèi)容分發(fā)網(wǎng)絡(luò)請求你服務(wù)端的所有資料(如:javascript, HTML,圖片等),而你的服務(wù)端只要提供API即可.

    • 去耦: 不需要綁定到一個特定的身份驗證方案。Token可以在任何地方生成,只要在 你的API被調(diào)用的時候,你可以進(jìn)行Token生成調(diào)用即可.

    • 更適用于移動應(yīng)用: 當(dāng)你的客戶端是一個原生平臺(iOS, Android,Windows 8等) 時,Cookie是不被支持的(你需要通過Cookie容器進(jìn)行處理),這時采用Token認(rèn) 證機(jī)制就會簡單得多。 CSRF:因為不再依賴于Cookie,所以你就不需要考慮對CSRF(跨站請求偽造)的防 范。

    • 性能: 一次網(wǎng)絡(luò)往返時間(通過數(shù)據(jù)庫查詢session信息)總比做一次HMACSHA256 計算 的Token驗證和解析要費時得多. 不需要為登錄頁面做特殊處理: 如果你使用Protractor 做功能測試的時候,不再需要 為登錄頁面做特殊處理.

    • 基于標(biāo)準(zhǔn)化:你的API可以采用標(biāo)準(zhǔn)化的 JSON Web Token (JWT). 這個標(biāo)準(zhǔn)已經(jīng)存在 多個后端庫(.NET, Ruby, Java,Python, PHP)和多家公司的支持(如: Firebase,Google, Microsoft)

    缺陷在哪?

    說了那么多token認(rèn)證的好處,但他其實并沒有想象的那么神,token 也并不是沒有問題。

  • 占帶寬

    正常情況下要比 session_id 更大,需要消耗更多流量,擠占更多帶寬,假如你的網(wǎng)站每月有 10 萬次的瀏覽器,就意味著要多開銷幾十兆的流量。聽起來并不多,但日積月累也是不小一筆開銷。實際上,許多人會在 JWT 中存儲的信息會更多。

  • 無論如何你需要操作數(shù)據(jù)庫

    在網(wǎng)站上使用 JWT,對于用戶加載的幾乎所有頁面,都需要從緩存/數(shù)據(jù)庫中加載用戶信息,如果對于高流量的服務(wù),你確定這個操作合適么?如果使用redis進(jìn)行緩存,那么效率上也并不能比 session 更高效

  • 無法在服務(wù)端注銷,那么久很難解決劫持問題

  • 性能問題

    JWT 的賣點之一就是加密簽名,由于這個特性,接收方得以驗證 JWT 是否有效且被信任。但是大多數(shù) Web 身份認(rèn)證應(yīng)用中,JWT 都會被存儲到 Cookie 中,這就是說你有了兩個層面的簽名。聽著似乎很牛逼,但是沒有任何優(yōu)勢,為此,你需要花費兩倍的 CPU 開銷來驗證簽名。對于有著嚴(yán)格性能要求的 Web 應(yīng)用,這并不理想,尤其對于單線程環(huán)境。

  • JWT

    現(xiàn)在我們來說說今天的主角,JWT

    JSON Web Token(JWT)是一個非常輕巧的規(guī)范。這個規(guī)范允許我們使用JWT在用 戶和服務(wù)器之間傳遞安全可靠的信息

    組成

    一個JWT實際上就是一個字符串,它由三部分組成,頭部載荷簽名

    頭部(header)

    頭部用于描述關(guān)于該JWT的最基本的信息,例如其類型以及簽名所用的算法等。這也可以 被表示成一個JSON對象。

    {
    ????"typ":"JWT",
    ????"alg":"HS256"
    }

    這就是頭部的明文內(nèi)容,第一部分說明他是一個jwt,第二部分則指出簽名算法用的是HS256算法。


    然后將這個頭部進(jìn)行BASE64編碼,編碼后形成頭部:

    eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9

    載荷(payload)

    載荷就是存放有效信息的地方,有效信息包含三個部分:

    (1)標(biāo)準(zhǔn)中注冊的聲明(建議但不強(qiáng)制使用)

    • iss: jwt簽發(fā)者

    • sub: jwt所面向的用戶

    • aud: 接收jwt的一方

    • exp: jwt的過期時間,這個過期時間必須要大于簽發(fā)時間

    • nbf: 定義在什么時間之前,該jwt都是不可用的.

    • iat: jwt的簽發(fā)時間

    • jti: jwt的唯一身份標(biāo)識,主要用來作為一次性token,從而回避重放攻擊。

    (2)公共的聲明

    公共的聲明可以添加任何的信息,一般添加用戶的相關(guān)信息或其他業(yè)務(wù)需要的必要信息. 但不建議添加敏感信息,因為該部分在客戶端可解密.

    (3)私有的聲明

    私有聲明是提供者和消費者所共同定義的聲明,一般不建議存放敏感信息,因為base64 是對稱解密的,意味著該部分信息可以歸類為明文信息。

    {
    ????"sub":"1234567890",
    ????"name":"tengshe789",
    ????"admin":?true
    }

    上面就是一個簡單的載荷的明文,接下來使用base64加密:

    eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9

    簽證(signature)

    jwt的第三部分是一個簽證信息,這個簽證信息由三部分組成:

  • header (base64后的)

  • payload (base64后的)

  • secret

  • 這個部分需要base64加密后的header和base64加密后的payload使用.連接組成的字符串,然后通過header中聲明的加密方式進(jìn)行加鹽secret組合加密,然后就構(gòu)成了jwt的第 三部分。

    實現(xiàn)JWT

    現(xiàn)在一般實現(xiàn)jwt,都使用Apache 的開源項目JJWT(一個提供端到端的JWT創(chuàng)建和驗證的Java庫)。

    依賴

    <!--?https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt?-->
    <dependency>
    ????<groupId>io.jsonwebtoken</groupId>
    ????<artifactId>jjwt</artifactId>
    ????<version>0.7.0</version>
    </dependency>

    創(chuàng)建token的demo

  • public class CreateJWT {

  • ? ?public static void main(String[] args) throws Exception{

  • ? ? ? ?JwtBuilder builder = Jwts.builder().setId("123")

  • ? ? ? ? ? ? ? ?.setSubject("jwt所面向的用戶")

  • ? ? ? ? ? ? ? ?.setIssuedAt(new Date())

  • ? ? ? ? ? ? ? ?.signWith(SignatureAlgorithm.HS256,"tengshe789");

  • ? ? ? ?String s = builder.compact();

  • ? ? ? ?System.out.println(s);

  • ? ? ? ?//eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIxMjMiLCJzdWIiOiJqd3TmiYDpnaLlkJHnmoTnlKjmiLciLCJpYXQiOjE1NDM3NTk0MjJ9.1sIlEynqqZmA4PbKI6GgiP3ljk_aiypcsUxSN6-ATIA

  • ? ?}

  • }

  • 解析Token的demo

  • public class ParseJWT {

  • ? ?public static void main(String[] args) {

  • ? ? ? ?String token = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIxMjMiLCJzdWIiOiJqd3TmiYDpnaLlkJHnmoTnlKjmiLciLCJpYXQiOjE1NDM3NTk0MjJ9.1sIlEynqqZmA4PbKI6GgiP3ljk_aiypcsUxSN6-ATIA";

  • ? ? ? ?Claims claims =

  • ? ? ? ? ? ? ? ?Jwts.parser().setSigningKey("tengshe789").parseClaimsJws(token).getBody();

  • ? ? ? ?System.out.println("id"+claims.getId());

  • ? ? ? ?System.out.println("Subject"+claims.getSubject());

  • ? ? ? ?System.out.println("IssuedAt"+claims.getIssuedAt());

  • ? ?}

  • }

  • 生產(chǎn)中的JWT

    在企業(yè)級系統(tǒng)中,通常內(nèi)部會有非常多的工具平臺供大家使用,比如人力資源,代碼管理,日志監(jiān)控,預(yù)算申請等等。如果每一個平臺都實現(xiàn)自己的用戶體系的話無疑是巨大的浪費,所以公司內(nèi)部會有一套公用的用戶體系,用戶只要登陸之后,就能夠訪問所有的系統(tǒng)。

    這就是 單點登錄(SSO: Single Sign-On)

    SSO 是一類解決方案的統(tǒng)稱,而在具體的實施方面,一般有兩種策略可供選擇:

    • SAML 2.0

    • OAuth 2.0

    欲揚先抑,先說說幾個重要的知識點。

    Authentication VS Authorisation

    • Authentication: 身份鑒別,鑒權(quán),以下簡稱認(rèn)證

      認(rèn)證 的作用在于認(rèn)可你有權(quán)限訪問系統(tǒng),用于鑒別訪問者是否是合法用戶。負(fù)責(zé)認(rèn)證的服務(wù)通常稱為 Authorization Server 或者 Identity Provider,以下簡稱 IdP

    • Authorisation: 授權(quán)

      授權(quán) 用于決定你有訪問哪些資源的權(quán)限。大多數(shù)人不會區(qū)分這兩者的區(qū)別,因為站在用戶的立場上。而作為系統(tǒng)的設(shè)計者來說,這兩者是有差別的,這是不同的兩個工作職責(zé),我們可以只需要認(rèn)證功能,而不需要授權(quán)功能,甚至不需要自己實現(xiàn)認(rèn)證功能,而借助 Google 的認(rèn)證系統(tǒng),即用戶可以用 Google 的賬號進(jìn)行登陸。負(fù)責(zé)提供資源(API調(diào)用)的服務(wù)稱為 Resource Server 或者 Service Provider,以下簡稱 SP

    SMAL 2.0

    OAuth(JWT)

    OAuth(開放授權(quán))是一個開放的授權(quán)標(biāo)準(zhǔn),允許用戶讓第三方應(yīng)用訪問該用戶在 某一web服務(wù)上存儲的私密的資源(如照片,視頻,聯(lián)系人列表),而無需將用戶名和密碼提供給第三方應(yīng)用。

    流程可以參考如下:

    簡單的來說,就是你要訪問一個應(yīng)用服務(wù),先找它要一個request token(請求令牌),再把這個request token發(fā)到第三方認(rèn)證服務(wù)器,此時第三方認(rèn)證服務(wù)器會給你一個aceess token(通行令牌), 有了aceess token你就可以使用你的應(yīng)用服務(wù)了。

    注意圖中第4步兌換 access token 的過程中,很多第三方系統(tǒng),如Google ,并不會僅僅返回 access token,還會返回額外的信息,這其中和之后更新相關(guān)的就是 refresh token。一旦 access token過期,你就可以通過 refresh token 再次請求 access token。

    當(dāng)然了,流程是根據(jù)你的請求方式和訪問的資源類型而定的,業(yè)務(wù)很多也是不一樣的,我這是簡單的聊聊。

    現(xiàn)在這種方法比較常見,常見的譬如使用QQ快速登陸,用的基本的都是這種方法。

    開源項目

    我們用一個很火的開源項目Cloud-Admin為栗子,來分析一下jwt的應(yīng)用。

    Cloud-Admin是基于Spring Cloud微服務(wù)化開發(fā)平臺,具有統(tǒng)一授權(quán)、認(rèn)證后臺管理系統(tǒng),其中包含具備用戶管理、資源權(quán)限管理、網(wǎng)關(guān)API管理等多個模塊,支持多業(yè)務(wù)系統(tǒng)并行開發(fā)。

    目錄結(jié)構(gòu)

    鑒權(quán)中心功能在ace-auth與ace-gate下。

    模型

    下面是官方提供的架構(gòu)模型。

    可以看到,AuthServer在架構(gòu)的中心環(huán)節(jié),要訪問服務(wù),必須需要鑒權(quán)中心的JWT鑒權(quán)。

    鑒權(quán)中心服務(wù)端代碼解讀

    實體類

    先看實體類,這里鑒權(quán)中心定義了一組客戶端實體,如下:

  • @Table(name = "auth_client")

  • @Getter

  • @Setter

  • public class Client {

  • ? ?@Id

  • ? ?private Integer id;

  • ? ?private String code;

  • ? ?private String secret;

  • ? ?private String name;

  • ? ?private String locked = "0";

  • ? ?private String description;

  • ? ?@Column(name = "crt_time")

  • ? ?private Date crtTime;

  • ? ?@Column(name = "crt_user")

  • ? ?private String crtUser;

  • ? ?@Column(name = "crt_name")

  • ? ?private String crtName;

  • ? ?@Column(name = "crt_host")

  • ? ?private String crtHost;

  • ? ?@Column(name = "upd_time")

  • ? ?private Date updTime;

  • ? ?@Column(name = "upd_user")

  • ? ?private String updUser;

  • ? ?@Column(name = "upd_name")

  • ? ?private String updName;

  • ? ?@Column(name = "upd_host")

  • ? ?private String updHost;

  • ? ?private String attr1;

  • ? ?private String attr2;

  • ? ?private String attr3;

  • ? ?private String attr4;

  • ? ?private String attr5;

  • ? ?private String attr6;

  • ? ?private String attr7;

  • ? ?private String attr8;

  • 對應(yīng)數(shù)據(jù)庫:

  • CREATE TABLE `auth_client` (

  • ?`id` int(11) NOT NULL AUTO_INCREMENT,

  • ?`code` varchar(255) DEFAULT NULL COMMENT '服務(wù)編碼',

  • ?`secret` varchar(255) DEFAULT NULL COMMENT '服務(wù)密鑰',

  • ?`name` varchar(255) DEFAULT NULL COMMENT '服務(wù)名',

  • ?`locked` char(1) DEFAULT NULL COMMENT '是否鎖定',

  • ?`description` varchar(255) DEFAULT NULL COMMENT '描述',

  • ?`crt_time` datetime DEFAULT NULL COMMENT '創(chuàng)建時間',

  • ?`crt_user` varchar(255) DEFAULT NULL COMMENT '創(chuàng)建人',

  • ?`crt_name` varchar(255) DEFAULT NULL COMMENT '創(chuàng)建人姓名',

  • ?`crt_host` varchar(255) DEFAULT NULL COMMENT '創(chuàng)建主機(jī)',

  • ?`upd_time` datetime DEFAULT NULL COMMENT '更新時間',

  • ?`upd_user` varchar(255) DEFAULT NULL COMMENT '更新人',

  • ?`upd_name` varchar(255) DEFAULT NULL COMMENT '更新姓名',

  • ?`upd_host` varchar(255) DEFAULT NULL COMMENT '更新主機(jī)',

  • ?`attr1` varchar(255) DEFAULT NULL,

  • ?`attr2` varchar(255) DEFAULT NULL,

  • ?`attr3` varchar(255) DEFAULT NULL,

  • ?`attr4` varchar(255) DEFAULT NULL,

  • ?`attr5` varchar(255) DEFAULT NULL,

  • ?`attr6` varchar(255) DEFAULT NULL,

  • ?`attr7` varchar(255) DEFAULT NULL,

  • ?`attr8` varchar(255) DEFAULT NULL,

  • ?PRIMARY KEY (`id`)

  • ) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8mb4;

  • 這些是每組微服務(wù)客戶端的信息

    第二個實體類,就是客戶端_服務(wù)的實體,也就是對應(yīng)著那些微服務(wù)客戶端能調(diào)用哪些微服務(wù)客戶端:

    大概對應(yīng)的就是微服務(wù)間調(diào)用權(quán)限關(guān)系。

  • @Table(name = "auth_client_service")

  • public class ClientService {

  • ? ?@Id

  • ? ?private Integer id;

  • ? ?@Column(name = "service_id")

  • ? ?private String serviceId;

  • ? ?@Column(name = "client_id")

  • ? ?private String clientId;

  • ? ?private String description;

  • ? ?@Column(name = "crt_time")

  • ? ?private Date crtTime;

  • ? ?@Column(name = "crt_user")

  • ? ?private String crtUser;

  • ? ?@Column(name = "crt_name")

  • ? ?private String crtName;

  • ? ?@Column(name = "crt_host")

  • ? ?private String crtHost;}

  • 接口層

    我們跳著看,先看接口層

  • @RestController

  • @RequestMapping("jwt")

  • @Slf4j

  • public class AuthController {

  • ? ?@Value("${jwt.token-header}")

  • ? ?private String tokenHeader;

  • ? ?@Autowired

  • ? ?private AuthService authService;

  • ? ?@RequestMapping(value = "token", method = RequestMethod.POST)

  • ? ?public ObjectRestResponse<String> createAuthenticationToken(

  • ? ? ? ? ? ?@RequestBody JwtAuthenticationRequest authenticationRequest) throws Exception {

  • ? ? ? ?log.info(authenticationRequest.getUsername()+" require logging...");

  • ? ? ? ?final String token = authService.login(authenticationRequest);

  • ? ? ? ?return new ObjectRestResponse<>().data(token);

  • ? ?}

  • ? ?@RequestMapping(value = "refresh", method = RequestMethod.GET)

  • ? ?public ObjectRestResponse<String> refreshAndGetAuthenticationToken(

  • ? ? ? ? ? ?HttpServletRequest request) throws Exception {

  • ? ? ? ?String token = request.getHeader(tokenHeader);

  • ? ? ? ?String refreshedToken = authService.refresh(token);

  • ? ? ? ?return new ObjectRestResponse<>().data(refreshedToken);

  • ? ?}

  • ? ?@RequestMapping(value = "verify", method = RequestMethod.GET)

  • ? ?public ObjectRestResponse<?> verify(String token) throws Exception {

  • ? ? ? ?authService.validate(token);

  • ? ? ? ?return new ObjectRestResponse<>();

  • ? ?}

  • }

  • 這里放出了三個接口

    先說第一個接口,創(chuàng)建token。

    具體邏輯如下: 每一個用戶登陸進(jìn)來時,都會進(jìn)入這個環(huán)節(jié)。根據(jù)request中用戶的用戶名和密碼,利用feign客戶端的攔截器攔截request,然后使用作者寫的JwtTokenUtil里面的各種方法取出token中的key和密鑰,驗證token是否正確,正確則用authService.login(authenticationRequest);的方法返回出去一個新的token。

  • public String login(JwtAuthenticationRequest authenticationRequest) throws Exception {

  • ? ? ? ?UserInfo info = userService.validate(authenticationRequest);

  • ? ? ? ?if (!StringUtils.isEmpty(info.getId())) {

  • ? ? ? ? ? ?return jwtTokenUtil.generateToken(new JWTInfo(info.getUsername(), info.getId() + "", info.getName()));

  • ? ? ? ?}

  • ? ? ? ?throw new UserInvalidException("用戶不存在或賬戶密碼錯誤!");

  • ? ?}

  • 下圖是詳細(xì)邏輯圖:

    鑒權(quán)中心客戶端代碼

    入口

    作者寫了個注解的入口,使用@EnableAceAuthClient即自動開啟微服務(wù)(客戶端)的鑒權(quán)管理

  • @Target(ElementType.TYPE)

  • @Retention(RetentionPolicy.RUNTIME)

  • @Import(AutoConfiguration.class)

  • @Documented

  • @Inherited

  • public @interface EnableAceAuthClient {

  • }

  • 配置

    接著沿著注解的入口看

  • @Configuration

  • @ComponentScan({"com.github.wxiaoqi.security.auth.client","com.github.wxiaoqi.security.auth.common.event"})

  • public class AutoConfiguration {

  • ? ?@Bean

  • ? ?ServiceAuthConfig getServiceAuthConfig(){

  • ? ? ? ?return new ServiceAuthConfig();

  • ? ?}

  • ? ?@Bean

  • ? ?UserAuthConfig getUserAuthConfig(){

  • ? ? ? ?return new UserAuthConfig();

  • ? ?}

  • }

  • 注解會自動的將客戶端的用戶token和服務(wù)token的關(guān)鍵信息加載到bean中

    feigin攔截器

    作者重寫了okhttp3攔截器的方法,每一次微服務(wù)客戶端請求的token都會被攔截下來,驗證服務(wù)調(diào)用服務(wù)的token和用戶調(diào)用服務(wù)的token是否過期,過期則返回新的token

  • @Override

  • ? ?public Response intercept(Chain chain) throws IOException {

  • ? ? ? ?Request newRequest = null;

  • ? ? ? ?if (chain.request().url().toString().contains("client/token")) {

  • ? ? ? ? ? ?newRequest = chain.request()

  • ? ? ? ? ? ? ? ? ? ?.newBuilder()

  • ? ? ? ? ? ? ? ? ? ?.header(userAuthConfig.getTokenHeader(), BaseContextHandler.getToken())

  • ? ? ? ? ? ? ? ? ? ?.build();

  • ? ? ? ?} else {

  • ? ? ? ? ? ?newRequest = chain.request()

  • ? ? ? ? ? ? ? ? ? ?.newBuilder()

  • ? ? ? ? ? ? ? ? ? ?.header(userAuthConfig.getTokenHeader(), BaseContextHandler.getToken())

  • ? ? ? ? ? ? ? ? ? ?.header(serviceAuthConfig.getTokenHeader(), serviceAuthUtil.getClientToken())

  • ? ? ? ? ? ? ? ? ? ?.build();

  • ? ? ? ?}

  • ? ? ? ?Response response = chain.proceed(newRequest);

  • ? ? ? ?if (HttpStatus.FORBIDDEN.value() == response.code()) {

  • ? ? ? ? ? ?if (response.body().string().contains(String.valueOf(CommonConstants.EX_CLIENT_INVALID_CODE))) {

  • ? ? ? ? ? ? ? ?log.info("Client Token Expire,Retry to request...");

  • ? ? ? ? ? ? ? ?serviceAuthUtil.refreshClientToken();

  • ? ? ? ? ? ? ? ?newRequest = chain.request()

  • ? ? ? ? ? ? ? ? ? ? ? ?.newBuilder()

  • ? ? ? ? ? ? ? ? ? ? ? ?.header(userAuthConfig.getTokenHeader(), BaseContextHandler.getToken())

  • ? ? ? ? ? ? ? ? ? ? ? ?.header(serviceAuthConfig.getTokenHeader(), serviceAuthUtil.getClientToken())

  • ? ? ? ? ? ? ? ? ? ? ? ?.build();

  • ? ? ? ? ? ? ? ?response = chain.proceed(newRequest);

  • ? ? ? ? ? ?}

  • ? ? ? ?}

  • ? ? ? ?return response;

  • ? ?}

  • spring容器的攔截器

    第二道攔截器是來自spring容器的,第一道feign攔截器只是驗證了兩個token是否過期,但token真實的權(quán)限卻沒驗證。接下來就要驗證兩個token的權(quán)限問題了。

    服務(wù)調(diào)用權(quán)限代碼如下:

  • @Override

  • ? ?public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

  • ? ? ? ?HandlerMethod handlerMethod = (HandlerMethod) handler;

  • ? ? ? ?// 配置該注解,說明不進(jìn)行服務(wù)攔截

  • ? ? ? ?IgnoreClientToken annotation = handlerMethod.getBeanType().getAnnotation(IgnoreClientToken.class);

  • ? ? ? ?if (annotation == null) {

  • ? ? ? ? ? ?annotation = handlerMethod.getMethodAnnotation(IgnoreClientToken.class);

  • ? ? ? ?}

  • ? ? ? ?if(annotation!=null) {

  • ? ? ? ? ? ?return super.preHandle(request, response, handler);

  • ? ? ? ?}

  • ? ? ? ?String token = request.getHeader(serviceAuthConfig.getTokenHeader());

  • ? ? ? ?IJWTInfo infoFromToken = serviceAuthUtil.getInfoFromToken(token);

  • ? ? ? ?String uniqueName = infoFromToken.getUniqueName();

  • ? ? ? ?for(String client:serviceAuthUtil.getAllowedClient()){

  • ? ? ? ? ? ?if(client.equals(uniqueName)){

  • ? ? ? ? ? ? ? ?return super.preHandle(request, response, handler);

  • ? ? ? ? ? ?}

  • ? ? ? ?}

  • ? ? ? ?throw new ClientForbiddenException("Client is Forbidden!");

  • ? ?}

  • 用戶權(quán)限:

  • @Override

  • ? ?public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

  • ? ? ? ?HandlerMethod handlerMethod = (HandlerMethod) handler;

  • ? ? ? ?// 配置該注解,說明不進(jìn)行用戶攔截

  • ? ? ? ?IgnoreUserToken annotation = handlerMethod.getBeanType().getAnnotation(IgnoreUserToken.class);

  • ? ? ? ?if (annotation == null) {

  • ? ? ? ? ? ?annotation = handlerMethod.getMethodAnnotation(IgnoreUserToken.class);

  • ? ? ? ?}

  • ? ? ? ?if (annotation != null) {

  • ? ? ? ? ? ?return super.preHandle(request, response, handler);

  • ? ? ? ?}

  • ? ? ? ?String token = request.getHeader(userAuthConfig.getTokenHeader());

  • ? ? ? ?if (StringUtils.isEmpty(token)) {

  • ? ? ? ? ? ?if (request.getCookies() != null) {

  • ? ? ? ? ? ? ? ?for (Cookie cookie : request.getCookies()) {

  • ? ? ? ? ? ? ? ? ? ?if (cookie.getName().equals(userAuthConfig.getTokenHeader())) {

  • ? ? ? ? ? ? ? ? ? ? ? ?token = cookie.getValue();

  • ? ? ? ? ? ? ? ? ? ?}

  • ? ? ? ? ? ? ? ?}

  • ? ? ? ? ? ?}

  • ? ? ? ?}

  • ? ? ? ?IJWTInfo infoFromToken = userAuthUtil.getInfoFromToken(token);

  • ? ? ? ?BaseContextHandler.setUsername(infoFromToken.getUniqueName());

  • ? ? ? ?BaseContextHandler.setName(infoFromToken.getName());

  • ? ? ? ?BaseContextHandler.setUserID(infoFromToken.getId());

  • ? ? ? ?return super.preHandle(request, response, handler);

  • ? ?}

  • ? ?@Override

  • ? ?public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

  • ? ? ? ?BaseContextHandler.remove();

  • ? ? ? ?super.afterCompletion(request, response, handler, ex);

  • ? ?}

  • spring cloud gateway網(wǎng)關(guān)代碼

    該框架中所有的請求都會走網(wǎng)關(guān)服務(wù)(ace-gatev2),通過網(wǎng)關(guān),來驗證token是否過期異常,驗證token是否不存在,驗證token是否有權(quán)限進(jìn)行服務(wù)。

    下面是核心代碼:

  • @Override

  • ? ?public Mono<Void> filter(ServerWebExchange serverWebExchange, GatewayFilterChain gatewayFilterChain) {

  • ? ? ? ?log.info("check token and user permission....");

  • ? ? ? ?LinkedHashSet requiredAttribute = serverWebExchange.getRequiredAttribute(ServerWebExchangeUtils.GATEWAY_ORIGINAL_REQUEST_URL_ATTR);

  • ? ? ? ?ServerHttpRequest request = serverWebExchange.getRequest();

  • ? ? ? ?String requestUri = request.getPath().pathWithinApplication().value();

  • ? ? ? ?if (requiredAttribute != null) {

  • ? ? ? ? ? ?Iterator<URI> iterator = requiredAttribute.iterator();

  • ? ? ? ? ? ?while (iterator.hasNext()){

  • ? ? ? ? ? ? ? ?URI next = iterator.next();

  • ? ? ? ? ? ? ? ?if(next.getPath().startsWith(GATE_WAY_PREFIX)){

  • ? ? ? ? ? ? ? ? ? ?requestUri = next.getPath().substring(GATE_WAY_PREFIX.length());

  • ? ? ? ? ? ? ? ?}

  • ? ? ? ? ? ?}

  • ? ? ? ?}

  • ? ? ? ?final String method = request.getMethod().toString();

  • ? ? ? ?BaseContextHandler.setToken(null);

  • ? ? ? ?ServerHttpRequest.Builder mutate = request.mutate();

  • ? ? ? ?// 不進(jìn)行攔截的地址

  • ? ? ? ?if (isStartWith(requestUri)) {

  • ? ? ? ? ? ?ServerHttpRequest build = mutate.build();

  • ? ? ? ? ? ?return gatewayFilterChain.filter(serverWebExchange.mutate().request(build).build());

  • ? ? ? ?}

  • ? ? ? ?IJWTInfo user = null;

  • ? ? ? ?try {

  • ? ? ? ? ? ?user = getJWTUser(request, mutate);

  • ? ? ? ?} catch (Exception e) {

  • ? ? ? ? ? ?log.error("用戶Token過期異常", e);

  • ? ? ? ? ? ?return getVoidMono(serverWebExchange, new TokenForbiddenResponse("User Token Forbidden or Expired!"));

  • ? ? ? ?}

  • ? ? ? ?List<PermissionInfo> permissionIfs = userService.getAllPermissionInfo();

  • ? ? ? ?// 判斷資源是否啟用權(quán)限約束

  • ? ? ? ?Stream<PermissionInfo> stream = getPermissionIfs(requestUri, method, permissionIfs);

  • ? ? ? ?List<PermissionInfo> result = stream.collect(Collectors.toList());

  • ? ? ? ?PermissionInfo[] permissions = result.toArray(new PermissionInfo[]{});

  • ? ? ? ?if (permissions.length > 0) {

  • ? ? ? ? ? ?if (checkUserPermission(permissions, serverWebExchange, user)) {

  • ? ? ? ? ? ? ? ?return getVoidMono(serverWebExchange, new TokenForbiddenResponse("User Forbidden!Does not has Permission!"));

  • ? ? ? ? ? ?}

  • ? ? ? ?}

  • ? ? ? ?// 申請客戶端密鑰頭

  • ? ? ? ?mutate.header(serviceAuthConfig.getTokenHeader(), serviceAuthUtil.getClientToken());

  • ? ? ? ?ServerHttpRequest build = mutate.build();

  • ? ? ? ?return gatewayFilterChain.filter(serverWebExchange.mutate().request(build).build());

  • ? ?}

  • cloud admin總結(jié)

    總的來說,鑒權(quán)和網(wǎng)關(guān)模塊就說完了。作者代碼構(gòu)思極其精妙,使用在大型的權(quán)限系統(tǒng)中,可以巧妙的減少耦合性,讓服務(wù)鑒權(quán)粒度細(xì)化,方便管理。

    本文來源:https://blog.tengshe789.tech/

    總結(jié)

    以上是生活随笔為你收集整理的感性认识JWT的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。