1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        Spring Boot 集成 JWT 實(shí)現(xiàn)用戶(hù)登錄認(rèn)證

        共 18602字,需瀏覽 38分鐘

         ·

        2020-10-28 13:59


        JWT 簡(jiǎn)介

        什么是 JWT

        JWT 是 JSON Web Token 的縮寫(xiě),是為了在網(wǎng)絡(luò)應(yīng)用環(huán)境間傳遞聲明而執(zhí)行的一種基于 JSON 的開(kāi)放標(biāo)準(zhǔn)((RFC 7519)。定義了一種簡(jiǎn)潔的,自包含的方法用于通信雙方之間以 JSON 對(duì)象的形式安全的傳遞信息。因?yàn)閿?shù)字簽名的存在,這些信息是可信的,JWT 可以使用 HMAC 算法或者是 RSA 的公私秘鑰對(duì)進(jìn)行簽名。

        JWT請(qǐng)求流程

        JWT 請(qǐng)求流程
        1. 用戶(hù)使用賬號(hào)和密碼發(fā)起 POST 請(qǐng)求;
        2. 服務(wù)器使用私鑰創(chuàng)建一個(gè) JWT;
        3. 服務(wù)器返回這個(gè) JWT 給瀏覽器;
        4. 瀏覽器將該 JWT 串在請(qǐng)求頭中像服務(wù)器發(fā)送請(qǐng)求;
        5. 服務(wù)器驗(yàn)證該 JWT;
        6. 返回響應(yīng)的資源給瀏覽器。

        JWT 的主要應(yīng)用場(chǎng)景

        身份認(rèn)證在這種場(chǎng)景下,一旦用戶(hù)完成了登錄,在接下來(lái)的每個(gè)請(qǐng)求中包含 JWT,可以用來(lái)驗(yàn)證用戶(hù)身份以及對(duì)路由,服務(wù)和資源的訪問(wèn)權(quán)限進(jìn)行驗(yàn)證。由于它的開(kāi)銷(xiāo)非常小,可以輕松的在不同域名的系統(tǒng)中傳遞,所有目前在單點(diǎn)登錄(SSO)中比較廣泛的使用了該技術(shù)。 信息交換在通信的雙方之間使用 JWT 對(duì)數(shù)據(jù)進(jìn)行編碼是一種非常安全的方式,由于它的信息是經(jīng)過(guò)簽名的,可以確保發(fā)送者發(fā)送的信息是沒(méi)有經(jīng)過(guò)偽造的。

        JWT 數(shù)據(jù)結(jié)構(gòu)

        JWT 是由三段信息構(gòu)成的,將這三段信息文本用 . 連接一起就構(gòu)成了 JWT 字符串。

        JWT 的三個(gè)部分依次為頭部:Header,負(fù)載:Payload 和簽名:Signature。

        JWT 數(shù)據(jù)結(jié)構(gòu)

        Header

        Header 部分是一個(gè) JSON 對(duì)象,描述 JWT 的元數(shù)據(jù),通常是下面的樣子。

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

        上面代碼中,alg 屬性表示簽名的算法(algorithm),默認(rèn)是 HMAC SHA256(寫(xiě)成 HS256);typ 屬性表示這個(gè)令牌(token)的類(lèi)型(type),JWT 令牌統(tǒng)一寫(xiě)為 JWT。

        最后,將上面的 JSON 對(duì)象使用 Base64URL 算法轉(zhuǎn)成字符串。

        Payload

        Payload 部分也是一個(gè) JSON 對(duì)象,用來(lái)存放實(shí)際需要傳遞的有效信息。有效信息包含三個(gè)部分:

        1. 標(biāo)準(zhǔn)中注冊(cè)的聲明
        2. 公共的聲明
        3. 私有的聲明

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

        • iss (issuer):簽發(fā)人
        • exp (expiration time):過(guò)期時(shí)間,必須要大于簽發(fā)時(shí)間
        • sub (subject):主題
        • aud (audience):受眾
        • nbf (Not Before):生效時(shí)間
        • iat (Issued At):簽發(fā)時(shí)間
        • jti (JWT ID):編號(hào),JWT 的唯一身份標(biāo)識(shí),主要用來(lái)作為一次性 token,從而回避重放攻擊。

        公共的聲明 :

        公共的聲明可以添加任何的信息,一般添加用戶(hù)的相關(guān)信息或其他業(yè)務(wù)需要的必要信息。但不建議添加敏感信息,因?yàn)樵摬糠衷诳蛻?hù)端可解密。

        私有的聲明 :

        私有聲明是提供者和消費(fèi)者所共同定義的聲明,一般不建議存放敏感信息,因?yàn)?base64 是對(duì)稱(chēng)解碼的,意味著該部分信息可以歸類(lèi)為明文信息。

        這個(gè) JSON 對(duì)象也要使用 Base64URL 算法轉(zhuǎn)成字符串。

        Signature

        Signature 部分是對(duì)前兩部分的簽名,防止數(shù)據(jù)篡改。

        首先,需要指定一個(gè)密鑰(secret)。這個(gè)密鑰只有服務(wù)器才知道,不能泄露給用戶(hù)。然后,使用 Header 里面指定的簽名算法(默認(rèn)是 HMAC SHA256),按照下面的公式產(chǎn)生簽名。

        HMACSHA256(base64UrlEncode(header)?+?"."?+?base64UrlEncode(payload),?secret)

        算出簽名以后,把 Header、Payload、Signature 三個(gè)部分拼成一個(gè)字符串,每個(gè)部分之間用"點(diǎn)"(.)分隔,就可以返回給用戶(hù)。

        Base64URL

        前面提到,Header 和 Payload 串型化的算法是 Base64URL。這個(gè)算法跟 Base64 算法基本類(lèi)似,但有一些小的不同。

        JWT 作為一個(gè)令牌(token),有些場(chǎng)合可能會(huì)放到 URL(比如 api.example.com/?token=xxx)。Base64 有三個(gè)字符 +、 /=,在 URL 里面有特殊含義,所以要被替換掉:= 被省略、+ 替換成 -,/ 替換成 _ 。這就是 Base64URL 算法。

        JWT 的使用方式

        客戶(hù)端收到服務(wù)器返回的 JWT 之后需要在本地做保存。此后,客戶(hù)端每次與服務(wù)器通信,都要帶上這個(gè) JWT。一般的的做法是放在 HTTP 請(qǐng)求的頭信息 Authorization 字段里面。

        Authorization:?Bearer?

        這樣每個(gè)請(qǐng)求中,服務(wù)端就可以在請(qǐng)求頭中拿到 JWT ?進(jìn)行解析與認(rèn)證。

        JWT 的特性

        1. JWT 默認(rèn)是不加密,但也是可以加密的。生成原始 Token 以后,可以用密鑰再加密一次。

        2. JWT 不加密的情況下,不能將秘密數(shù)據(jù)寫(xiě)入 JWT。

        3. JWT 不僅可以用于認(rèn)證,也可以用于交換信息。有效使用 JWT,可以降低服務(wù)器查詢(xún)數(shù)據(jù)庫(kù)的次數(shù)。

        4. JWT 的最大缺點(diǎn)是,由于服務(wù)器不保存 session 狀態(tài),因此無(wú)法在使用過(guò)程中廢止某個(gè) token,或者更改 token 的權(quán)限。也就是說(shuō),一旦 JWT 簽發(fā)了,在到期之前就會(huì)始終有效,除非服務(wù)器部署額外的邏輯。

        5. JWT 本身包含了認(rèn)證信息,一旦泄露,任何人都可以獲得該令牌的所有權(quán)限。為了減少盜用,JWT 的有效期應(yīng)該設(shè)置得比較短。對(duì)于一些比較重要的權(quán)限,使用時(shí)應(yīng)該再次對(duì)用戶(hù)進(jìn)行認(rèn)證。

        6. 為了減少盜用,JWT 不應(yīng)該使用 HTTP 協(xié)議明碼傳輸,要使用 HTTPS 協(xié)議傳輸。

        基于 nimbus-jose-jwt 簡(jiǎn)單封裝

        nimbus-jose-jwt 是最受歡迎的 JWT 開(kāi)源庫(kù),基于Apache 2.0開(kāi)源協(xié)議,支持所有標(biāo)準(zhǔn)的簽名(JWS)和加密(JWE)算法。nimbus-jose-jwt 支持使用對(duì)稱(chēng)加密(HMAC)和非對(duì)稱(chēng)加密(RSA)兩種算法來(lái)生成和解析 JWT 令牌。

        下面我們對(duì) nimbus-jose-jwt 進(jìn)行簡(jiǎn)單的封裝,提供以下功能的支持:

        1. 支持使用 HMAC 和 RSA 算法生成和解析 JWT 令牌
        2. 支持私有信息直接作為 Payload,以及標(biāo)準(zhǔn)信息+私有信息作為 Payload。內(nèi)置支持后者。
        3. 提供工具類(lèi)及可擴(kuò)展接口,方便自定義擴(kuò)展開(kāi)發(fā)。

        pom 中添加依賴(lài)

        首先我們?cè)?pom.xml 中引入 nimbus-jose-jwt 的依賴(lài)。

        <dependency>
        ??<groupId>com.nimbusdsgroupId>
        ??<artifactId>nimbus-jose-jwtartifactId>
        ??<version>8.20version>
        dependency>

        JwtConfig

        這個(gè)類(lèi)用于統(tǒng)一管理相關(guān)的參數(shù)配置。

        public?class?JwtConfig?{

        ????//?JWT?在?HTTP?HEADER?中默認(rèn)的?KEY
        ????private?String?tokenName?=?JwtUtils.DEFAULT_TOKEN_NAME;

        ????//?HMAC?密鑰,用于支持?HMAC?算法
        ????private?String?hmacKey;

        ????//?JKS?密鑰路徑,用于支持?RSA?算法
        ????private?String?jksFileName;

        ????//?JKS?密鑰密碼,用于支持?RSA?算法
        ????private?String?jksPassword;

        ????//?證書(shū)密碼,用于支持?RSA?算法
        ????private?String?certPassword;

        ????//?JWT?標(biāo)準(zhǔn)信息:簽發(fā)人?-?iss
        ????private?String?issuer;

        ????//?JWT?標(biāo)準(zhǔn)信息:主題?-?sub
        ????private?String?subject;

        ????//?JWT?標(biāo)準(zhǔn)信息:受眾?-?aud
        ????private?String?audience;

        ????//?JWT?標(biāo)準(zhǔn)信息:生效時(shí)間?-?nbf,未來(lái)多長(zhǎng)時(shí)間內(nèi)生效
        ????private?long?notBeforeIn;
        ????
        ????//?JWT?標(biāo)準(zhǔn)信息:生效時(shí)間?-?nbf,具體哪個(gè)時(shí)間生效
        ????private?long?notBeforeAt;

        ????//?JWT?標(biāo)準(zhǔn)信息:過(guò)期時(shí)間?-?exp,未來(lái)多長(zhǎng)時(shí)間內(nèi)過(guò)期
        ????private?long?expiredIn;

        ????//?JWT?標(biāo)準(zhǔn)信息:過(guò)期時(shí)間?-?exp,具體哪個(gè)時(shí)間過(guò)期
        ????private?long?expiredAt;
        }??

        hmacKey 字段用于支持 HMAC 算法,只要該字段不為空,則使用該值作為 HMAC 的密鑰對(duì) JWT 進(jìn)行簽名與驗(yàn)證。

        jksFileName、jksPassword、certPassword 三個(gè)字段用于支持 RSA 算法,程序?qū)⒆x取證書(shū)文件作為 RSA 密鑰對(duì) JWT 進(jìn)行簽名與驗(yàn)證。

        其他幾個(gè)字段用于設(shè)置 Payload 中需要攜帶的標(biāo)準(zhǔn)信息。

        JwtService

        JwtService 是提供 JWT 簽名與驗(yàn)證的接口,內(nèi)置了 HMACJwtServiceImpl 提供 HMAC 算法的實(shí)現(xiàn)和 RSAJwtServiceImpl 提供 RSA 算法的實(shí)現(xiàn)。兩種算法在獲取密鑰的方式上是有差別的,這里也提出來(lái)成了接口方法。后續(xù)如果要自定義實(shí)現(xiàn),只需要再寫(xiě)一個(gè)具體實(shí)現(xiàn)類(lèi)。

        public?interface?JwtService?{

        ????/**
        ?????*?獲取?key
        ?????*
        ?????*?@return
        ?????*/

        ????Object?genKey();

        ????/**
        ?????*?對(duì)信息進(jìn)行簽名
        ?????*
        ?????*?@param?payload
        ?????*?@return
        ?????*/

        ????String?sign(String?payload);

        ????/**
        ?????*?驗(yàn)證并返回信息
        ?????*
        ?????*?@param?token
        ?????*?@return
        ?????*/

        ????String?verify(String?token);
        }
        public?class?HMACJwtServiceImpl?implements?JwtService?{

        ????private?JwtConfig?jwtConfig;

        ????public?HMACJwtServiceImpl(JwtConfig?jwtConfig)?{
        ????????this.jwtConfig?=?jwtConfig;
        ????}

        ????@Override
        ????public?String?genKey()?{
        ????????String?key?=?jwtConfig.getHmacKey();
        ????????if?(JwtUtils.isEmpty(key))?{
        ????????????throw?new?KeyGenerateException(JwtUtils.KEY_GEN_ERROR,?new?NullPointerException("HMAC?need?a?key"));
        ????????}
        ????????return?key;
        ????}

        ????@Override
        ????public?String?sign(String?info)?{
        ????????return?JwtUtils.signClaimByHMAC(info,?genKey(),?jwtConfig);
        ????}

        ????@Override
        ????public?String?verify(String?token)?{
        ????????return?JwtUtils.verifyClaimByHMAC(token,?genKey(),?jwtConfig);
        ????}
        }
        public?class?RSAJwtServiceImpl?implements?JwtService?{

        ????private?JwtConfig?jwtConfig;

        ????private?RSAKey?rsaKey;

        ????public?RSAJwtServiceImpl(JwtConfig?jwtConfig)?{
        ????????this.jwtConfig?=?jwtConfig;
        ????}

        ????private?InputStream?getCertInputStream()?throws?IOException?{
        ????????//?讀取配置文件中的證書(shū)路徑
        ????????String?jksFile?=?jwtConfig.getJksFileName();
        ????????if?(jksFile.contains("://"))?{
        ????????????//?從本地文件讀取
        ????????????return?new?FileInputStream(new?File(jksFile));
        ????????}?else?{
        ????????????//?從?classpath?讀取
        ????????????return?getClass().getClassLoader().getResourceAsStream(jwtConfig.getJksFileName());
        ????????}
        ????}

        ????@Override
        ????public?RSAKey?genKey()?{
        ????????if?(rsaKey?!=?null)?{
        ????????????return?rsaKey;
        ????????}
        ????????InputStream?is?=?null;
        ????????try?{
        ????????????KeyStore?keyStore?=?KeyStore.getInstance(KeyStore.getDefaultType());
        ????????????is?=?getCertInputStream();
        ????????????keyStore.load(is,?jwtConfig.getJksPassword().toCharArray());
        ????????????Enumeration?aliases?=?keyStore.aliases();
        ????????????String?alias?=?null;
        ????????????while?(aliases.hasMoreElements())?{
        ????????????????alias?=?aliases.nextElement();
        ????????????}
        ????????????RSAPrivateKey?privateKey?=?(RSAPrivateKey)?keyStore.getKey(alias,?jwtConfig.getCertPassword().toCharArray());
        ????????????Certificate?certificate?=?keyStore.getCertificate(alias);
        ????????????RSAPublicKey?publicKey?=?(RSAPublicKey)?certificate.getPublicKey();
        ????????????rsaKey?=?new?RSAKey.Builder(publicKey).privateKey(privateKey).build();
        ????????????return?rsaKey;
        ????????}?catch?(IOException?|?CertificateException?|?UnrecoverableKeyException
        ????????????????|?NoSuchAlgorithmException?|?KeyStoreException?e)?{
        ????????????e.printStackTrace();
        ????????????throw?new?KeyGenerateException(JwtUtils.KEY_GEN_ERROR,?e);
        ????????}?finally?{
        ????????????if?(is?!=?null)?{
        ????????????????try?{
        ????????????????????is.close();
        ????????????????}?catch?(IOException?e)?{
        ????????????????????e.printStackTrace();
        ????????????????}
        ????????????}
        ????????}
        ????}

        ????@Override
        ????public?String?sign(String?payload)?{
        ????????return?JwtUtils.signClaimByRSA(payload,?genKey(),?jwtConfig);
        ????}

        ????@Override
        ????public?String?verify(String?token)?{
        ????????return?JwtUtils.verifyClaimByRSA(token,?genKey(),?jwtConfig);
        ????}
        }

        JwtUtils

        JwtService 的實(shí)現(xiàn)類(lèi)中比較簡(jiǎn)潔,因?yàn)橹饕姆椒ǘ荚?JwtUtils 中提供了。如下是 Payload 中只包含私有信息時(shí),兩種算法的簽名與驗(yàn)證實(shí)現(xiàn)??梢允褂眠@些方法方便的實(shí)現(xiàn)自己的擴(kuò)展。

        ???/**
        ?????*?使用?HMAC?算法簽名信息(Payload?中只包含私有信息)
        ?????*
        ?????*?@param?info
        ?????*?@param?key
        ?????*?@return
        ?????*/

        ????public?static?String?signDirectByHMAC(String?info,?String?key)?{
        ????????try?{
        ????????????JWSHeader?jwsHeader?=?new?JWSHeader.Builder(JWSAlgorithm.HS256)
        ????????????????????.type(JOSEObjectType.JWT)
        ????????????????????.build();

        ????????????//?建立一個(gè)載荷?Payload
        ????????????Payload?payload?=?new?Payload(info);

        ????????????//?將頭部和載荷結(jié)合在一起
        ????????????JWSObject?jwsObject?=?new?JWSObject(jwsHeader,?payload);

        ????????????//?建立一個(gè)密匙
        ????????????JWSSigner?jwsSigner?=?new?MACSigner(key);

        ????????????//?簽名
        ????????????jwsObject.sign(jwsSigner);

        ????????????//?生成?token
        ????????????return?jwsObject.serialize();
        ????????}?catch?(JOSEException?e)?{
        ????????????e.printStackTrace();
        ????????????throw?new?PayloadSignException(JwtUtils.PAYLOAD_SIGN_ERROR,?e);
        ????????}
        ????}

        ????/**
        ?????*?使用?RSA?算法簽名信息(Payload?中只包含私有信息)
        ?????*
        ?????*?@param?info
        ?????*?@param?rsaKey
        ?????*?@return
        ?????*/

        ????public?static?String?signDirectByRSA(String?info,?RSAKey?rsaKey)?{
        ????????try?{
        ????????????JWSSigner?signer?=?new?RSASSASigner(rsaKey);
        ????????????JWSObject?jwsObject?=?new?JWSObject(
        ????????????????????new?JWSHeader.Builder(JWSAlgorithm.RS256).keyID(rsaKey.getKeyID()).build(),
        ????????????????????new?Payload(info)
        ????????????);
        ????????????//?進(jìn)行加密
        ????????????jwsObject.sign(signer);

        ????????????return?jwsObject.serialize();
        ????????}?catch?(JOSEException?e)?{
        ????????????e.printStackTrace();
        ????????????throw?new?PayloadSignException(JwtUtils.PAYLOAD_SIGN_ERROR,?e);
        ????????}
        ????}

        ????/**
        ?????*?使用?HMAC?算法驗(yàn)證?token(Payload?中只包含私有信息)
        ?????*
        ?????*?@param?token
        ?????*?@param?key
        ?????*?@return
        ?????*/

        ????public?static?String?verifyDirectByHMAC(String?token,?String?key)?{
        ????????try?{
        ????????????JWSObject?jwsObject?=?JWSObject.parse(token);
        ????????????//?建立一個(gè)解鎖密匙
        ????????????JWSVerifier?jwsVerifier?=?new?MACVerifier(key);
        ????????????if?(jwsObject.verify(jwsVerifier))?{
        ????????????????return?jwsObject.getPayload().toString();
        ????????????}
        ????????????throw?new?TokenVerifyException(JwtUtils.TOKEN_VERIFY_ERROR,?new?NullPointerException("Payload?can?not?be?null"));
        ????????}?catch?(JOSEException?|?ParseException?e)?{
        ????????????e.printStackTrace();
        ????????????throw?new?TokenVerifyException(JwtUtils.TOKEN_VERIFY_ERROR,?e);
        ????????}
        ????}

        ????/**
        ?????*?使用?RSA?算法驗(yàn)證?token(Payload?中只包含私有信息)
        ?????*
        ?????*?@param?token
        ?????*?@param?rsaKey
        ?????*?@return
        ?????*/

        ????public?static?String?verifyDirectByRSA(String?token,?RSAKey?rsaKey)?{
        ????????try?{
        ????????????RSAKey?publicRSAKey?=?rsaKey.toPublicJWK();
        ????????????JWSObject?jwsObject?=?JWSObject.parse(token);
        ????????????JWSVerifier?jwsVerifier?=?new?RSASSAVerifier(publicRSAKey);
        ????????????//?驗(yàn)證數(shù)據(jù)
        ????????????if?(jwsObject.verify(jwsVerifier))?{
        ????????????????return?jwsObject.getPayload().toString();
        ????????????}
        ????????????throw?new?TokenVerifyException(JwtUtils.TOKEN_VERIFY_ERROR,?new?NullPointerException("Payload?can?not?be?null"));
        ????????}?catch?(JOSEException?|?ParseException?e)?{
        ????????????e.printStackTrace();
        ????????????throw?new?TokenVerifyException(JwtUtils.TOKEN_VERIFY_ERROR,?e);
        ????????}
        ????}

        JwtException

        定義統(tǒng)一的異常類(lèi),可以屏蔽 nimbus-jose-jwt 以及其他諸如加載證書(shū)錯(cuò)誤拋出的異常,并且在其他項(xiàng)目集成我們封裝好的庫(kù)的時(shí)候,方便的進(jìn)行異常處理。

        在 JwtService 實(shí)現(xiàn)的不同階段,我們封裝了不同的 JwtException 子類(lèi),來(lái)方便外部根據(jù)需要做對(duì)應(yīng)的處理。如異常是 KeyGenerateException,則處理成服務(wù)器處理錯(cuò)誤;如異常是 TokenVerifyException,則處理成 Token 驗(yàn)證失敗,無(wú)權(quán)限。

        JwtContext

        JWT 用于用戶(hù)認(rèn)證,經(jīng)常在 Token 驗(yàn)證完成后,程序中需要獲取到當(dāng)前登錄的用戶(hù)信息, JwtContext 中提供了通過(guò)線程局部變量保存信息的方法。

        public?class?JwtContext?{

        ????private?static?final?String?KEY_TOKEN?=?"token";
        ????private?static?final?String?KEY_PAYLOAD?=?"payload";

        ????private?static?ThreadLocal>?context?=?new?ThreadLocal<>();

        ????private?JwtContext()?{}

        ????public?static?void?set(Object?key,?Object?value)?{
        ????????Map?locals?=?context.get();
        ????????if?(locals?==?null)?{
        ????????????locals?=?new?HashMap<>();
        ????????????context.set(locals);
        ????????}
        ????????locals.put(key,?value);
        ????}

        ????public?static?Object?get(Object?key)?{
        ????????Map?locals?=?context.get();
        ????????if?(locals?!=?null)?{
        ????????????return?locals.get(key);
        ????????}
        ????????return?null;
        ????}

        ????public?static?void?remove(Object?key)?{
        ????????Map?locals?=?context.get();
        ????????if?(locals?!=?null)?{
        ????????????locals.remove(key);
        ????????????if?(locals.isEmpty())?{
        ????????????????context.remove();
        ????????????}
        ????????}
        ????}

        ????public?static?void?removeAll()?{
        ????????Map?locals?=?context.get();
        ????????if?(locals?!=?null)?{
        ????????????locals.clear();
        ????????}
        ????????context.remove();
        ????}

        ????public?static?void?setToken(String?token)?{
        ????????set(KEY_TOKEN,?token);
        ????}

        ????public?static?String?getToken()?{
        ????????return?(String)?get(KEY_TOKEN);
        ????}

        ????public?static?void?setPayload(Object?payload)?{
        ????????set(KEY_PAYLOAD,?payload);
        ????}

        ????public?static?Object?getPayload()?{
        ????????return?get(KEY_PAYLOAD);
        ????}
        }

        @AuthRequired

        在項(xiàng)目實(shí)戰(zhàn)中,并不是所有 Controller 中的方法都必須傳 Token,通過(guò) @AuthRequired 注解來(lái)區(qū)分方法是否需要校驗(yàn) Token。

        /**
        ?*?應(yīng)用于?Controller?中的方法,標(biāo)識(shí)是否攔截進(jìn)行?JWT?驗(yàn)證
        ?*/

        @Target({ElementType.METHOD,?ElementType.TYPE})
        public?@interface?AuthRequired?{

        ????boolean?required()?default?true;
        }

        Spring Boot 集成 JWT 實(shí)例

        有了上面封裝好的庫(kù),我們?cè)?SpringBoot 項(xiàng)目中集成 JWT。創(chuàng)建好 Spring Boot 項(xiàng)目后,我們編寫(xiě)下面主要的類(lèi)。

        JwtDemoInterceptor

        在 Spring Boot 項(xiàng)目中,通過(guò)自定義 HandlerInterceptor 的實(shí)現(xiàn)類(lèi)可以對(duì)請(qǐng)求和響應(yīng)進(jìn)行攔截,我們新建 JwtDemoInterceptor 類(lèi)進(jìn)行攔截。

        public?class?JwtDemoInterceptor?implements?HandlerInterceptor?{

        ????private?static?final?Logger?logger?=?LoggerFactory.getLogger(JwtDemoInterceptor.class);

        ????private?static?final?String?PREFIX_BEARER?=?"Bearer?";

        ????@Autowired
        ????private?JwtConfig?jwtConfig;

        ????@Autowired
        ????private?JwtService?jwtService;

        ????/**
        ?????*?預(yù)處理回調(diào)方法,實(shí)現(xiàn)處理器的預(yù)處理(如檢查登陸),第三個(gè)參數(shù)為響應(yīng)的處理器,自定義?Controller
        ?????*?返回值:
        ?????*?true?表示繼續(xù)流程(如調(diào)用下一個(gè)攔截器或處理器);
        ?????*?false?表示流程中斷(如登錄檢查失?。粫?huì)繼續(xù)調(diào)用其他的攔截器或處理器,此時(shí)我們需要通過(guò)?response?來(lái)產(chǎn)生響應(yīng)。
        ?????*/

        ????@Override
        ????public?boolean?preHandle(HttpServletRequest?request,?HttpServletResponse?response,?Object?handler)?throws?Exception?{
        ????????//?如果不是映射到方法直接通過(guò)
        ????????if(!(handler?instanceof?HandlerMethod)){
        ????????????return?true;
        ????????}

        ????????HandlerMethod?handlerMethod?=?(HandlerMethod)?handler;
        ????????Method?method?=?handlerMethod.getMethod();
        ????????//?檢查是否有?@AuthRequired?注解,有且?required()?為?false?則跳過(guò)
        ????????if?(method.isAnnotationPresent(AuthRequired.class))?{
        ????????????AuthRequired?authRequired?=?method.getAnnotation(AuthRequired.class);
        ????????????if?(!authRequired.required())?{
        ????????????????return?true;
        ????????????}
        ????????}

        ????????String?token?=?request.getHeader(jwtConfig.getTokenName());

        ????????logger.info("token:?{}",?token);

        ????????if?(StringUtils.isEmpty(token)?||?token.trim().equals(PREFIX_BEARER.trim()))?{
        ????????????return?true;
        ????????}

        ????????token?=?token.replace(PREFIX_BEARER,?"");

        ????????String?payload?=?jwtService.verify(token);

        ????????//?設(shè)置線程局部變量中的?token
        ????????JwtContext.setToken(token);
        ????????JwtContext.setPayload(payload);
        ????????return?true;
        ????}

        ????/**
        ?????*?后處理回調(diào)方法,實(shí)現(xiàn)處理器的后處理(但在渲染視圖之前),此時(shí)我們可以通過(guò)?modelAndView(模型和視圖對(duì)象)對(duì)模型數(shù)據(jù)進(jìn)行處理或?qū)σ晥D進(jìn)行處理,modelAndView?也可能為null。
        ?????*/

        ????@Override
        ????public?void?postHandle(HttpServletRequest?request,?HttpServletResponse?response,?Object?handler,?ModelAndView?modelAndView)?throws?Exception?{

        ????}

        ????/**
        ?????*?整個(gè)請(qǐng)求處理完畢回調(diào)方法,即在視圖渲染完畢時(shí)回調(diào),如性能監(jiān)控中我們可以在此記錄結(jié)束時(shí)間并輸出消耗時(shí)間,還可以進(jìn)行一些資源清理,類(lèi)似于?try-catch-finally?中的?finally
        ?????*?但僅調(diào)用處理器執(zhí)行鏈中?preHandle?返回?true?的攔截器的?afterCompletion。
        ?????*/

        ????@Override
        ????public?void?afterCompletion(HttpServletRequest?request,?HttpServletResponse?response,?Object?handler,?Exception?ex)?throws?Exception?{
        ????????JwtContext.removeAll();
        ????}
        }

        preHandle、postHandle、afterCompletion 三個(gè)方法的具體作用,可以看代碼上的注釋。

        preHandle 中這段代碼中的邏輯如下:

        1. 攔截被 @AuthRequired 注解的方法,只要不是 required = false 都會(huì)進(jìn)行 Token 的校驗(yàn)。
        2. 從請(qǐng)求中解析出 Token,對(duì) Token 進(jìn)行驗(yàn)證。如果驗(yàn)證異常,會(huì)在方法中拋出異常。
        3. Token 驗(yàn)證通過(guò),會(huì)在線程局部變量中設(shè)置相關(guān)信息,以便后續(xù)程序獲取處理。

        afterCompletion 中這段代碼對(duì)線程變量進(jìn)行了清理。

        InterceptorConfig

        定義 InterceptorConfig,通過(guò) @Configuration 注解,Spring 會(huì)加載該類(lèi),并完成裝配。

        addInterceptors 方法中設(shè)置攔截器,并攔截所有請(qǐng)求。

        jwtDemoConfig 方法中注入 JwtConfig,并設(shè)置了 HMACKey。

        jwtDemoService 方法會(huì)根據(jù)注入的 JwtConfig 配置,生成具體的 JwtService,這里是 HMACJwtServiceImpl。

        @Configuration
        public?class?InterceptorConfig?implements?WebMvcConfigurer?{

        ????@Override
        ????public?void?addInterceptors(InterceptorRegistry?registry)?{
        ????????registry.addInterceptor(jwtDemoInterceptor()).addPathPatterns("/**");
        ????}

        ????@Bean
        ????public?JwtDemoInterceptor?jwtDemoInterceptor()?{
        ????????return?new?JwtDemoInterceptor();
        ????}

        ????@Bean
        ????public?JwtConfig?jwtDemoConfig()?{
        ????????JwtConfig?jwtConfig?=?new?JwtConfig();
        ????????jwtConfig.setHmacKey("cb9915297c8b43e820afd2a90a1e36cb");

        ????????return?jwtConfig;
        ????}

        ????@Bean
        ????public?JwtService?jwtDemoService()?{
        ????????return?JwtUtils.obtainJwtService(jwtDemoConfig());
        ????}

        }

        編寫(xiě)測(cè)試 Controller

        @RestController
        public?class?UserController?{

        ????@Autowired
        ????private?ObjectMapper?objectMapper;

        ????@Autowired
        ????private?JwtService?jwtService;

        ????@GetMapping("/sign")
        ????@AuthRequired(required?=?false)
        ????public?String?sign()?throws?JsonProcessingException?{

        ????????UserDTO?userDTO?=?new?UserDTO();
        ????????userDTO.setName("fatfoo");
        ????????userDTO.setPassword("112233");
        ????????userDTO.setSex(0);

        ????????String?payload?=?objectMapper.writeValueAsString(userDTO);

        ????????return?jwtService.sign(payload);
        ????}

        ????@GetMapping("/verify")
        ????public?UserDTO?verify()?throws?IOException?{
        ????????String?payload?=?(String)?JwtContext.getPayload();
        ????????return?objectMapper.readValue(payload,?UserDTO.class);
        ????}
        }

        sign 方法對(duì)用戶(hù)信息進(jìn)行簽名并返回 Token;由于 @AuthRequired(required = false) 攔截器將不會(huì)對(duì)其進(jìn)行攔截。

        verify 方法在 Token 通過(guò)驗(yàn)證后,獲取解析出的信息并返回。

        用 Postman 進(jìn)行測(cè)試

        訪問(wèn) sign 接口,返回簽名 Token。

        在 Header 中添加 Token 信息,請(qǐng)求 verify 接口,返回用戶(hù)信息。

        測(cè)試 RSA 算法實(shí)現(xiàn)

        上面我們只設(shè)置了 JwtConfig 的 hmacKey 參數(shù),使用的是 HMAC 算法進(jìn)行簽名和驗(yàn)證。本節(jié)我們演示 RSA 算法進(jìn)行簽名和驗(yàn)證的實(shí)現(xiàn)。

        生成簽名文件

        使用 Java 自帶的 keytool 工具可以方便的生成證書(shū)文件。

        ???resources?git:(master)???keytool?-genkey?-alias?jwt?-keyalg?RSA?-keystore?jwt.jks
        輸入密鑰庫(kù)口令:
        密鑰庫(kù)口令太短?-?至少必須為?6?個(gè)字符
        輸入密鑰庫(kù)口令:?ronjwt
        再次輸入新口令:?ronjwt
        您的名字與姓氏是什么?
        ??[Unknown]:??ron
        您的組織單位名稱(chēng)是什么?
        ??[Unknown]:??ron
        您的組織名稱(chēng)是什么?
        ??[Unknown]:??ron
        您所在的城市或區(qū)域名稱(chēng)是什么?
        ??[Unknown]:??Xiamen
        您所在的省/市/自治區(qū)名稱(chēng)是什么?
        ??[Unknown]:??Fujian
        該單位的雙字母國(guó)家/地區(qū)代碼是什么?
        ??[Unknown]:??CN
        CN=ron,?OU=ron,?O=ron,?L=Xiamen,?ST=Fujian,?C=CN是否正確?
        ??[否]:??是

        輸入??的密鑰口令
        ?(如果和密鑰庫(kù)口令相同,?按回車(chē)):

        Warning:
        JKS?密鑰庫(kù)使用專(zhuān)用格式。建議使用?"keytool?-importkeystore?-srckeystore?jwt.jks?-destkeystore?jwt.jks?-deststoretype?pkcs12"?遷移到行業(yè)標(biāo)準(zhǔn)格式?PKCS12。

        文件生成后,復(fù)制到項(xiàng)目的 resource 目錄下。

        設(shè)置 JwtConfig 參數(shù)

        修改上節(jié) InterceptorConfig 中的 jwtDemoConfig 方法,這是 jksFileName、jksPassword、certPassword 3 個(gè)參數(shù)。

        @Bean
        public?JwtConfig?jwtDemoConfig()?{
        ????JwtConfig?jwtConfig?=?new?JwtConfig();
        //????????jwtConfig.setHmacKey("cb9915297c8b43e820afd2a90a1e36cb");

        ????jwtConfig.setJksFileName("jwt.jks");
        ????jwtConfig.setJksPassword("ronjwt");
        ????jwtConfig.setCertPassword("ronjwt");
        ????return?jwtConfig;
        }

        不要設(shè)置 hmacKey 參數(shù),否則會(huì)加載 HMACJwtServiceImpl。因?yàn)?JwtUtils#obtainJwtService 方法實(shí)現(xiàn)如下:

        /**
        ?*?獲取內(nèi)置?JwtService?的工廠方法。
        ?*
        ?*?優(yōu)先采用?HMAC?算法實(shí)現(xiàn)
        ?*
        ?*?@param?jwtConfig
        ?*?@return
        ?*/

        public?static?JwtService?obtainJwtService(JwtConfig?jwtConfig)?{
        ????if?(!JwtUtils.isEmpty(jwtConfig.getHmacKey()))?{
        ????????return?new?HMACJwtServiceImpl(jwtConfig);
        ????}

        ????return?new?RSAJwtServiceImpl(jwtConfig);
        }

        這樣就可以進(jìn)行 RSA 算法簽名與驗(yàn)證的測(cè)試了。運(yùn)行程序并使用 Postman 測(cè)試,請(qǐng)自行查看區(qū)別。

        END


        有熱門(mén)推薦?

        1.?面試官靈魂一問(wèn): MySQL 的 delete、truncate、drop 有什么區(qū)別?

        2.?什么是堡壘機(jī)?為什么需要堡壘機(jī)?

        3.?IntelliJ IDEA 15款 神級(jí)超級(jí)牛逼插件推薦(自用,真的超級(jí)牛逼)

        4.?后端必備 Git 分支開(kāi)發(fā):規(guī)范指南

        最近面試BAT,整理一份面試資料Java面試BATJ通關(guān)手冊(cè),覆蓋了Java核心技術(shù)、JVM、Java并發(fā)、SSM、微服務(wù)、數(shù)據(jù)庫(kù)、數(shù)據(jù)結(jié)構(gòu)等等。

        獲取方式:點(diǎn)“在看”,關(guān)注公眾號(hào)并回復(fù)?Java?領(lǐng)取,更多內(nèi)容陸續(xù)奉上。

        文章有幫助的話,在看,轉(zhuǎn)發(fā)吧。

        謝謝支持喲 (*^__^*)

        瀏覽 47
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            又大又硬又爽的视频 | 欧美色另类 | 高清无码日韩A级黄色高潮免费看视频 | 中文字幕无码毛片免费看 | 日韩黄片在线视频 | 洋洋AV | 日本黄色大片日本美女 | 人人操人人操人人操人人 | 午夜男女爽爽爽 | 艹女人的逼|