'(建議收藏) | Spring Boot集成JSON Web Token(JWT)'

JSON 瀏覽器 收藏 Redis Java實用技術 2019-09-17
"

本號主要用於分享企業中常用的技術,更加側重於實用,歡迎關注,便於瀏覽其它更多實用的歷史文章。

一:認證

在瞭解JWT之前先來回顧一下傳統session認證和基於token認證。

1.1 傳統session認證

http協議是一種無狀態協議,即瀏覽器發送請求到服務器,服務器是不知道這個請求是哪個用戶發來的。為了讓服務器知道請求是哪個用戶發來的,需要讓用戶提供用戶名和密碼來進行認證。當瀏覽器第一次訪問服務器(假設是登錄接口),服務器驗證用戶名和密碼之後,服務器會生成一個sessionid(只有第一次會生成,其它會使用同一個sessionid),並將該session和用戶信息關聯起來,然後將sessionid返回給瀏覽器,瀏覽器收到sessionid保存到Cookie中,當用戶第二次訪問服務器是就會攜帶Cookie值,服務器獲取到Cookie值,進而獲取到sessionid,根據sessionid獲取關聯的用戶信息。

public User login(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
String username = request.getParameter("username");
String password = request.getParameter("password");
User user = userService.login(username, password);
if (user == null) {
throw new AuthenticationException("用戶名或密碼錯誤");
}
// 返回這次請求關聯的當前會話,如果沒有會話則創建一個新的
// 需要在服務器端記錄該session
HttpSession session = request.getSession();
session.setAttribute("user", user);
// 讓瀏覽器保存sessionid到cookie中
// Cookie cookie = new Cookie("sessionid", session.getId());
// cookie.setPath("/");
// response.addCookie(cookie);
return user;
}
public Object getUserInfo(HttpServletRequest request){
// 從request中獲取Cookie
// 從Cookie中獲取sessionid
// 根據sessionid獲取對應的Session對象
// 從session中獲取關聯的用戶信息
HttpSession session = request.getSession();
Object user = session.getAttribute("user");
return user;
}

session的缺點:

  • Session: 每個用戶經過我們的應用認證之後,我們的應用都要在服務端做一次記錄,以方便用戶下次請求的鑑別,通常而言session都是保存在內存中,而隨著認證用戶的增多,服務端的開銷會明顯增大。
  • 擴展性: 用戶認證之後,服務端做認證記錄,如果認證的記錄被保存在內存中的話,這意味著用戶下次請求還必須要請求在這臺服務器上,這樣才能拿到授權的資源,這樣在分佈式的應用上,相應的限制了負載均衡器的能力。這也意味著限制了應用的擴展能力。即不能滿足單點登陸
  • CSRF: 因為是基於cookie來進行用戶識別的, cookie如果被截獲,用戶就會很容易受到跨站請求偽造的攻擊。

1.2 基於token認證

token原理:

  1. 使用用戶名和密碼請求登錄接口
  2. 登錄接口驗證用戶名和密碼
  3. 登錄接口生成一個uuid作為token,將用戶信息作為值,然後保存到redis緩存中jedis.set(token, user);
  4. 登錄接口返回用戶信息和token
  5. 瀏覽器將token保存到本地
  6. 當請求其它接口時就攜帶token值
  7. 接口根據token去緩存中查,如果找到了就調用接口,如果找不到報token錯誤(一般通過攔截器來實現檢查)
public String auth(String username, String password) throws AuthenticationException {
User user = userService.login(username, password);
if (user == null) {
throw new AuthenticationException("用戶名或密碼錯誤");
}
String token = UUID.randomUUID().toString();
redisClient.set(token, user);
return token;
}
public Object getUserInfo(@RequestHeader("token") String token) throws AuthenticationException {
User user = redisClient.get(token);
if (user == null) {
throw new AuthenticationException("token不可用");
}
return user;
}

session和token的區別:

此種方式原理上和session方式差不多,都是客戶端調用接口時攜帶一個值,服務器通過該值來獲取用戶的信息。

不同的是session是將信息保存到本機內存中,對負載均衡有限制(只能負載到同一臺機器),token是保存到緩存服務器(redis)中,對負載均衡沒有限制,如果使用同一個redis服務器還可以保證單點登錄。session一般用在PC上,token即可用在PC上也可以用在APP上。

1.3 JWT

"

本號主要用於分享企業中常用的技術,更加側重於實用,歡迎關注,便於瀏覽其它更多實用的歷史文章。

一:認證

在瞭解JWT之前先來回顧一下傳統session認證和基於token認證。

1.1 傳統session認證

http協議是一種無狀態協議,即瀏覽器發送請求到服務器,服務器是不知道這個請求是哪個用戶發來的。為了讓服務器知道請求是哪個用戶發來的,需要讓用戶提供用戶名和密碼來進行認證。當瀏覽器第一次訪問服務器(假設是登錄接口),服務器驗證用戶名和密碼之後,服務器會生成一個sessionid(只有第一次會生成,其它會使用同一個sessionid),並將該session和用戶信息關聯起來,然後將sessionid返回給瀏覽器,瀏覽器收到sessionid保存到Cookie中,當用戶第二次訪問服務器是就會攜帶Cookie值,服務器獲取到Cookie值,進而獲取到sessionid,根據sessionid獲取關聯的用戶信息。

public User login(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
String username = request.getParameter("username");
String password = request.getParameter("password");
User user = userService.login(username, password);
if (user == null) {
throw new AuthenticationException("用戶名或密碼錯誤");
}
// 返回這次請求關聯的當前會話,如果沒有會話則創建一個新的
// 需要在服務器端記錄該session
HttpSession session = request.getSession();
session.setAttribute("user", user);
// 讓瀏覽器保存sessionid到cookie中
// Cookie cookie = new Cookie("sessionid", session.getId());
// cookie.setPath("/");
// response.addCookie(cookie);
return user;
}
public Object getUserInfo(HttpServletRequest request){
// 從request中獲取Cookie
// 從Cookie中獲取sessionid
// 根據sessionid獲取對應的Session對象
// 從session中獲取關聯的用戶信息
HttpSession session = request.getSession();
Object user = session.getAttribute("user");
return user;
}

session的缺點:

  • Session: 每個用戶經過我們的應用認證之後,我們的應用都要在服務端做一次記錄,以方便用戶下次請求的鑑別,通常而言session都是保存在內存中,而隨著認證用戶的增多,服務端的開銷會明顯增大。
  • 擴展性: 用戶認證之後,服務端做認證記錄,如果認證的記錄被保存在內存中的話,這意味著用戶下次請求還必須要請求在這臺服務器上,這樣才能拿到授權的資源,這樣在分佈式的應用上,相應的限制了負載均衡器的能力。這也意味著限制了應用的擴展能力。即不能滿足單點登陸
  • CSRF: 因為是基於cookie來進行用戶識別的, cookie如果被截獲,用戶就會很容易受到跨站請求偽造的攻擊。

1.2 基於token認證

token原理:

  1. 使用用戶名和密碼請求登錄接口
  2. 登錄接口驗證用戶名和密碼
  3. 登錄接口生成一個uuid作為token,將用戶信息作為值,然後保存到redis緩存中jedis.set(token, user);
  4. 登錄接口返回用戶信息和token
  5. 瀏覽器將token保存到本地
  6. 當請求其它接口時就攜帶token值
  7. 接口根據token去緩存中查,如果找到了就調用接口,如果找不到報token錯誤(一般通過攔截器來實現檢查)
public String auth(String username, String password) throws AuthenticationException {
User user = userService.login(username, password);
if (user == null) {
throw new AuthenticationException("用戶名或密碼錯誤");
}
String token = UUID.randomUUID().toString();
redisClient.set(token, user);
return token;
}
public Object getUserInfo(@RequestHeader("token") String token) throws AuthenticationException {
User user = redisClient.get(token);
if (user == null) {
throw new AuthenticationException("token不可用");
}
return user;
}

session和token的區別:

此種方式原理上和session方式差不多,都是客戶端調用接口時攜帶一個值,服務器通過該值來獲取用戶的信息。

不同的是session是將信息保存到本機內存中,對負載均衡有限制(只能負載到同一臺機器),token是保存到緩存服務器(redis)中,對負載均衡沒有限制,如果使用同一個redis服務器還可以保證單點登錄。session一般用在PC上,token即可用在PC上也可以用在APP上。

1.3 JWT

(建議收藏) | Spring Boot集成JSON Web Token(JWT)

1.3.1 簡介

JSON Web Token(JWT)是為了在網絡應用環境間傳遞聲明而執行的一種基於JSON的開放標準((RFC 7519),它定義了一種緊湊(Compact)且自包含(Self-contained)的方式,用於在各方之間以JSON對象安全傳輸信息。 這些信息可以通過數字簽名進行驗證和信任。 可以使用祕密(使用HMAC算法)或使用RSA的公鑰/私鑰對對JWT進行簽名。JWT的聲明一般被用來在身份提供者和服務提供者間傳遞被認證的用戶身份信息,以便於從資源服務器獲取資源,也可以增加一些額外的其它業務邏輯所必須的聲明信息,該token也可直接被用於認證,也可被加密。是目前最流行的跨域認證解決方案。

  • Compact(緊湊): 由於它們尺寸較小,JWT可以通過URL,POST參數或HTTP標頭內發送。 另外,尺寸越小意味著傳輸速度越快。
  • Self-contained(自包含): 有效載荷(Playload)包含有關用戶的所有必需信息,避免了多次查詢數據庫。

1.3.2 應用場景

  • Authentication(鑑權): 這是使用JWT最常見的情況。 一旦用戶登錄,每個後續請求都將包含JWT,允許用戶訪問該令牌允許的路由,服務和資源。 單點登錄是當今廣泛使用JWT的一項功能,因為它的開銷很小,並且能夠輕鬆地跨不同域使用。
  • 分佈式站點的單點登錄(SSO)
  • Information Exchange(信息交換): JSON Web Tokens是在各方之間安全傳輸信息的好方式。 因為JWT可以簽名:例如使用公鑰/私鑰對,所以可以確定發件人是他們自稱的人。 此外,由於使用標頭和有效載荷計算簽名,因此您還可以驗證內容是否未被篡改。

1.3.3 語法

jwt有3個組成部分,每部分通過點號來分割 header.payload.signature

  • 頭部(header) 是一個 JSON 對象,描述 JWT 的元數據,通常是下面的樣子
  • 載荷(payload) 是一個 JSON 對象,用來存放實際需要傳遞的數據
  • 簽證(signature) 對header和payload使用密鑰進行簽名,防止數據篡改。

① 頭部header

Jwt的頭部是一個JSON,然後使用Base64URL編碼,承載兩部分信息:

  • 聲明類型typ,表示這個令牌(token)的類型(type),JWT令牌統一寫為JWT
  • 聲明加密的算法alg,通常直接使用HMACSHA256,就是HS256了,也可以使用RSA,支持很多算法(HS256、HS384、HS512、RS256、RS384、RS512、ES256、ES384、ES512、PS256、PS384)

var header = Base64URL({ "alg": "HS256", "typ": "JWT"})

Base64URL:Header 和 Payload 串型化的算法是 Base64URL。這個算法跟 Base64 算法基本類似,但有一些小的不同。JWT 作為一個令牌(token),有些場合可能會放到 URL(比如 api.example.com/?token=xxx)。Base64 有三個字符+、/和=,在 URL 裡面有特殊含義,所以要被替換掉:=被省略、+替換成-,/替換成_ 。這就是 Base64URL 算法。

② 載荷payload

payload也是一個JSON字符串,是承載消息具體內容的地方,也需要使用Base64URL編碼,payload中可以包含預定義的7個可用,它們不是強制性的,但推薦使用,也可以添加任意自定義的key

  1. iss(issuer): jwt簽發者
  2. sub(subject): jwt所面向的用戶
  3. aud(audience): 接收jwt的一方, 受眾
  4. exp(expiration time): jwt的過期時間,這個過期時間必須要大於簽發時間
  5. nbf(Not Before): 生效時間,定義在什麼時間之前.
  6. iat(Issued At): jwt的簽發時間
  7. jti(JWT ID): jwt的唯一身份標識,主要用來作為一次性token,從而回避重放攻擊。

// 該token簽發給1234567890,姓名為John Doe(自定義的字段),簽發時間為1516239022

var payload = Base64URL( {"sub": "1234567890", "name": "John Doe", "iat": 1516239022})

注意,JWT中payload是不加密的,只是Base64URL編碼一下,任何人拿到都可以進行解碼,所以不要把敏感信息放到裡面。

③ signature

Signature 部分是對前兩部分的簽名,防止數據篡改。

var header = Base64URL({ "alg": "HS256", "typ": "JWT"});
var payload = Base64URL( {"sub": "1234567890", "name": "John Doe", "iat": 1516239022});
var secret = "私鑰";
var signature = HMACSHA256(header + "." + payload, secret);
var jwt = header + "." + payload + "." + signature;

我們可以使用jwt.io調試器來解碼,驗證和生成JWT:

"

本號主要用於分享企業中常用的技術,更加側重於實用,歡迎關注,便於瀏覽其它更多實用的歷史文章。

一:認證

在瞭解JWT之前先來回顧一下傳統session認證和基於token認證。

1.1 傳統session認證

http協議是一種無狀態協議,即瀏覽器發送請求到服務器,服務器是不知道這個請求是哪個用戶發來的。為了讓服務器知道請求是哪個用戶發來的,需要讓用戶提供用戶名和密碼來進行認證。當瀏覽器第一次訪問服務器(假設是登錄接口),服務器驗證用戶名和密碼之後,服務器會生成一個sessionid(只有第一次會生成,其它會使用同一個sessionid),並將該session和用戶信息關聯起來,然後將sessionid返回給瀏覽器,瀏覽器收到sessionid保存到Cookie中,當用戶第二次訪問服務器是就會攜帶Cookie值,服務器獲取到Cookie值,進而獲取到sessionid,根據sessionid獲取關聯的用戶信息。

public User login(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
String username = request.getParameter("username");
String password = request.getParameter("password");
User user = userService.login(username, password);
if (user == null) {
throw new AuthenticationException("用戶名或密碼錯誤");
}
// 返回這次請求關聯的當前會話,如果沒有會話則創建一個新的
// 需要在服務器端記錄該session
HttpSession session = request.getSession();
session.setAttribute("user", user);
// 讓瀏覽器保存sessionid到cookie中
// Cookie cookie = new Cookie("sessionid", session.getId());
// cookie.setPath("/");
// response.addCookie(cookie);
return user;
}
public Object getUserInfo(HttpServletRequest request){
// 從request中獲取Cookie
// 從Cookie中獲取sessionid
// 根據sessionid獲取對應的Session對象
// 從session中獲取關聯的用戶信息
HttpSession session = request.getSession();
Object user = session.getAttribute("user");
return user;
}

session的缺點:

  • Session: 每個用戶經過我們的應用認證之後,我們的應用都要在服務端做一次記錄,以方便用戶下次請求的鑑別,通常而言session都是保存在內存中,而隨著認證用戶的增多,服務端的開銷會明顯增大。
  • 擴展性: 用戶認證之後,服務端做認證記錄,如果認證的記錄被保存在內存中的話,這意味著用戶下次請求還必須要請求在這臺服務器上,這樣才能拿到授權的資源,這樣在分佈式的應用上,相應的限制了負載均衡器的能力。這也意味著限制了應用的擴展能力。即不能滿足單點登陸
  • CSRF: 因為是基於cookie來進行用戶識別的, cookie如果被截獲,用戶就會很容易受到跨站請求偽造的攻擊。

1.2 基於token認證

token原理:

  1. 使用用戶名和密碼請求登錄接口
  2. 登錄接口驗證用戶名和密碼
  3. 登錄接口生成一個uuid作為token,將用戶信息作為值,然後保存到redis緩存中jedis.set(token, user);
  4. 登錄接口返回用戶信息和token
  5. 瀏覽器將token保存到本地
  6. 當請求其它接口時就攜帶token值
  7. 接口根據token去緩存中查,如果找到了就調用接口,如果找不到報token錯誤(一般通過攔截器來實現檢查)
public String auth(String username, String password) throws AuthenticationException {
User user = userService.login(username, password);
if (user == null) {
throw new AuthenticationException("用戶名或密碼錯誤");
}
String token = UUID.randomUUID().toString();
redisClient.set(token, user);
return token;
}
public Object getUserInfo(@RequestHeader("token") String token) throws AuthenticationException {
User user = redisClient.get(token);
if (user == null) {
throw new AuthenticationException("token不可用");
}
return user;
}

session和token的區別:

此種方式原理上和session方式差不多,都是客戶端調用接口時攜帶一個值,服務器通過該值來獲取用戶的信息。

不同的是session是將信息保存到本機內存中,對負載均衡有限制(只能負載到同一臺機器),token是保存到緩存服務器(redis)中,對負載均衡沒有限制,如果使用同一個redis服務器還可以保證單點登錄。session一般用在PC上,token即可用在PC上也可以用在APP上。

1.3 JWT

(建議收藏) | Spring Boot集成JSON Web Token(JWT)

1.3.1 簡介

JSON Web Token(JWT)是為了在網絡應用環境間傳遞聲明而執行的一種基於JSON的開放標準((RFC 7519),它定義了一種緊湊(Compact)且自包含(Self-contained)的方式,用於在各方之間以JSON對象安全傳輸信息。 這些信息可以通過數字簽名進行驗證和信任。 可以使用祕密(使用HMAC算法)或使用RSA的公鑰/私鑰對對JWT進行簽名。JWT的聲明一般被用來在身份提供者和服務提供者間傳遞被認證的用戶身份信息,以便於從資源服務器獲取資源,也可以增加一些額外的其它業務邏輯所必須的聲明信息,該token也可直接被用於認證,也可被加密。是目前最流行的跨域認證解決方案。

  • Compact(緊湊): 由於它們尺寸較小,JWT可以通過URL,POST參數或HTTP標頭內發送。 另外,尺寸越小意味著傳輸速度越快。
  • Self-contained(自包含): 有效載荷(Playload)包含有關用戶的所有必需信息,避免了多次查詢數據庫。

1.3.2 應用場景

  • Authentication(鑑權): 這是使用JWT最常見的情況。 一旦用戶登錄,每個後續請求都將包含JWT,允許用戶訪問該令牌允許的路由,服務和資源。 單點登錄是當今廣泛使用JWT的一項功能,因為它的開銷很小,並且能夠輕鬆地跨不同域使用。
  • 分佈式站點的單點登錄(SSO)
  • Information Exchange(信息交換): JSON Web Tokens是在各方之間安全傳輸信息的好方式。 因為JWT可以簽名:例如使用公鑰/私鑰對,所以可以確定發件人是他們自稱的人。 此外,由於使用標頭和有效載荷計算簽名,因此您還可以驗證內容是否未被篡改。

1.3.3 語法

jwt有3個組成部分,每部分通過點號來分割 header.payload.signature

  • 頭部(header) 是一個 JSON 對象,描述 JWT 的元數據,通常是下面的樣子
  • 載荷(payload) 是一個 JSON 對象,用來存放實際需要傳遞的數據
  • 簽證(signature) 對header和payload使用密鑰進行簽名,防止數據篡改。

① 頭部header

Jwt的頭部是一個JSON,然後使用Base64URL編碼,承載兩部分信息:

  • 聲明類型typ,表示這個令牌(token)的類型(type),JWT令牌統一寫為JWT
  • 聲明加密的算法alg,通常直接使用HMACSHA256,就是HS256了,也可以使用RSA,支持很多算法(HS256、HS384、HS512、RS256、RS384、RS512、ES256、ES384、ES512、PS256、PS384)

var header = Base64URL({ "alg": "HS256", "typ": "JWT"})

Base64URL:Header 和 Payload 串型化的算法是 Base64URL。這個算法跟 Base64 算法基本類似,但有一些小的不同。JWT 作為一個令牌(token),有些場合可能會放到 URL(比如 api.example.com/?token=xxx)。Base64 有三個字符+、/和=,在 URL 裡面有特殊含義,所以要被替換掉:=被省略、+替換成-,/替換成_ 。這就是 Base64URL 算法。

② 載荷payload

payload也是一個JSON字符串,是承載消息具體內容的地方,也需要使用Base64URL編碼,payload中可以包含預定義的7個可用,它們不是強制性的,但推薦使用,也可以添加任意自定義的key

  1. iss(issuer): jwt簽發者
  2. sub(subject): jwt所面向的用戶
  3. aud(audience): 接收jwt的一方, 受眾
  4. exp(expiration time): jwt的過期時間,這個過期時間必須要大於簽發時間
  5. nbf(Not Before): 生效時間,定義在什麼時間之前.
  6. iat(Issued At): jwt的簽發時間
  7. jti(JWT ID): jwt的唯一身份標識,主要用來作為一次性token,從而回避重放攻擊。

// 該token簽發給1234567890,姓名為John Doe(自定義的字段),簽發時間為1516239022

var payload = Base64URL( {"sub": "1234567890", "name": "John Doe", "iat": 1516239022})

注意,JWT中payload是不加密的,只是Base64URL編碼一下,任何人拿到都可以進行解碼,所以不要把敏感信息放到裡面。

③ signature

Signature 部分是對前兩部分的簽名,防止數據篡改。

var header = Base64URL({ "alg": "HS256", "typ": "JWT"});
var payload = Base64URL( {"sub": "1234567890", "name": "John Doe", "iat": 1516239022});
var secret = "私鑰";
var signature = HMACSHA256(header + "." + payload, secret);
var jwt = header + "." + payload + "." + signature;

我們可以使用jwt.io調試器來解碼,驗證和生成JWT:

(建議收藏) | Spring Boot集成JSON Web Token(JWT)

注意:secret是保存在服務器端的,jwt的簽發生成也是在服務器端的,secret就是用來進行jwt的簽發和jwt的驗證,所以,它就是你服務端的私鑰,在任何場景都不應該流露出去。一旦客戶端得知這個secret, 那就意味著客戶端是可以自我簽發jwt了。

1.3.4 jwt的特點

  • 因為json的通用性,所以JWT是可以進行跨語言支持的,像JAVA,JavaScript,NodeJS,PHP等很多語言都可以使用。
  • 因為有了payload部分,所以JWT可以在自身存儲一些其他業務邏輯所必要的非敏感信息。
  • 它不需要在服務端保存會話信息, 所以它易於應用的擴展

JWT 的幾個特點

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

(2)JWT 不加密的情況下,不能將敏感數據(如密碼)寫入 JWT,除非對payload進行加密。保護好secret私鑰,該私鑰非常重要。

(3)JWT 不僅可以用於認證,也可以用於交換信息。有效使用 JWT,可以降低服務器查詢數據庫的次數。

(4)JWT 的最大缺點是,由於服務器不保存 session 狀態,因此無法在使用過程中廢止某個 token,或者更改 token 的權限。也就是說,一旦 JWT 簽發了,在到期之前就會始終有效,除非服務器部署額外的邏輯。

(5)JWT 本身包含了認證信息,一旦洩露,任何人都可以獲得該令牌的所有權限。為了減少盜用,JWT 的有效期應該設置得比較短。對於一些比較重要的權限,使用時應該再次對用戶進行認證。

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

1.3.5 JWT的優點:

  • 體積小,因而傳輸速度更快
  • 多樣化的傳輸方式,可以通過URL傳輸、POST傳輸、請求頭Header傳輸(常用)
  • 簡單方便,服務端拿到jwt後無需再次查詢數據庫校驗token可用性,也無需進行redis緩存校驗
  • 在分佈式系統中,很好地解決了單點登錄問題
  • 很方便的解決了跨域授權問題,因為跨域無法共享cookie

1.3.6 JWT的缺點:

  • 因為JWT是無狀態的,因此服務端無法控制已經生成的Token失效,是不可控的,這一點對於是否使用jwt是需要重點考量的
  • 獲取到jwt也就擁有了登錄權限,因此jwt是不可洩露的,網站最好使用https,防止中間攻擊偷取jwt
  • 在退出登錄 / 修改密碼時怎樣實現JWT Token失效https://segmentfault.com/q/1010000010043871

1.3.7 對於JWT安全性:

JWT被確實存在被竊取的問題,但是如果能得到別人的token,其實也就相當於能竊取別人的密碼,這其實已經不是JWT安全性的問題。網絡是存在多種不安全性的,對於傳統的session登錄的方式,如果別人能竊取登錄後的sessionID,也就能模擬登錄狀態,這和JWT是類似的。為了安全,https加密非常有必要,對於JWT有效時間最好設置短一點。

1.3.8 常見問題

① JWT 安全嗎?

Base64編碼方式是可逆的,也就是透過編碼後發放的Token內容是可以被解析的。一般而言,我們都不建議在有效載荷內放敏感訊息,比如使用者的密碼。

② JWT Payload 內容可以被偽造嗎?

JWT其中的一個組成內容為Signature,可以防止通過Base64可逆方法回推有效載荷內容並將其修改。因為Signature是經由Header跟Payload一起Base64組成的。

③ 如果我的 Cookie 被竊取了,那不就表示第三方可以做 CSRF 攻擊?

是的,Cookie丟失,就表示身份就可以被偽造。故官方建議的使用方式是存放在LocalStorage中,並放在請求頭中發送。

④ 空間及長度問題?

JWT Token通常長度不會太小,特別是Stateless JWT Token,把所有的數據都編在Token裡,很快的就會超過Cookie的大小(4K)或者是URL長度限制。

⑤ Token失效問題?

無狀態JWT令牌(Stateless JWT Token)發放出去之後,不能通過服務器端讓令牌失效,必須等到過期時間過才會失去效用。

假設在這之間Token被攔截,或者有權限管理身份的差異造成授權Scope修改,都不能阻止發出去的Token失效並要求使用者重新請求新的Token。

1.3.9 JWT使用建議

  • Payload中的exp時效不要設定太長。
  • 開啟Only Http預防XSS攻擊。
  • 如果擔心重播攻擊(replay attacks )可以增加jti(JWT ID),exp(有效時間) Claim。
  • 在你的應用程序應用層中增加黑名單機制,必要的時候可以進行Block做阻擋(這是針對掉令牌被第三方使用竊取的手動防禦)。

二:io.jsonwebtoken.jjwt

jwt使用流程

"

本號主要用於分享企業中常用的技術,更加側重於實用,歡迎關注,便於瀏覽其它更多實用的歷史文章。

一:認證

在瞭解JWT之前先來回顧一下傳統session認證和基於token認證。

1.1 傳統session認證

http協議是一種無狀態協議,即瀏覽器發送請求到服務器,服務器是不知道這個請求是哪個用戶發來的。為了讓服務器知道請求是哪個用戶發來的,需要讓用戶提供用戶名和密碼來進行認證。當瀏覽器第一次訪問服務器(假設是登錄接口),服務器驗證用戶名和密碼之後,服務器會生成一個sessionid(只有第一次會生成,其它會使用同一個sessionid),並將該session和用戶信息關聯起來,然後將sessionid返回給瀏覽器,瀏覽器收到sessionid保存到Cookie中,當用戶第二次訪問服務器是就會攜帶Cookie值,服務器獲取到Cookie值,進而獲取到sessionid,根據sessionid獲取關聯的用戶信息。

public User login(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
String username = request.getParameter("username");
String password = request.getParameter("password");
User user = userService.login(username, password);
if (user == null) {
throw new AuthenticationException("用戶名或密碼錯誤");
}
// 返回這次請求關聯的當前會話,如果沒有會話則創建一個新的
// 需要在服務器端記錄該session
HttpSession session = request.getSession();
session.setAttribute("user", user);
// 讓瀏覽器保存sessionid到cookie中
// Cookie cookie = new Cookie("sessionid", session.getId());
// cookie.setPath("/");
// response.addCookie(cookie);
return user;
}
public Object getUserInfo(HttpServletRequest request){
// 從request中獲取Cookie
// 從Cookie中獲取sessionid
// 根據sessionid獲取對應的Session對象
// 從session中獲取關聯的用戶信息
HttpSession session = request.getSession();
Object user = session.getAttribute("user");
return user;
}

session的缺點:

  • Session: 每個用戶經過我們的應用認證之後,我們的應用都要在服務端做一次記錄,以方便用戶下次請求的鑑別,通常而言session都是保存在內存中,而隨著認證用戶的增多,服務端的開銷會明顯增大。
  • 擴展性: 用戶認證之後,服務端做認證記錄,如果認證的記錄被保存在內存中的話,這意味著用戶下次請求還必須要請求在這臺服務器上,這樣才能拿到授權的資源,這樣在分佈式的應用上,相應的限制了負載均衡器的能力。這也意味著限制了應用的擴展能力。即不能滿足單點登陸
  • CSRF: 因為是基於cookie來進行用戶識別的, cookie如果被截獲,用戶就會很容易受到跨站請求偽造的攻擊。

1.2 基於token認證

token原理:

  1. 使用用戶名和密碼請求登錄接口
  2. 登錄接口驗證用戶名和密碼
  3. 登錄接口生成一個uuid作為token,將用戶信息作為值,然後保存到redis緩存中jedis.set(token, user);
  4. 登錄接口返回用戶信息和token
  5. 瀏覽器將token保存到本地
  6. 當請求其它接口時就攜帶token值
  7. 接口根據token去緩存中查,如果找到了就調用接口,如果找不到報token錯誤(一般通過攔截器來實現檢查)
public String auth(String username, String password) throws AuthenticationException {
User user = userService.login(username, password);
if (user == null) {
throw new AuthenticationException("用戶名或密碼錯誤");
}
String token = UUID.randomUUID().toString();
redisClient.set(token, user);
return token;
}
public Object getUserInfo(@RequestHeader("token") String token) throws AuthenticationException {
User user = redisClient.get(token);
if (user == null) {
throw new AuthenticationException("token不可用");
}
return user;
}

session和token的區別:

此種方式原理上和session方式差不多,都是客戶端調用接口時攜帶一個值,服務器通過該值來獲取用戶的信息。

不同的是session是將信息保存到本機內存中,對負載均衡有限制(只能負載到同一臺機器),token是保存到緩存服務器(redis)中,對負載均衡沒有限制,如果使用同一個redis服務器還可以保證單點登錄。session一般用在PC上,token即可用在PC上也可以用在APP上。

1.3 JWT

(建議收藏) | Spring Boot集成JSON Web Token(JWT)

1.3.1 簡介

JSON Web Token(JWT)是為了在網絡應用環境間傳遞聲明而執行的一種基於JSON的開放標準((RFC 7519),它定義了一種緊湊(Compact)且自包含(Self-contained)的方式,用於在各方之間以JSON對象安全傳輸信息。 這些信息可以通過數字簽名進行驗證和信任。 可以使用祕密(使用HMAC算法)或使用RSA的公鑰/私鑰對對JWT進行簽名。JWT的聲明一般被用來在身份提供者和服務提供者間傳遞被認證的用戶身份信息,以便於從資源服務器獲取資源,也可以增加一些額外的其它業務邏輯所必須的聲明信息,該token也可直接被用於認證,也可被加密。是目前最流行的跨域認證解決方案。

  • Compact(緊湊): 由於它們尺寸較小,JWT可以通過URL,POST參數或HTTP標頭內發送。 另外,尺寸越小意味著傳輸速度越快。
  • Self-contained(自包含): 有效載荷(Playload)包含有關用戶的所有必需信息,避免了多次查詢數據庫。

1.3.2 應用場景

  • Authentication(鑑權): 這是使用JWT最常見的情況。 一旦用戶登錄,每個後續請求都將包含JWT,允許用戶訪問該令牌允許的路由,服務和資源。 單點登錄是當今廣泛使用JWT的一項功能,因為它的開銷很小,並且能夠輕鬆地跨不同域使用。
  • 分佈式站點的單點登錄(SSO)
  • Information Exchange(信息交換): JSON Web Tokens是在各方之間安全傳輸信息的好方式。 因為JWT可以簽名:例如使用公鑰/私鑰對,所以可以確定發件人是他們自稱的人。 此外,由於使用標頭和有效載荷計算簽名,因此您還可以驗證內容是否未被篡改。

1.3.3 語法

jwt有3個組成部分,每部分通過點號來分割 header.payload.signature

  • 頭部(header) 是一個 JSON 對象,描述 JWT 的元數據,通常是下面的樣子
  • 載荷(payload) 是一個 JSON 對象,用來存放實際需要傳遞的數據
  • 簽證(signature) 對header和payload使用密鑰進行簽名,防止數據篡改。

① 頭部header

Jwt的頭部是一個JSON,然後使用Base64URL編碼,承載兩部分信息:

  • 聲明類型typ,表示這個令牌(token)的類型(type),JWT令牌統一寫為JWT
  • 聲明加密的算法alg,通常直接使用HMACSHA256,就是HS256了,也可以使用RSA,支持很多算法(HS256、HS384、HS512、RS256、RS384、RS512、ES256、ES384、ES512、PS256、PS384)

var header = Base64URL({ "alg": "HS256", "typ": "JWT"})

Base64URL:Header 和 Payload 串型化的算法是 Base64URL。這個算法跟 Base64 算法基本類似,但有一些小的不同。JWT 作為一個令牌(token),有些場合可能會放到 URL(比如 api.example.com/?token=xxx)。Base64 有三個字符+、/和=,在 URL 裡面有特殊含義,所以要被替換掉:=被省略、+替換成-,/替換成_ 。這就是 Base64URL 算法。

② 載荷payload

payload也是一個JSON字符串,是承載消息具體內容的地方,也需要使用Base64URL編碼,payload中可以包含預定義的7個可用,它們不是強制性的,但推薦使用,也可以添加任意自定義的key

  1. iss(issuer): jwt簽發者
  2. sub(subject): jwt所面向的用戶
  3. aud(audience): 接收jwt的一方, 受眾
  4. exp(expiration time): jwt的過期時間,這個過期時間必須要大於簽發時間
  5. nbf(Not Before): 生效時間,定義在什麼時間之前.
  6. iat(Issued At): jwt的簽發時間
  7. jti(JWT ID): jwt的唯一身份標識,主要用來作為一次性token,從而回避重放攻擊。

// 該token簽發給1234567890,姓名為John Doe(自定義的字段),簽發時間為1516239022

var payload = Base64URL( {"sub": "1234567890", "name": "John Doe", "iat": 1516239022})

注意,JWT中payload是不加密的,只是Base64URL編碼一下,任何人拿到都可以進行解碼,所以不要把敏感信息放到裡面。

③ signature

Signature 部分是對前兩部分的簽名,防止數據篡改。

var header = Base64URL({ "alg": "HS256", "typ": "JWT"});
var payload = Base64URL( {"sub": "1234567890", "name": "John Doe", "iat": 1516239022});
var secret = "私鑰";
var signature = HMACSHA256(header + "." + payload, secret);
var jwt = header + "." + payload + "." + signature;

我們可以使用jwt.io調試器來解碼,驗證和生成JWT:

(建議收藏) | Spring Boot集成JSON Web Token(JWT)

注意:secret是保存在服務器端的,jwt的簽發生成也是在服務器端的,secret就是用來進行jwt的簽發和jwt的驗證,所以,它就是你服務端的私鑰,在任何場景都不應該流露出去。一旦客戶端得知這個secret, 那就意味著客戶端是可以自我簽發jwt了。

1.3.4 jwt的特點

  • 因為json的通用性,所以JWT是可以進行跨語言支持的,像JAVA,JavaScript,NodeJS,PHP等很多語言都可以使用。
  • 因為有了payload部分,所以JWT可以在自身存儲一些其他業務邏輯所必要的非敏感信息。
  • 它不需要在服務端保存會話信息, 所以它易於應用的擴展

JWT 的幾個特點

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

(2)JWT 不加密的情況下,不能將敏感數據(如密碼)寫入 JWT,除非對payload進行加密。保護好secret私鑰,該私鑰非常重要。

(3)JWT 不僅可以用於認證,也可以用於交換信息。有效使用 JWT,可以降低服務器查詢數據庫的次數。

(4)JWT 的最大缺點是,由於服務器不保存 session 狀態,因此無法在使用過程中廢止某個 token,或者更改 token 的權限。也就是說,一旦 JWT 簽發了,在到期之前就會始終有效,除非服務器部署額外的邏輯。

(5)JWT 本身包含了認證信息,一旦洩露,任何人都可以獲得該令牌的所有權限。為了減少盜用,JWT 的有效期應該設置得比較短。對於一些比較重要的權限,使用時應該再次對用戶進行認證。

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

1.3.5 JWT的優點:

  • 體積小,因而傳輸速度更快
  • 多樣化的傳輸方式,可以通過URL傳輸、POST傳輸、請求頭Header傳輸(常用)
  • 簡單方便,服務端拿到jwt後無需再次查詢數據庫校驗token可用性,也無需進行redis緩存校驗
  • 在分佈式系統中,很好地解決了單點登錄問題
  • 很方便的解決了跨域授權問題,因為跨域無法共享cookie

1.3.6 JWT的缺點:

  • 因為JWT是無狀態的,因此服務端無法控制已經生成的Token失效,是不可控的,這一點對於是否使用jwt是需要重點考量的
  • 獲取到jwt也就擁有了登錄權限,因此jwt是不可洩露的,網站最好使用https,防止中間攻擊偷取jwt
  • 在退出登錄 / 修改密碼時怎樣實現JWT Token失效https://segmentfault.com/q/1010000010043871

1.3.7 對於JWT安全性:

JWT被確實存在被竊取的問題,但是如果能得到別人的token,其實也就相當於能竊取別人的密碼,這其實已經不是JWT安全性的問題。網絡是存在多種不安全性的,對於傳統的session登錄的方式,如果別人能竊取登錄後的sessionID,也就能模擬登錄狀態,這和JWT是類似的。為了安全,https加密非常有必要,對於JWT有效時間最好設置短一點。

1.3.8 常見問題

① JWT 安全嗎?

Base64編碼方式是可逆的,也就是透過編碼後發放的Token內容是可以被解析的。一般而言,我們都不建議在有效載荷內放敏感訊息,比如使用者的密碼。

② JWT Payload 內容可以被偽造嗎?

JWT其中的一個組成內容為Signature,可以防止通過Base64可逆方法回推有效載荷內容並將其修改。因為Signature是經由Header跟Payload一起Base64組成的。

③ 如果我的 Cookie 被竊取了,那不就表示第三方可以做 CSRF 攻擊?

是的,Cookie丟失,就表示身份就可以被偽造。故官方建議的使用方式是存放在LocalStorage中,並放在請求頭中發送。

④ 空間及長度問題?

JWT Token通常長度不會太小,特別是Stateless JWT Token,把所有的數據都編在Token裡,很快的就會超過Cookie的大小(4K)或者是URL長度限制。

⑤ Token失效問題?

無狀態JWT令牌(Stateless JWT Token)發放出去之後,不能通過服務器端讓令牌失效,必須等到過期時間過才會失去效用。

假設在這之間Token被攔截,或者有權限管理身份的差異造成授權Scope修改,都不能阻止發出去的Token失效並要求使用者重新請求新的Token。

1.3.9 JWT使用建議

  • Payload中的exp時效不要設定太長。
  • 開啟Only Http預防XSS攻擊。
  • 如果擔心重播攻擊(replay attacks )可以增加jti(JWT ID),exp(有效時間) Claim。
  • 在你的應用程序應用層中增加黑名單機制,必要的時候可以進行Block做阻擋(這是針對掉令牌被第三方使用竊取的手動防禦)。

二:io.jsonwebtoken.jjwt

jwt使用流程

(建議收藏) | Spring Boot集成JSON Web Token(JWT)

一般是在請求頭裡加入Authorization,並加上Bearer標註:

// Authorization: Bearer <token>
getToken('api/user/1', {
headers: {
'Authorization': 'Bearer ' + token
}
})

io.jsonwebtoken是最常用的工具包。

<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>

application.properties

jwt.secret=JO6HN3NGIU25G2FIG8V7VD6CK9B6T2Z5
jwt.expire=60000
wtToken
@Configuration
public class JwtToken {
private static Logger logger = LoggerFactory.getLogger(JwtToken.class);
/** 祕鑰 */
@Value("${jwt.secret}")
private String secret;
/** 過期時間(秒) */
@Value("${jwt.expire}")
private long expire;
/**
* 生成jwt token
*/
public String generateToken(Long userId) {
Date nowDate = new Date();
Date expireDate = new Date(nowDate.getTime() + expire * 1000);
return Jwts.builder()
.setHeaderParam("typ", "JWT")
.setSubject(userId + "")
.setIssuedAt(nowDate)
.setExpiration(expireDate)
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
public Claims getClaimByToken(String token) {
if (StringUtils.isEmpty(token)) {
return null;
}
String[] header = token.split("Bearer");
token = header[1];
try {
return Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
}catch (Exception e){
logger.debug("validate is token error ", e);
return null;
}
}
/**
* token是否過期
* @return true:過期
*/
public static boolean isTokenExpired(Date expiration) {
return expiration.before(new Date());
}
// Getter && Setter
}
JwtController
@RestController
public class JwtController {
@Autowired
private JwtToken jwtToken;
@PostMapping("/login")
public String login(User user) {
// 1. 驗證用戶名和密碼
// 2. 驗證成功生成token
Long userId = 666L;
String token = jwtToken.generateToken(userId);
return token;
}
@GetMapping("/getUserInfo")
public String getUserInfo(@RequestHeader("Authorization") String authHeader) throws AuthenticationException {
// 黑名單token
List<String> blacklistToken = Arrays.asList("禁止訪問的token");
Claims claims = jwtToken.getClaimByToken(authHeader);
if (claims == null || JwtToken.isTokenExpired(claims.getExpiration()) || blacklistToken.contains(authHeader)) {
throw new AuthenticationException("token 不可用");
}
String userId = claims.getSubject();
// 根據用戶id獲取接口數據返回接口
return userId;
}
}
"

本號主要用於分享企業中常用的技術,更加側重於實用,歡迎關注,便於瀏覽其它更多實用的歷史文章。

一:認證

在瞭解JWT之前先來回顧一下傳統session認證和基於token認證。

1.1 傳統session認證

http協議是一種無狀態協議,即瀏覽器發送請求到服務器,服務器是不知道這個請求是哪個用戶發來的。為了讓服務器知道請求是哪個用戶發來的,需要讓用戶提供用戶名和密碼來進行認證。當瀏覽器第一次訪問服務器(假設是登錄接口),服務器驗證用戶名和密碼之後,服務器會生成一個sessionid(只有第一次會生成,其它會使用同一個sessionid),並將該session和用戶信息關聯起來,然後將sessionid返回給瀏覽器,瀏覽器收到sessionid保存到Cookie中,當用戶第二次訪問服務器是就會攜帶Cookie值,服務器獲取到Cookie值,進而獲取到sessionid,根據sessionid獲取關聯的用戶信息。

public User login(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
String username = request.getParameter("username");
String password = request.getParameter("password");
User user = userService.login(username, password);
if (user == null) {
throw new AuthenticationException("用戶名或密碼錯誤");
}
// 返回這次請求關聯的當前會話,如果沒有會話則創建一個新的
// 需要在服務器端記錄該session
HttpSession session = request.getSession();
session.setAttribute("user", user);
// 讓瀏覽器保存sessionid到cookie中
// Cookie cookie = new Cookie("sessionid", session.getId());
// cookie.setPath("/");
// response.addCookie(cookie);
return user;
}
public Object getUserInfo(HttpServletRequest request){
// 從request中獲取Cookie
// 從Cookie中獲取sessionid
// 根據sessionid獲取對應的Session對象
// 從session中獲取關聯的用戶信息
HttpSession session = request.getSession();
Object user = session.getAttribute("user");
return user;
}

session的缺點:

  • Session: 每個用戶經過我們的應用認證之後,我們的應用都要在服務端做一次記錄,以方便用戶下次請求的鑑別,通常而言session都是保存在內存中,而隨著認證用戶的增多,服務端的開銷會明顯增大。
  • 擴展性: 用戶認證之後,服務端做認證記錄,如果認證的記錄被保存在內存中的話,這意味著用戶下次請求還必須要請求在這臺服務器上,這樣才能拿到授權的資源,這樣在分佈式的應用上,相應的限制了負載均衡器的能力。這也意味著限制了應用的擴展能力。即不能滿足單點登陸
  • CSRF: 因為是基於cookie來進行用戶識別的, cookie如果被截獲,用戶就會很容易受到跨站請求偽造的攻擊。

1.2 基於token認證

token原理:

  1. 使用用戶名和密碼請求登錄接口
  2. 登錄接口驗證用戶名和密碼
  3. 登錄接口生成一個uuid作為token,將用戶信息作為值,然後保存到redis緩存中jedis.set(token, user);
  4. 登錄接口返回用戶信息和token
  5. 瀏覽器將token保存到本地
  6. 當請求其它接口時就攜帶token值
  7. 接口根據token去緩存中查,如果找到了就調用接口,如果找不到報token錯誤(一般通過攔截器來實現檢查)
public String auth(String username, String password) throws AuthenticationException {
User user = userService.login(username, password);
if (user == null) {
throw new AuthenticationException("用戶名或密碼錯誤");
}
String token = UUID.randomUUID().toString();
redisClient.set(token, user);
return token;
}
public Object getUserInfo(@RequestHeader("token") String token) throws AuthenticationException {
User user = redisClient.get(token);
if (user == null) {
throw new AuthenticationException("token不可用");
}
return user;
}

session和token的區別:

此種方式原理上和session方式差不多,都是客戶端調用接口時攜帶一個值,服務器通過該值來獲取用戶的信息。

不同的是session是將信息保存到本機內存中,對負載均衡有限制(只能負載到同一臺機器),token是保存到緩存服務器(redis)中,對負載均衡沒有限制,如果使用同一個redis服務器還可以保證單點登錄。session一般用在PC上,token即可用在PC上也可以用在APP上。

1.3 JWT

(建議收藏) | Spring Boot集成JSON Web Token(JWT)

1.3.1 簡介

JSON Web Token(JWT)是為了在網絡應用環境間傳遞聲明而執行的一種基於JSON的開放標準((RFC 7519),它定義了一種緊湊(Compact)且自包含(Self-contained)的方式,用於在各方之間以JSON對象安全傳輸信息。 這些信息可以通過數字簽名進行驗證和信任。 可以使用祕密(使用HMAC算法)或使用RSA的公鑰/私鑰對對JWT進行簽名。JWT的聲明一般被用來在身份提供者和服務提供者間傳遞被認證的用戶身份信息,以便於從資源服務器獲取資源,也可以增加一些額外的其它業務邏輯所必須的聲明信息,該token也可直接被用於認證,也可被加密。是目前最流行的跨域認證解決方案。

  • Compact(緊湊): 由於它們尺寸較小,JWT可以通過URL,POST參數或HTTP標頭內發送。 另外,尺寸越小意味著傳輸速度越快。
  • Self-contained(自包含): 有效載荷(Playload)包含有關用戶的所有必需信息,避免了多次查詢數據庫。

1.3.2 應用場景

  • Authentication(鑑權): 這是使用JWT最常見的情況。 一旦用戶登錄,每個後續請求都將包含JWT,允許用戶訪問該令牌允許的路由,服務和資源。 單點登錄是當今廣泛使用JWT的一項功能,因為它的開銷很小,並且能夠輕鬆地跨不同域使用。
  • 分佈式站點的單點登錄(SSO)
  • Information Exchange(信息交換): JSON Web Tokens是在各方之間安全傳輸信息的好方式。 因為JWT可以簽名:例如使用公鑰/私鑰對,所以可以確定發件人是他們自稱的人。 此外,由於使用標頭和有效載荷計算簽名,因此您還可以驗證內容是否未被篡改。

1.3.3 語法

jwt有3個組成部分,每部分通過點號來分割 header.payload.signature

  • 頭部(header) 是一個 JSON 對象,描述 JWT 的元數據,通常是下面的樣子
  • 載荷(payload) 是一個 JSON 對象,用來存放實際需要傳遞的數據
  • 簽證(signature) 對header和payload使用密鑰進行簽名,防止數據篡改。

① 頭部header

Jwt的頭部是一個JSON,然後使用Base64URL編碼,承載兩部分信息:

  • 聲明類型typ,表示這個令牌(token)的類型(type),JWT令牌統一寫為JWT
  • 聲明加密的算法alg,通常直接使用HMACSHA256,就是HS256了,也可以使用RSA,支持很多算法(HS256、HS384、HS512、RS256、RS384、RS512、ES256、ES384、ES512、PS256、PS384)

var header = Base64URL({ "alg": "HS256", "typ": "JWT"})

Base64URL:Header 和 Payload 串型化的算法是 Base64URL。這個算法跟 Base64 算法基本類似,但有一些小的不同。JWT 作為一個令牌(token),有些場合可能會放到 URL(比如 api.example.com/?token=xxx)。Base64 有三個字符+、/和=,在 URL 裡面有特殊含義,所以要被替換掉:=被省略、+替換成-,/替換成_ 。這就是 Base64URL 算法。

② 載荷payload

payload也是一個JSON字符串,是承載消息具體內容的地方,也需要使用Base64URL編碼,payload中可以包含預定義的7個可用,它們不是強制性的,但推薦使用,也可以添加任意自定義的key

  1. iss(issuer): jwt簽發者
  2. sub(subject): jwt所面向的用戶
  3. aud(audience): 接收jwt的一方, 受眾
  4. exp(expiration time): jwt的過期時間,這個過期時間必須要大於簽發時間
  5. nbf(Not Before): 生效時間,定義在什麼時間之前.
  6. iat(Issued At): jwt的簽發時間
  7. jti(JWT ID): jwt的唯一身份標識,主要用來作為一次性token,從而回避重放攻擊。

// 該token簽發給1234567890,姓名為John Doe(自定義的字段),簽發時間為1516239022

var payload = Base64URL( {"sub": "1234567890", "name": "John Doe", "iat": 1516239022})

注意,JWT中payload是不加密的,只是Base64URL編碼一下,任何人拿到都可以進行解碼,所以不要把敏感信息放到裡面。

③ signature

Signature 部分是對前兩部分的簽名,防止數據篡改。

var header = Base64URL({ "alg": "HS256", "typ": "JWT"});
var payload = Base64URL( {"sub": "1234567890", "name": "John Doe", "iat": 1516239022});
var secret = "私鑰";
var signature = HMACSHA256(header + "." + payload, secret);
var jwt = header + "." + payload + "." + signature;

我們可以使用jwt.io調試器來解碼,驗證和生成JWT:

(建議收藏) | Spring Boot集成JSON Web Token(JWT)

注意:secret是保存在服務器端的,jwt的簽發生成也是在服務器端的,secret就是用來進行jwt的簽發和jwt的驗證,所以,它就是你服務端的私鑰,在任何場景都不應該流露出去。一旦客戶端得知這個secret, 那就意味著客戶端是可以自我簽發jwt了。

1.3.4 jwt的特點

  • 因為json的通用性,所以JWT是可以進行跨語言支持的,像JAVA,JavaScript,NodeJS,PHP等很多語言都可以使用。
  • 因為有了payload部分,所以JWT可以在自身存儲一些其他業務邏輯所必要的非敏感信息。
  • 它不需要在服務端保存會話信息, 所以它易於應用的擴展

JWT 的幾個特點

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

(2)JWT 不加密的情況下,不能將敏感數據(如密碼)寫入 JWT,除非對payload進行加密。保護好secret私鑰,該私鑰非常重要。

(3)JWT 不僅可以用於認證,也可以用於交換信息。有效使用 JWT,可以降低服務器查詢數據庫的次數。

(4)JWT 的最大缺點是,由於服務器不保存 session 狀態,因此無法在使用過程中廢止某個 token,或者更改 token 的權限。也就是說,一旦 JWT 簽發了,在到期之前就會始終有效,除非服務器部署額外的邏輯。

(5)JWT 本身包含了認證信息,一旦洩露,任何人都可以獲得該令牌的所有權限。為了減少盜用,JWT 的有效期應該設置得比較短。對於一些比較重要的權限,使用時應該再次對用戶進行認證。

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

1.3.5 JWT的優點:

  • 體積小,因而傳輸速度更快
  • 多樣化的傳輸方式,可以通過URL傳輸、POST傳輸、請求頭Header傳輸(常用)
  • 簡單方便,服務端拿到jwt後無需再次查詢數據庫校驗token可用性,也無需進行redis緩存校驗
  • 在分佈式系統中,很好地解決了單點登錄問題
  • 很方便的解決了跨域授權問題,因為跨域無法共享cookie

1.3.6 JWT的缺點:

  • 因為JWT是無狀態的,因此服務端無法控制已經生成的Token失效,是不可控的,這一點對於是否使用jwt是需要重點考量的
  • 獲取到jwt也就擁有了登錄權限,因此jwt是不可洩露的,網站最好使用https,防止中間攻擊偷取jwt
  • 在退出登錄 / 修改密碼時怎樣實現JWT Token失效https://segmentfault.com/q/1010000010043871

1.3.7 對於JWT安全性:

JWT被確實存在被竊取的問題,但是如果能得到別人的token,其實也就相當於能竊取別人的密碼,這其實已經不是JWT安全性的問題。網絡是存在多種不安全性的,對於傳統的session登錄的方式,如果別人能竊取登錄後的sessionID,也就能模擬登錄狀態,這和JWT是類似的。為了安全,https加密非常有必要,對於JWT有效時間最好設置短一點。

1.3.8 常見問題

① JWT 安全嗎?

Base64編碼方式是可逆的,也就是透過編碼後發放的Token內容是可以被解析的。一般而言,我們都不建議在有效載荷內放敏感訊息,比如使用者的密碼。

② JWT Payload 內容可以被偽造嗎?

JWT其中的一個組成內容為Signature,可以防止通過Base64可逆方法回推有效載荷內容並將其修改。因為Signature是經由Header跟Payload一起Base64組成的。

③ 如果我的 Cookie 被竊取了,那不就表示第三方可以做 CSRF 攻擊?

是的,Cookie丟失,就表示身份就可以被偽造。故官方建議的使用方式是存放在LocalStorage中,並放在請求頭中發送。

④ 空間及長度問題?

JWT Token通常長度不會太小,特別是Stateless JWT Token,把所有的數據都編在Token裡,很快的就會超過Cookie的大小(4K)或者是URL長度限制。

⑤ Token失效問題?

無狀態JWT令牌(Stateless JWT Token)發放出去之後,不能通過服務器端讓令牌失效,必須等到過期時間過才會失去效用。

假設在這之間Token被攔截,或者有權限管理身份的差異造成授權Scope修改,都不能阻止發出去的Token失效並要求使用者重新請求新的Token。

1.3.9 JWT使用建議

  • Payload中的exp時效不要設定太長。
  • 開啟Only Http預防XSS攻擊。
  • 如果擔心重播攻擊(replay attacks )可以增加jti(JWT ID),exp(有效時間) Claim。
  • 在你的應用程序應用層中增加黑名單機制,必要的時候可以進行Block做阻擋(這是針對掉令牌被第三方使用竊取的手動防禦)。

二:io.jsonwebtoken.jjwt

jwt使用流程

(建議收藏) | Spring Boot集成JSON Web Token(JWT)

一般是在請求頭裡加入Authorization,並加上Bearer標註:

// Authorization: Bearer <token>
getToken('api/user/1', {
headers: {
'Authorization': 'Bearer ' + token
}
})

io.jsonwebtoken是最常用的工具包。

<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>

application.properties

jwt.secret=JO6HN3NGIU25G2FIG8V7VD6CK9B6T2Z5
jwt.expire=60000
wtToken
@Configuration
public class JwtToken {
private static Logger logger = LoggerFactory.getLogger(JwtToken.class);
/** 祕鑰 */
@Value("${jwt.secret}")
private String secret;
/** 過期時間(秒) */
@Value("${jwt.expire}")
private long expire;
/**
* 生成jwt token
*/
public String generateToken(Long userId) {
Date nowDate = new Date();
Date expireDate = new Date(nowDate.getTime() + expire * 1000);
return Jwts.builder()
.setHeaderParam("typ", "JWT")
.setSubject(userId + "")
.setIssuedAt(nowDate)
.setExpiration(expireDate)
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
public Claims getClaimByToken(String token) {
if (StringUtils.isEmpty(token)) {
return null;
}
String[] header = token.split("Bearer");
token = header[1];
try {
return Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
}catch (Exception e){
logger.debug("validate is token error ", e);
return null;
}
}
/**
* token是否過期
* @return true:過期
*/
public static boolean isTokenExpired(Date expiration) {
return expiration.before(new Date());
}
// Getter && Setter
}
JwtController
@RestController
public class JwtController {
@Autowired
private JwtToken jwtToken;
@PostMapping("/login")
public String login(User user) {
// 1. 驗證用戶名和密碼
// 2. 驗證成功生成token
Long userId = 666L;
String token = jwtToken.generateToken(userId);
return token;
}
@GetMapping("/getUserInfo")
public String getUserInfo(@RequestHeader("Authorization") String authHeader) throws AuthenticationException {
// 黑名單token
List<String> blacklistToken = Arrays.asList("禁止訪問的token");
Claims claims = jwtToken.getClaimByToken(authHeader);
if (claims == null || JwtToken.isTokenExpired(claims.getExpiration()) || blacklistToken.contains(authHeader)) {
throw new AuthenticationException("token 不可用");
}
String userId = claims.getSubject();
// 根據用戶id獲取接口數據返回接口
return userId;
}
}
(建議收藏) | Spring Boot集成JSON Web Token(JWT)

"

本號主要用於分享企業中常用的技術,更加側重於實用,歡迎關注,便於瀏覽其它更多實用的歷史文章。

一:認證

在瞭解JWT之前先來回顧一下傳統session認證和基於token認證。

1.1 傳統session認證

http協議是一種無狀態協議,即瀏覽器發送請求到服務器,服務器是不知道這個請求是哪個用戶發來的。為了讓服務器知道請求是哪個用戶發來的,需要讓用戶提供用戶名和密碼來進行認證。當瀏覽器第一次訪問服務器(假設是登錄接口),服務器驗證用戶名和密碼之後,服務器會生成一個sessionid(只有第一次會生成,其它會使用同一個sessionid),並將該session和用戶信息關聯起來,然後將sessionid返回給瀏覽器,瀏覽器收到sessionid保存到Cookie中,當用戶第二次訪問服務器是就會攜帶Cookie值,服務器獲取到Cookie值,進而獲取到sessionid,根據sessionid獲取關聯的用戶信息。

public User login(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
String username = request.getParameter("username");
String password = request.getParameter("password");
User user = userService.login(username, password);
if (user == null) {
throw new AuthenticationException("用戶名或密碼錯誤");
}
// 返回這次請求關聯的當前會話,如果沒有會話則創建一個新的
// 需要在服務器端記錄該session
HttpSession session = request.getSession();
session.setAttribute("user", user);
// 讓瀏覽器保存sessionid到cookie中
// Cookie cookie = new Cookie("sessionid", session.getId());
// cookie.setPath("/");
// response.addCookie(cookie);
return user;
}
public Object getUserInfo(HttpServletRequest request){
// 從request中獲取Cookie
// 從Cookie中獲取sessionid
// 根據sessionid獲取對應的Session對象
// 從session中獲取關聯的用戶信息
HttpSession session = request.getSession();
Object user = session.getAttribute("user");
return user;
}

session的缺點:

  • Session: 每個用戶經過我們的應用認證之後,我們的應用都要在服務端做一次記錄,以方便用戶下次請求的鑑別,通常而言session都是保存在內存中,而隨著認證用戶的增多,服務端的開銷會明顯增大。
  • 擴展性: 用戶認證之後,服務端做認證記錄,如果認證的記錄被保存在內存中的話,這意味著用戶下次請求還必須要請求在這臺服務器上,這樣才能拿到授權的資源,這樣在分佈式的應用上,相應的限制了負載均衡器的能力。這也意味著限制了應用的擴展能力。即不能滿足單點登陸
  • CSRF: 因為是基於cookie來進行用戶識別的, cookie如果被截獲,用戶就會很容易受到跨站請求偽造的攻擊。

1.2 基於token認證

token原理:

  1. 使用用戶名和密碼請求登錄接口
  2. 登錄接口驗證用戶名和密碼
  3. 登錄接口生成一個uuid作為token,將用戶信息作為值,然後保存到redis緩存中jedis.set(token, user);
  4. 登錄接口返回用戶信息和token
  5. 瀏覽器將token保存到本地
  6. 當請求其它接口時就攜帶token值
  7. 接口根據token去緩存中查,如果找到了就調用接口,如果找不到報token錯誤(一般通過攔截器來實現檢查)
public String auth(String username, String password) throws AuthenticationException {
User user = userService.login(username, password);
if (user == null) {
throw new AuthenticationException("用戶名或密碼錯誤");
}
String token = UUID.randomUUID().toString();
redisClient.set(token, user);
return token;
}
public Object getUserInfo(@RequestHeader("token") String token) throws AuthenticationException {
User user = redisClient.get(token);
if (user == null) {
throw new AuthenticationException("token不可用");
}
return user;
}

session和token的區別:

此種方式原理上和session方式差不多,都是客戶端調用接口時攜帶一個值,服務器通過該值來獲取用戶的信息。

不同的是session是將信息保存到本機內存中,對負載均衡有限制(只能負載到同一臺機器),token是保存到緩存服務器(redis)中,對負載均衡沒有限制,如果使用同一個redis服務器還可以保證單點登錄。session一般用在PC上,token即可用在PC上也可以用在APP上。

1.3 JWT

(建議收藏) | Spring Boot集成JSON Web Token(JWT)

1.3.1 簡介

JSON Web Token(JWT)是為了在網絡應用環境間傳遞聲明而執行的一種基於JSON的開放標準((RFC 7519),它定義了一種緊湊(Compact)且自包含(Self-contained)的方式,用於在各方之間以JSON對象安全傳輸信息。 這些信息可以通過數字簽名進行驗證和信任。 可以使用祕密(使用HMAC算法)或使用RSA的公鑰/私鑰對對JWT進行簽名。JWT的聲明一般被用來在身份提供者和服務提供者間傳遞被認證的用戶身份信息,以便於從資源服務器獲取資源,也可以增加一些額外的其它業務邏輯所必須的聲明信息,該token也可直接被用於認證,也可被加密。是目前最流行的跨域認證解決方案。

  • Compact(緊湊): 由於它們尺寸較小,JWT可以通過URL,POST參數或HTTP標頭內發送。 另外,尺寸越小意味著傳輸速度越快。
  • Self-contained(自包含): 有效載荷(Playload)包含有關用戶的所有必需信息,避免了多次查詢數據庫。

1.3.2 應用場景

  • Authentication(鑑權): 這是使用JWT最常見的情況。 一旦用戶登錄,每個後續請求都將包含JWT,允許用戶訪問該令牌允許的路由,服務和資源。 單點登錄是當今廣泛使用JWT的一項功能,因為它的開銷很小,並且能夠輕鬆地跨不同域使用。
  • 分佈式站點的單點登錄(SSO)
  • Information Exchange(信息交換): JSON Web Tokens是在各方之間安全傳輸信息的好方式。 因為JWT可以簽名:例如使用公鑰/私鑰對,所以可以確定發件人是他們自稱的人。 此外,由於使用標頭和有效載荷計算簽名,因此您還可以驗證內容是否未被篡改。

1.3.3 語法

jwt有3個組成部分,每部分通過點號來分割 header.payload.signature

  • 頭部(header) 是一個 JSON 對象,描述 JWT 的元數據,通常是下面的樣子
  • 載荷(payload) 是一個 JSON 對象,用來存放實際需要傳遞的數據
  • 簽證(signature) 對header和payload使用密鑰進行簽名,防止數據篡改。

① 頭部header

Jwt的頭部是一個JSON,然後使用Base64URL編碼,承載兩部分信息:

  • 聲明類型typ,表示這個令牌(token)的類型(type),JWT令牌統一寫為JWT
  • 聲明加密的算法alg,通常直接使用HMACSHA256,就是HS256了,也可以使用RSA,支持很多算法(HS256、HS384、HS512、RS256、RS384、RS512、ES256、ES384、ES512、PS256、PS384)

var header = Base64URL({ "alg": "HS256", "typ": "JWT"})

Base64URL:Header 和 Payload 串型化的算法是 Base64URL。這個算法跟 Base64 算法基本類似,但有一些小的不同。JWT 作為一個令牌(token),有些場合可能會放到 URL(比如 api.example.com/?token=xxx)。Base64 有三個字符+、/和=,在 URL 裡面有特殊含義,所以要被替換掉:=被省略、+替換成-,/替換成_ 。這就是 Base64URL 算法。

② 載荷payload

payload也是一個JSON字符串,是承載消息具體內容的地方,也需要使用Base64URL編碼,payload中可以包含預定義的7個可用,它們不是強制性的,但推薦使用,也可以添加任意自定義的key

  1. iss(issuer): jwt簽發者
  2. sub(subject): jwt所面向的用戶
  3. aud(audience): 接收jwt的一方, 受眾
  4. exp(expiration time): jwt的過期時間,這個過期時間必須要大於簽發時間
  5. nbf(Not Before): 生效時間,定義在什麼時間之前.
  6. iat(Issued At): jwt的簽發時間
  7. jti(JWT ID): jwt的唯一身份標識,主要用來作為一次性token,從而回避重放攻擊。

// 該token簽發給1234567890,姓名為John Doe(自定義的字段),簽發時間為1516239022

var payload = Base64URL( {"sub": "1234567890", "name": "John Doe", "iat": 1516239022})

注意,JWT中payload是不加密的,只是Base64URL編碼一下,任何人拿到都可以進行解碼,所以不要把敏感信息放到裡面。

③ signature

Signature 部分是對前兩部分的簽名,防止數據篡改。

var header = Base64URL({ "alg": "HS256", "typ": "JWT"});
var payload = Base64URL( {"sub": "1234567890", "name": "John Doe", "iat": 1516239022});
var secret = "私鑰";
var signature = HMACSHA256(header + "." + payload, secret);
var jwt = header + "." + payload + "." + signature;

我們可以使用jwt.io調試器來解碼,驗證和生成JWT:

(建議收藏) | Spring Boot集成JSON Web Token(JWT)

注意:secret是保存在服務器端的,jwt的簽發生成也是在服務器端的,secret就是用來進行jwt的簽發和jwt的驗證,所以,它就是你服務端的私鑰,在任何場景都不應該流露出去。一旦客戶端得知這個secret, 那就意味著客戶端是可以自我簽發jwt了。

1.3.4 jwt的特點

  • 因為json的通用性,所以JWT是可以進行跨語言支持的,像JAVA,JavaScript,NodeJS,PHP等很多語言都可以使用。
  • 因為有了payload部分,所以JWT可以在自身存儲一些其他業務邏輯所必要的非敏感信息。
  • 它不需要在服務端保存會話信息, 所以它易於應用的擴展

JWT 的幾個特點

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

(2)JWT 不加密的情況下,不能將敏感數據(如密碼)寫入 JWT,除非對payload進行加密。保護好secret私鑰,該私鑰非常重要。

(3)JWT 不僅可以用於認證,也可以用於交換信息。有效使用 JWT,可以降低服務器查詢數據庫的次數。

(4)JWT 的最大缺點是,由於服務器不保存 session 狀態,因此無法在使用過程中廢止某個 token,或者更改 token 的權限。也就是說,一旦 JWT 簽發了,在到期之前就會始終有效,除非服務器部署額外的邏輯。

(5)JWT 本身包含了認證信息,一旦洩露,任何人都可以獲得該令牌的所有權限。為了減少盜用,JWT 的有效期應該設置得比較短。對於一些比較重要的權限,使用時應該再次對用戶進行認證。

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

1.3.5 JWT的優點:

  • 體積小,因而傳輸速度更快
  • 多樣化的傳輸方式,可以通過URL傳輸、POST傳輸、請求頭Header傳輸(常用)
  • 簡單方便,服務端拿到jwt後無需再次查詢數據庫校驗token可用性,也無需進行redis緩存校驗
  • 在分佈式系統中,很好地解決了單點登錄問題
  • 很方便的解決了跨域授權問題,因為跨域無法共享cookie

1.3.6 JWT的缺點:

  • 因為JWT是無狀態的,因此服務端無法控制已經生成的Token失效,是不可控的,這一點對於是否使用jwt是需要重點考量的
  • 獲取到jwt也就擁有了登錄權限,因此jwt是不可洩露的,網站最好使用https,防止中間攻擊偷取jwt
  • 在退出登錄 / 修改密碼時怎樣實現JWT Token失效https://segmentfault.com/q/1010000010043871

1.3.7 對於JWT安全性:

JWT被確實存在被竊取的問題,但是如果能得到別人的token,其實也就相當於能竊取別人的密碼,這其實已經不是JWT安全性的問題。網絡是存在多種不安全性的,對於傳統的session登錄的方式,如果別人能竊取登錄後的sessionID,也就能模擬登錄狀態,這和JWT是類似的。為了安全,https加密非常有必要,對於JWT有效時間最好設置短一點。

1.3.8 常見問題

① JWT 安全嗎?

Base64編碼方式是可逆的,也就是透過編碼後發放的Token內容是可以被解析的。一般而言,我們都不建議在有效載荷內放敏感訊息,比如使用者的密碼。

② JWT Payload 內容可以被偽造嗎?

JWT其中的一個組成內容為Signature,可以防止通過Base64可逆方法回推有效載荷內容並將其修改。因為Signature是經由Header跟Payload一起Base64組成的。

③ 如果我的 Cookie 被竊取了,那不就表示第三方可以做 CSRF 攻擊?

是的,Cookie丟失,就表示身份就可以被偽造。故官方建議的使用方式是存放在LocalStorage中,並放在請求頭中發送。

④ 空間及長度問題?

JWT Token通常長度不會太小,特別是Stateless JWT Token,把所有的數據都編在Token裡,很快的就會超過Cookie的大小(4K)或者是URL長度限制。

⑤ Token失效問題?

無狀態JWT令牌(Stateless JWT Token)發放出去之後,不能通過服務器端讓令牌失效,必須等到過期時間過才會失去效用。

假設在這之間Token被攔截,或者有權限管理身份的差異造成授權Scope修改,都不能阻止發出去的Token失效並要求使用者重新請求新的Token。

1.3.9 JWT使用建議

  • Payload中的exp時效不要設定太長。
  • 開啟Only Http預防XSS攻擊。
  • 如果擔心重播攻擊(replay attacks )可以增加jti(JWT ID),exp(有效時間) Claim。
  • 在你的應用程序應用層中增加黑名單機制,必要的時候可以進行Block做阻擋(這是針對掉令牌被第三方使用竊取的手動防禦)。

二:io.jsonwebtoken.jjwt

jwt使用流程

(建議收藏) | Spring Boot集成JSON Web Token(JWT)

一般是在請求頭裡加入Authorization,並加上Bearer標註:

// Authorization: Bearer <token>
getToken('api/user/1', {
headers: {
'Authorization': 'Bearer ' + token
}
})

io.jsonwebtoken是最常用的工具包。

<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>

application.properties

jwt.secret=JO6HN3NGIU25G2FIG8V7VD6CK9B6T2Z5
jwt.expire=60000
wtToken
@Configuration
public class JwtToken {
private static Logger logger = LoggerFactory.getLogger(JwtToken.class);
/** 祕鑰 */
@Value("${jwt.secret}")
private String secret;
/** 過期時間(秒) */
@Value("${jwt.expire}")
private long expire;
/**
* 生成jwt token
*/
public String generateToken(Long userId) {
Date nowDate = new Date();
Date expireDate = new Date(nowDate.getTime() + expire * 1000);
return Jwts.builder()
.setHeaderParam("typ", "JWT")
.setSubject(userId + "")
.setIssuedAt(nowDate)
.setExpiration(expireDate)
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
public Claims getClaimByToken(String token) {
if (StringUtils.isEmpty(token)) {
return null;
}
String[] header = token.split("Bearer");
token = header[1];
try {
return Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
}catch (Exception e){
logger.debug("validate is token error ", e);
return null;
}
}
/**
* token是否過期
* @return true:過期
*/
public static boolean isTokenExpired(Date expiration) {
return expiration.before(new Date());
}
// Getter && Setter
}
JwtController
@RestController
public class JwtController {
@Autowired
private JwtToken jwtToken;
@PostMapping("/login")
public String login(User user) {
// 1. 驗證用戶名和密碼
// 2. 驗證成功生成token
Long userId = 666L;
String token = jwtToken.generateToken(userId);
return token;
}
@GetMapping("/getUserInfo")
public String getUserInfo(@RequestHeader("Authorization") String authHeader) throws AuthenticationException {
// 黑名單token
List<String> blacklistToken = Arrays.asList("禁止訪問的token");
Claims claims = jwtToken.getClaimByToken(authHeader);
if (claims == null || JwtToken.isTokenExpired(claims.getExpiration()) || blacklistToken.contains(authHeader)) {
throw new AuthenticationException("token 不可用");
}
String userId = claims.getSubject();
// 根據用戶id獲取接口數據返回接口
return userId;
}
}
(建議收藏) | Spring Boot集成JSON Web Token(JWT)

(建議收藏) | Spring Boot集成JSON Web Token(JWT)

本號主要用於分享企業中常用的技術,更加側重於實用,歡迎關注,便於瀏覽其它更多實用的歷史文章。

"

相關推薦

推薦中...