SpringBoot實現JWT認證


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實現思路

實現思路:

  1. 驗證用戶的賬號密碼
  2. 如果正確,生成UUID作為Key
  3. 將此Key作為Key,將用戶信息作為Value,存入Redis
  4. 最后返回Token給客戶端,客戶端將Cookie保存到Cookie中

用戶在每次請求時,都會攜帶此Token,后端在攔截器中校驗Token是否存在,如果存在找到對應的用戶信息,判斷其有哪些權限。

Token優點:

  1. 可以通過Header、Body提交,實現跨域操作
  2. 可以隱藏參數的真實性,實現參數的脫敏
  3. 臨時、唯一

存在問題:

  1. 使用Token,必須依賴Redis和Cookie
  2. 需要頻繁操作Redis

JWT

簡介

JWT,全稱Json Web Token,是目前最流行的跨域認證解決方案。它的實現思想和上面的token是基本一致的,是一種更加成熟和完善的解決方案。

原理

JWT的原理就是,當服務器認證賬號密碼通過后,生成一個JSON對象,返回給用戶,保存在Cookie中。當用戶下一次訪問的時候自動攜帶這個JSON對象,服務器可以根據這個對象判斷用戶的身份。為了防止用戶篡改數據信息,服務器生成這個JSON的時候,會進行一些加密操作。此時服務器中就不需要保存session數據。

JWT的數據結構

JWT中的數據分為三部分,每部分都是一串很長的字符串,中間用.間隔

  • header: 頭部,標記加密算法
  • Payload: 負載,存放具體數據
  • Signature:簽名,Payload采用MD5加密后的簽名值

完整的格式為:header.Payload.Signature

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

  1. 創建兩個JSONObject對象,分別作為Header和Payload
  2. 初始化Header,添加相應內容,進行base64編碼
  3. 初始化Payload,添加相應內容,進行base64編碼+混淆
  4. 對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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM