您好,登錄后才能下訂單哦!
這期內(nèi)容當(dāng)中小編將會(huì)給大家?guī)?lái)有關(guān)Spring Boot 中怎么使用JWT 實(shí)現(xiàn)用戶(hù)登錄認(rèn)證,文章內(nèi)容豐富且以專(zhuān)業(yè)的角度為大家分析和敘述,閱讀完這篇文章希望大家可以有所收獲。
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)行簽名。
用戶(hù)使用賬號(hào)和密碼發(fā)起 POST 請(qǐng)求;
服務(wù)器使用私鑰創(chuàng)建一個(gè) JWT;
服務(wù)器返回這個(gè) JWT 給瀏覽器;
瀏覽器將該 JWT 串在請(qǐng)求頭中像服務(wù)器發(fā)送請(qǐng)求;
服務(wù)器驗(yàn)證該 JWT;
返回響應(yī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 是由三段信息構(gòu)成的,將這三段信息文本用 .
連接一起就構(gòu)成了 JWT 字符串。
JWT 的三個(gè)部分依次為頭部:Header,負(fù)載:Payload 和簽名:Signature。
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 部分也是一個(gè) JSON 對(duì)象,用來(lái)存放實(shí)際需要傳遞的有效信息。有效信息包含三個(gè)部分:
標(biāo)準(zhǔn)中注冊(cè)的聲明
公共的聲明
私有的聲明
標(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 部分是對(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ù)。
前面提到,Header 和 Payload 串型化的算法是 Base64URL。這個(gè)算法跟 Base64 算法基本類(lèi)似,但有一些小的不同。
JWT 作為一個(gè)令牌(token),有些場(chǎng)合可能會(huì)放到 URL(比如 api.example.com/?token=xxx
)。Base64 有三個(gè)字符 +
、 /
和 =
,在 URL 里面有特殊含義,所以要被替換掉:=
被省略、+
替換成 -
,/
替換成 _
。這就是 Base64URL 算法。
客戶(hù)端收到服務(wù)器返回的 JWT 之后需要在本地做保存。此后,客戶(hù)端每次與服務(wù)器通信,都要帶上這個(gè) JWT。一般的的做法是放在 HTTP 請(qǐng)求的頭信息 Authorization
字段里面。
Authorization: Bearer <token>
這樣每個(gè)請(qǐng)求中,服務(wù)端就可以在請(qǐng)求頭中拿到 JWT 進(jìn)行解析與認(rèn)證。
JWT 默認(rèn)是不加密,但也是可以加密的。生成原始 Token 以后,可以用密鑰再加密一次。
JWT 不加密的情況下,不能將秘密數(shù)據(jù)寫(xiě)入 JWT。
JWT 不僅可以用于認(rèn)證,也可以用于交換信息。有效使用 JWT,可以降低服務(wù)器查詢(xún)數(shù)據(jù)庫(kù)的次數(shù)。
JWT 的最大缺點(diǎn)是,由于服務(wù)器不保存 session 狀態(tài),因此無(wú)法在使用過(guò)程中廢止某個(gè) token,或者更改 token 的權(quán)限。也就是說(shuō),一旦 JWT 簽發(fā)了,在到期之前就會(huì)始終有效,除非服務(wù)器部署額外的邏輯。
JWT 本身包含了認(rèn)證信息,一旦泄露,任何人都可以獲得該令牌的所有權(quán)限。為了減少盜用,JWT 的有效期應(yīng)該設(shè)置得比較短。對(duì)于一些比較重要的權(quán)限,使用時(shí)應(yīng)該再次對(duì)用戶(hù)進(jìn)行認(rèn)證。
為了減少盜用,JWT 不應(yīng)該使用 HTTP 協(xié)議明碼傳輸,要使用 HTTPS 協(xié)議傳輸。
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)單的封裝,提供以下功能的支持:
支持使用 HMAC 和 RSA 算法生成和解析 JWT 令牌
支持私有信息直接作為 Payload,以及標(biāo)準(zhǔn)信息+私有信息作為 Payload。內(nèi)置支持后者。
提供工具類(lèi)及可擴(kuò)展接口,方便自定義擴(kuò)展開(kāi)發(fā)。
首先我們?cè)?pom.xml 中引入 nimbus-jose-jwt 的依賴(lài)。
<dependency> <groupid>com.nimbusds</groupid> <artifactid>nimbus-jose-jwt</artifactid> <version>8.20</version> </dependency>
這個(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 是提供 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<string> 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); } }
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); } }
定義統(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)限。
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<map<object, object>> context = new ThreadLocal<>(); private JwtContext() {} public static void set(Object key, Object value) { Map<object, object> locals = context.get(); if (locals == null) { locals = new HashMap<>(); context.set(locals); } locals.put(key, value); } public static Object get(Object key) { Map<object, object> locals = context.get(); if (locals != null) { return locals.get(key); } return null; } public static void remove(Object key) { Map<object, object> locals = context.get(); if (locals != null) { locals.remove(key); if (locals.isEmpty()) { context.remove(); } } } public static void removeAll() { Map<object, object> 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); } }
在項(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; }
有了上面封裝好的庫(kù),我們?cè)?SpringBoot 項(xiàng)目中集成 JWT。創(chuàng)建好 Spring Boot 項(xiàng)目后,我們編寫(xiě)下面主要的類(lèi)。
在 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
中這段代碼中的邏輯如下:
攔截被 @AuthRequired 注解的方法,只要不是 required = false
都會(huì)進(jìn)行 Token 的校驗(yàn)。
從請(qǐng)求中解析出 Token,對(duì) Token 進(jìn)行驗(yàn)證。如果驗(yàn)證異常,會(huì)在方法中拋出異常。
Token 驗(yàn)證通過(guò),會(huì)在線程局部變量中設(shè)置相關(guān)信息,以便后續(xù)程序獲取處理。
afterCompletion
中這段代碼對(duì)線程變量進(jìn)行了清理。
定義 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()); } }
@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)證后,獲取解析出的信息并返回。
訪問(wèn) sign 接口,返回簽名 Token。
在 Header 中添加 Token 信息,請(qǐng)求 verify 接口,返回用戶(hù)信息。
上面我們只設(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是否正確? [否]: 是 輸入 <jwt> 的密鑰口令 (如果和密鑰庫(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)目的 resources 目錄下。
修改上節(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); }
上述就是小編為大家分享的Spring Boot 中怎么使用JWT 實(shí)現(xiàn)用戶(hù)登錄認(rèn)證了,如果剛好有類(lèi)似的疑惑,不妨參照上述分析進(jìn)行理解。如果想知道更多相關(guān)知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。