SpringBoot實現JWT認證
本文會從Token、JWT、JWT的實現、JWTUtil封裝到SpringBoot中使用JWT,如果有一定的基礎,可以跳過前面的內容~
Token
簡介
Token 是一個臨時、唯一、保證不重復的令牌,例如智能門鎖,它可以生成一個臨時密碼,具有一定時間內的有效期。
實現思路
UUID具有上述的特性,所以我們可以使用UUID作為token,生產UUID后放入Redis,設置Redis的過期時間。
Token的SessionID
token和SESSIONID非常的相似,但是SESSIONID在分布式項目中不能共享,雖然SESSION可以通過Redis等技術實現共享,但是使用這類技術會降低項目的性能和可用性。所以現在普通使用Token代替Session使用。
簡單的Token實現思路
實現思路:
- 驗證用戶的賬號密碼
- 如果正確,生成UUID作為Key
- 將此Key作為Key,將用戶信息作為Value,存入Redis
- 最后返回Token給客戶端,客戶端將Cookie保存到Cookie中
用戶在每次請求時,都會攜帶此Token,后端在攔截器中校驗Token是否存在,如果存在找到對應的用戶信息,判斷其有哪些權限。
Token優點:
- 可以通過Header、Body提交,實現跨域操作
- 可以隱藏參數的真實性,實現參數的脫敏
- 臨時、唯一
存在問題:
- 使用Token,必須依賴Redis和Cookie
- 需要頻繁操作Redis
JWT
簡介
JWT,全稱Json Web Token,是目前最流行的跨域認證解決方案。它的實現思想和上面的token是基本一致的,是一種更加成熟和完善的解決方案。
原理
JWT的原理就是,當服務器認證賬號密碼通過后,生成一個JSON對象,返回給用戶,保存在Cookie中。當用戶下一次訪問的時候自動攜帶這個JSON對象,服務器可以根據這個對象判斷用戶的身份。為了防止用戶篡改數據信息,服務器生成這個JSON的時候,會進行一些加密操作。此時服務器中就不需要保存session數據。
JWT的數據結構
JWT中的數據分為三部分,每部分都是一串很長的字符串,中間用.
間隔
- header: 頭部,標記加密算法
- Payload: 負載,存放具體數據
- Signature:簽名,Payload采用MD5加密后的簽名值
完整的格式為:header.Payload.Signature
Header
Header部分是由一個JSON對象組成,它描述JWT的元數據,通常是下面的樣子:
{
'alg' : "HS256",
"typ" : "JWT"
}
alg表示簽名的算法,默認為HMAC SHA256(可以寫成 HS256)
typ屬性表示令牌的類型,JWT令牌統一寫為JWT
生成JWT后,此部分會進行BASE64編碼,最終被解析為:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
jwt中的Header部分的內容默認是沒有加密的,只是進行了Base64處理。可以直接使用Base64反加密獲取原文。
Payload
Payload部分也是一個JSON對象,用來存放實際需要傳遞的數據。JWT規定了7個官方字段:
- iss (issuer):簽發人
- exp (expiration time):過期時間
- sub (subject):主題
- aud (audience):受眾
- nbf (Not Before):生效時間
- iat (Issued At):簽發時間
- jti (JWT ID):編號
除了官方字段,還可以支持自定義字段,下面就是一個例子:
{
"name": "rayfoo",
"phone": 18338862369
}
生產JWT后的:
eyJuYW1lIjoicmF5Zm9vIiwicGhvbmUiOjE4MzM4ODYyMzY5fQ
注意,這部分的內容默認也是沒有加密的,只是進行了Base64編碼。可以直接使用Base64反加密獲取原文。但是我們可以對其進行一些混淆操作。
Signature
Signature 部分是對前兩部分的簽名,防止數據篡改。
首先,需要指定一個密鑰(secret)。這個密鑰只有服務器才知道,不能泄露給用戶。然后,使用 Header 里面指定的簽名算法(默認是 HMAC SHA256),按照下面的公式產生簽名。
這一段並不是使用base64加密,而是使用header中提供的加密方式進行的加密.
可以淺顯的理解為將Payload中的數據按照header,payload+密鑰(secret)作為一個整體進行MD5(也可能是任意類型的)加密。在下面這段代碼中,密鑰就是:rayfoo。
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
rayfoo
)
jwt生成的signature
AIwKf4x_nYr1N_cmw_VQ5t_nuaX5b-gTN8RgHtkTO4w
完整的token
算出簽名以后,把 Header、Payload、Signature 三個部分拼成一個字符串,每個部分之間用"點"(.
)分隔,就可以返回給用戶。這就是完整的JWT:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoicmF5Zm9vIiwicGhvbmUiOjE4MzM4ODYyMzY5fQ.AIwKf4x_nYr1N_cmw_VQ5t_nuaX5b-gTN8RgHtkTO4w
可以在https://jwt.io/#encoded-jwt進行測試
Base64URL編碼
前面提到,Header 和 Payload 串型化的算法是 Base64URL。這個算法跟 Base64 算法基本類似,但有一些小的不同。
JWT 作為一個令牌(token),有些場合可能會放到 URL(比如 api.example.com/?token=xxx)。Base64 有三個字符+
、/
和=
,在 URL 里面有特殊含義,所以要被替換掉:=
被省略、+
替換成-
,/
替換成_
。這就是 Base64URL 算法。
JWT的使用方式
客戶端收到服務器返回的 JWT,可以儲存在 Cookie 里面,也可以儲存在 localStorage。
此后,客戶端每次與服務器通信,都要帶上這個 JWT。你可以把它放在 Cookie 里面自動發送,但是這樣不能跨域,所以更好的做法是放在 HTTP 請求的頭信息Authorization
字段里面。
Authorization: Bearer <token>
另一種做法是,跨域的時候,JWT 就放在 POST 請求的數據體里面。
JWT 的幾個特點
優點:
(1)JWT 默認是不加密,但也是可以加密的。生成原始 Token 以后,可以用密鑰再加密一次,不容易被客戶端修改。
(2)JWT 不加密的情況下,不能將秘密數據寫入 JWT。
(3)JWT 不僅可以用於認證,也可以用於交換信息。有效使用 JWT,可以降低服務器查詢數據庫的次數。效率也比token高。
缺點:
(1)JWT 的最大缺點是,由於服務器不保存 session 狀態,因此無法在使用過程中廢止某個 token,或者更改 token 的權限。也就是說,一旦 JWT 簽發了,在到期之前就會始終有效,除非服務器部署額外的邏輯。
(2)JWT 本身包含了認證信息,一旦泄露,任何人都可以獲得該令牌的所有權限。為了減少盜用,JWT 的有效期應該設置得比較短。對於一些比較重要的權限,使用時應該再次對用戶進行認證。
(3)為了減少盜用,JWT 不應該使用 HTTP 協議明碼傳輸,要使用 HTTPS 協議傳輸。
(4)如果jwt中payload的數據過多,會占用服務器的帶寬資源。
如何手寫一個JWT
了解了上面的一些概念后,我們可以自己動手實現一個jwt
- 創建兩個JSONObject對象,分別作為Header和Payload
- 初始化Header,添加相應內容,進行base64編碼
- 初始化Payload,添加相應內容,進行base64編碼+混淆
- 對Payload進行md5加鹽、加密
對三部分內容進行拼接,使用.
間隔
建議在payload中增加一個時間戳,用於指定過期時間。
在Java中使用JWT
jwt提供了不止一種的實現
-
Auth0實現 的 java-jwt
-
Brian Campbell實現的 jose4j
-
connect2id實現的 nimbus-jose-jwt
-
Les Haziewood實現的 jjwt
-
Inversoft實現的prime-jwt
-
Vertx實現的vertx-auth-jwt.
幾乎所有庫都要求JAVA版本1.7或更高版本, 1.6或以下的版本需要二次開發(或不支持)
從易用性, 擴展性, 完整性等來看, 使用首先推薦 jose4j, 其次是 Nimbus-jose-jwt.
關於這些類庫的評測:http://andaily.com/blog/?p=956
封裝JWT工具類&payload加密
引入依賴
下面的代碼都是基於auth0 提供的 java-jwt實現的
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.0</version>
</dependency>
封裝工具類
由於JWT中的payload是不安全的,沒有進行加密,所以在工具類中進行了加密操作。
這里的加密操作只是一種加密思路,你也可以使用自己的任意加密方式來讓payload中的內容更加安全。
package cn.rayfoo.common.util;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.interfaces.DecodedJWT;
import java.util.Calendar;
import java.util.Map;
/**
* @author rayfoo@qq.com
* @version 1.0
* <p>JSON WEB TOKEN 工具類</p>
* @date 2020/8/11 9:19
*/
public class JWTUtil {
/**
* 簽名 此簽名為 rayfoo 的16位 大寫 MD5
*/
private static final String SIGN_KEY = "5A1332068BA9FD17";
/**
* 默認的過期時間,30分鍾
*/
private static final Integer DEFAULT_EXPIRES = 60 * 30;
/**
* token默認的長度
*/
private static final Integer DEFAULT_TOKEN_SIZE = 3;
/**
* 生成令牌
*
* @param map 數據正文
* @param expires 過期時間,單位(秒)
*/
public static String getToken(Map<String, String> map, Integer expires) throws Exception {
//創建日歷
Calendar instance = Calendar.getInstance();
//設置過期時間
instance.add(Calendar.SECOND, expires);
//創建jwt builder對象
JWTCreator.Builder builder = JWT.create();
//payload
map.forEach((k, v) -> {
builder.withClaim(k, v);
});
//指定過期時間
String token = builder.withExpiresAt(instance.getTime())
//設置加密方式
.sign(Algorithm.HMAC256(SIGN_KEY));
//返回tokean
return confoundPayload(token);
}
/**
* 解析token
*
* @param token 輸入混淆payload后的token
*/
public static DecodedJWT verify(String token) throws Exception {
//如果token無效
if (token == null || "".equals(token)) {
throw new JWTDecodeException("無效的token!");
}
//解析token
String dToken = deConfoundPayload(token);
//創建返回結果
return JWT.require(Algorithm.HMAC256(SIGN_KEY)).build().verify(dToken);
}
/**
* 重載getToken 此方法為獲取默認30分鍾有效期的token
*
* @param map 數據正文
*/
public static String getToken(Map<String, String> map) throws Exception {
return getToken(map, DEFAULT_EXPIRES);
}
/**
* 對一個base64編碼進行混淆 此處還可以進行replace混淆,考慮到效率問題,這里就不做啦~
* 對於加密的思路還有位移、字符替換等~
*
* @param token 混淆payload前的token
*/
private static String confoundPayload(String token) throws Exception {
//分割token
String[] split = token.split("\\.");
//如果token不符合規范
if (split.length != DEFAULT_TOKEN_SIZE) {
throw new JWTDecodeException("簽名不正確");
}
//取出payload
String payload = split[1];
//獲取長度
int length = payload.length() / 2;
//指定截取點
int index = payload.length() % 2 != 0 ? length + 1 : length;
//混淆處理后的token
return split[0] + "." + reversePayload(payload, index) + "." + split[2];
}
/**
* 對一個混淆后的base編碼進行解析
*
* @param token 混淆后的token
*/
private static String deConfoundPayload(String token) throws Exception {
//分割token
String[] split = token.split("\\.");
//如果token不符合規范
if (split.length != DEFAULT_TOKEN_SIZE) {
throw new JWTDecodeException("簽名不正確");
}
//取出payload
String payload = split[1];
//返回解析后的token
return split[0] + "." + reversePayload(payload, payload.length() / 2) + "." + split[2];
}
/**
* 將md5編碼位移
*
* @param payload payload編碼
* @param index 位移處
*/
private static String reversePayload(String payload, Integer index) {
return payload.substring(index) + payload.substring(0, index);
}
}
此時,我們就可以使用此工具類頒發token、解析token了~
package cn.rayfoo;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.auth0.jwt.interfaces.Verification;
import org.apache.commons.codec.binary.StringUtils;
import java.util.*;
/**
* @author rayfoo@qq.com
* @version 1.0
* <p></p>
* @date 2020/8/10 16:18
*/
public class JwtTest {
//密鑰
private static final String SIGN_KEY = "rayfoo";
public static void main(String[] args) throws Exception{
//創建map
Map<String, String> map = new HashMap<>();
map.put("username", "rayfoo");
//頒發token
String token = JWTUtil.getToken(map);
System.out.println(token);
//解析token
DecodedJWT verify = JWTUtil.verify(token);
System.out.println(verify.getClaim("username").asString());
}
}
此時,使用混淆后的token解析,發現無法解析到payload:
常見異常的處理
- JWTDecodeException:header、payload被修改會出現的異常
- SignatureVerificationException:簽名不匹配異常
- TokenExpiredException:令牌過期異常
- AlgorithmMismatchException:算法不匹配異常
建議使用全局異常處理進行細粒度異常處理
在SpringBoot中使用JWT
使用JWT之前
先基於SpringBoot+MyBatis實現一個簡單的查詢操作,完整的代碼稍后會上傳到Github,這里只進行關鍵部分的介紹。
傳統的密碼校驗:
@Override
public User login(User user) throws Exception {
//這里假設user、user內的username、password數據都是正確的
User example = User.builder().username(user.getUsername()).password(user.getPassword()).build();
//查詢用戶是否存在
List<User> reslut = userMapper.select(example);
//如果沒找到代表用戶名或者密碼錯誤
if (ObjectUtils.isEmpty(reslut)) {
throw new Exception("用戶名或密碼錯誤!");
}
return reslut.get(0);
}
上面時service層代碼,如果執行沒有報錯說明拿到了正確的查詢結果,此時在Controller中可以將用戶的登錄信息保存到Session或者Redis中,用於校驗。
@PostMapping("/login")
public Map<String, Object> login(@RequestBody User user) {
//初始化返回值
Map<String, Object> map = new HashMap<>(2);
try {
//用戶登錄校驗
User loginUser = userService.login(user);
//沒有拋出異常表示正常
map.put("code", 200);
map.put("msg", "認證成功!");
//使用session或者redis記錄。。。
} catch (Exception exception) {
//如果出現異常記錄錯誤信息
map.put("code", 500);
map.put("msg", exception.getMessage());
}
//返回結果
return map;
}
使用JWT
有了JWT以后,我們可以使用token來代替Session/Redis
@PostMapping("/login")
public Map<String, Object> login(@RequestBody User user) {
//初始化返回值
Map<String, Object> map = new HashMap<>(3);
try {
//用戶登錄校驗
User loginUser = userService.login(user);
//沒有拋出異常表示正常
map.put("code", 200);
map.put("msg", "認證成功!");
//聲明payload
Map<String, String> payload = new HashMap<>(2);
//初始化payload
payload.put("id", loginUser.getId().toString());
payload.put("username", loginUser.getUsername());
//獲取令牌
String token = JWTUtil.getToken(payload,20);
//在響應結果中添加token
map.put("token", token);
} catch (Exception exception) {
//如果出現異常記錄錯誤信息
map.put("code", 500);
map.put("msg", exception.getMessage());
}
//返回結果
return map;
}
JWT包含有權限的接口
這里的代碼沒有進行優化,只是用最簡單直白的方式介紹了JWT對接口的保護
@GetMapping("/list")
public Map<String, Object> userList(String token) {
//初始化返回值
Map<String, Object> map = new HashMap<>(3);
List<User> result = null;
String errorMsg = "";
//校驗token
log.info("當前token為:" + token);
try {
//驗證令牌
DecodedJWT verify = JWTUtil.verify(token);
//如果令牌校驗成功
result = userService.userList();
//返回查詢結果
map.put("code", 200);
map.put("msg", "查詢成功");
map.put("result", result);
return map;
} catch (JWTDecodeException e) {
//其實是用戶修改了header或者payload,但是不用告訴用戶錯誤的細節
e.printStackTrace();
errorMsg = "token無效!";
} catch (SignatureVerificationException e) {
e.printStackTrace();
//其實是修改了簽名,但是不用告訴用戶錯誤的細節
errorMsg = "token無效!";
} catch (TokenExpiredException e) {
e.printStackTrace();
errorMsg = "token已過期!";
} catch (AlgorithmMismatchException e) {
e.printStackTrace();
//其實是修改了算法,但是不用告訴用戶錯誤的細節
errorMsg = "token無效!";
} catch (Exception e) {
e.printStackTrace();
errorMsg = "token無效!";
}
//返回錯誤信息
map.put("code", 500);
map.put("msg", "token校驗失敗");
map.put("result", errorMsg);
return map;
}
使用攔截器優化Token校驗
在前面的案例中,如果每個接口都進行攔截器校驗,冗余的代碼會非常的多,程序的可讀性也非常低。
在單體應用中,可以使用攔截器來校驗token
分布式項目中,可以在網關內校驗token
token攔截器
在上面的案例中,token在body中作為數據傳遞的,但是這樣是不安全的,比較推薦的做法是加在請求頭內,通過請求頭攜帶.
package cn.rayfoo.modules.base.interceptor;
import cn.rayfoo.common.util.JWTUtil;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author rayfoo@qq.com
* @version 1.0
* <p></p>
* @date 2020/8/11 15:58
*/
public class JWTInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//從請求頭內獲取token
String token = request.getHeader("authorization");
//驗證令牌 如果令牌不正確會出現異常 被全局異常處理
JWTUtil.verify(token);
return true;
}
}
攔截器注冊
這里我對所有的請求進行了攔截,放行了登錄接口,真實的場景下,我們一般會放行所有/user/**的請求,另外,這個攔截器中沒有注入其他屬性,所以可以通過此種方式創建,如果攔截器內注入了屬性,需要使用@Bean+方法的形式注冊攔截器。詳細的內容,可以參考我博客中關於攔截器的介紹。
package cn.rayfoo.common.config;
import cn.rayfoo.modules.base.interceptor.JWTInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* @author rayfoo@qq.com
* @version 1.0
* <p></p>
* @date 2020/8/11 16:13
*/
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new JWTInterceptor())
.addPathPatterns("/**")
.excludePathPatterns("/user/login");
}
}
封裝統一返回結果類
接下來的代碼我會使用統一結果集來封裝返回值,下面是統一結果集的代碼:
package cn.rayfoo.common.response;
import lombok.Data;
/**
* @author rayfoo@qq.com
* @date 2020年8月6日
*/
@Data
public class Result<T> {
/**
* 狀態碼
*/
private Integer code;
/**
* 提示信息
*/
private String msg;
/**
* 數據記錄
*/
private T data;
public Result() {
}
public Result(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
public Result(Integer code, String msg, T data) {
this.code = code;
this.msg = msg;
this.data = data;
}
}
全局異常處理
在攔截器中處理異常也是非常不好的習慣,我們可以將異常交由統一異常處理來管理
package cn.rayfoo.common.exception;
import cn.rayfoo.common.response.Result;
import com.auth0.jwt.exceptions.AlgorithmMismatchException;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* @author rayfoo@qq.com
* @version 1.0
* <p>全局異常處理</p>
* @date 2020/8/11 16:36
*/
@ControllerAdvice@Slf4j
public class ServiceExceptionHandler {
/**
* 默認異常的狀態碼
*/
private static final Integer DEFAULT_EXCEPTION = 500;
/**
* token超時異常狀態碼
*/
private static final Integer TOKEN_ERROR_EXCEPTION = 505;
/**
* token無效狀態碼
*/
private static final Integer TOKEN_EXPIRED_EXCEPTION = 506;
/**
* 處理token異常
*/
@ResponseBody
@ExceptionHandler({SignatureVerificationException.class, AlgorithmMismatchException.class, JWTDecodeException.class})
public Result<String> tokenErrorException() {
Result<String> result = new Result<>();
result.setCode(TOKEN_ERROR_EXCEPTION);
result.setMsg("無效的token!");
log.error("無效的token");
return result;
}
/**
* 處理token異常
*/
@ResponseBody
@ExceptionHandler({TokenExpiredException.class})
public Result<String> tokenExpiredException() {
Result<String> result = new Result<>();
result.setCode(TOKEN_EXPIRED_EXCEPTION);
result.setMsg("token超時!");
log.error("用戶token超時");
return result;
}
/**
* 處理所有RuntimeException異常
*/
@ResponseBody
@ExceptionHandler({RuntimeException.class})
public Result<String> allException(RuntimeException e) {
Result<String> result = new Result<>();
result.setCode(DEFAULT_EXCEPTION);
result.setMsg( e.getMessage());
log.error(e.getMessage());
e.printStackTrace();
return result;
}
/**
* 處理所有Exception異常
*/
@ResponseBody
@ExceptionHandler({Exception.class})
public Result<String> allException(Exception e) {
Result<String> result = new Result<>();
result.setCode(DEFAULT_EXCEPTION);
result.setMsg( e.getMessage());
log.error(e.getMessage());
e.printStackTrace();
return result;
}
}
封裝BaseController
這里介紹一個Controller的小技巧,可以通過通用Controller來封裝一些公共的屬性
package cn.rayfoo.modules.base.controller;
import cn.rayfoo.modules.base.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.ModelAttribute;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
/**
* @author rayfoo@qq.com
* @version 1.0
* @date 2020/8/5 14:34
* @description 基礎controller
*/
public class BaseController {
/**
* 注入全部service
*/
@Autowired
protected UserService userService;
/**
* 創建session、Request、Response等對象
*/
protected HttpServletRequest request;
protected HttpServletResponse response;
protected HttpSession session;
/**
* 在每個子類方法調用之前先調用
* 設置request,response,session這三個對象
*
* @param request
* @param response
*/
@ModelAttribute
public void setReqAndRes(HttpServletRequest request, HttpServletResponse response) {
this.request = request;
this.response = response;
this.session = request.getSession(true);
//可以在此處拿到當前登錄的用戶
}
}
Controller代碼優化
如果需要用到token中的數據,可以使用request對象中獲取token進行相關的處理。下面是優化后的Controller代碼,是不是煥然一新呢?
package cn.rayfoo.modules.base.controller;
import cn.rayfoo.common.response.HttpStatus;
import cn.rayfoo.common.response.Result;
import cn.rayfoo.common.util.JWTUtil;
import cn.rayfoo.modules.base.entity.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author rayfoo@qq.com
* @version 1.0
* <p></p>
* @date 2020/8/11 14:12
*/
@RestController
@Slf4j
@RequestMapping("/user")
public class UserController extends BaseController {
@PostMapping("/login")
public Result<String> login(@RequestBody User user) throws Exception {
//初始化返回值
Result<String> result = new Result<>();
//用戶登錄校驗
User loginUser = userService.login(user);
//沒有拋出異常表示正常
result.setCode(HttpStatus.OK.value());
result.setMsg("認證成功!");
//聲明payload
Map<String, String> payload = new HashMap<>(2);
//初始化payload
payload.put("id", loginUser.getId().toString());
payload.put("username", loginUser.getUsername());
//獲取令牌
String token = JWTUtil.getToken(payload, 20);
//在響應結果中添加token
result.setData(token);
//返回結果
return result;
}
@GetMapping("/list")
public Result<List<User>> userList() throws Exception {
//初始化返回值
Result<List<User>> result = new Result<>();
//如果成功,設置狀態碼和查詢到的結果
result.setCode(HttpStatus.OK.value());
result.setMsg("查詢成功!");
List<User> users = userService.userList();
result.setData(users);
//返回結果
return result;
}
}
github:https://github.com/18338862369/SpringBoot-JWT
gitee:https://gitee.com/rayfoo/SpringBoot-JWT
如果代碼對你有幫助 青幫我點個star哦~
參考:http://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html