Spring Boot詳解整合JWT教程

1、概述

JWT 簡稱 JSON Web Token,也就是通過JSON形式作為Web應用中的令牌,用於各方之間安全地將信息作為JSON對象傳輸,在數據傳輸的過程中還可以完成數據加密、簽名等相關處理。

2、優勢所在

在JavaWeb階段,經常使用session來存儲,以方便用來判斷用戶是否操作等等。但這又恰巧暴露瞭一個問題,用session存儲是需要占用服務器內存的。當用戶隻有一兩個的時候沒什麼問題,但是當用戶成千上萬的話,服務器就很難招架得住瞭。並且session是基於cookie來實現的,因此數據很容易被截獲,遭遇CSRF跨域偽造攻擊,因此不太安全。這時JWT的優勢就突顯出來的:

  • 簡潔(Compact): 可以通過URL,POST參數或者在HTTP header發送,因為數據量小,傳輸速度也很快
  • 自包含(Self-contained):負載中包含瞭所有用戶所需要的信息,避免瞭多次查詢數據庫
  • 因為Token是以JSON加密的形式保存在客戶端的,所以JWT是跨語言的,原則上任何web形式都支持。
  • 不需要在服務端保存會話信息,特別適用於分佈式微服務。

3、結構組成

JWT 中的 token 組成主要有三部分:

  • 標頭(Header)
  • 有效載荷(Payload)
  • 簽名(Signature)

3.1、標頭(Header)

標頭通常由兩部分組成:令牌的類型 ( 即JWT ) 和所使用的簽名算法,例如HMAC SHA256(默認)或RSA。它會使用Base64編碼組成JWT結構的第一部分。值得註意的是,Base64是一種編碼,也就是說,它是可以被翻譯回原來的樣子的,它並不是一種加密過程。

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

3.2、有效負載(Payload)

有效負荷是包含聲明,通常將需要傳遞的數據存放在Payload中,同樣的是使用Base64編碼,但這同時說明這一塊是可以被反編譯的,這是什麼意思呢?就是太敏感的信息存放在這裡的話存在被捕獲的可能,因此官方推薦在Payload中不要存放敏感的信息,例如女生的年齡。

3.3、簽名(Signature)

簽名是由編碼後的標頭和有效負荷以及我們提供的一個密鑰,然後使用標頭規定的簽名算法進行簽名。因此,簽名的主要作用是保證JWT沒有被篡改過,保證 token 的合法性。

這裡特別註意的是,密鑰 secret 是驗證 token 是否合法的重要憑證,如果用於驗證的密鑰被外界所獲取到的話,我們所建立的驗證防線將如同虛設!

4、Spring boot整合JWT 導入依賴

<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>3.19.0</version>
</dependency>

編寫JwtUtils,提高代碼的重用率

public class JwtUtils {
    /** 過期時間,單位:秒,默認半小時過期 **/
    private static final long EXPIRATION = 60 * 30L;
    /** 密鑰,一般長度較長,內容較復雜 **/
    private static final String SECRET = "my_secret";
    /**
     * @description 創建token
     * @author xBaozi
     * @date 20:49 2022/3/31
     **/
    public static String createToken(Map<String, String> claimMap) {
        // 當前時間戳加上設定的毫秒數(1秒 == 1000毫秒)
        Date expiration = new Date(System.currentTimeMillis() + EXPIRATION * 1000);
        // 設置JWT頭部
        Map<String, Object> map = new HashMap<>();
        map.put("alg", "HS256");
        map.put("typ", "JWT");
        // 創建token
        JWTCreator.Builder builder = JWT.create();
        //使用Lambda創建payload
        claimMap.forEach((k,v)->{
            builder.withClaim(k,v);
        });
        // 添加頭部,可省略保持默認,默認即map中的鍵值對
        return builder.withHeader(map)
                // 設置過期時間
                .withExpiresAt(expiration)
                // 設置簽名解碼算法
                .sign(Algorithm.HMAC256(SECRET));
    }
    /**
     * @description 驗證token
     * @author xBaozi
     * @date 23:36 2022/3/31
     **/
    public static DecodedJWT verifyToken(String token) {
        return JWT.require(Algorithm.HMAC256(SECRET)).build().verify(token);
    }
}

編寫過濾器,這是為瞭使得不用在每一個controller前端控制器都寫一遍獲取token,將token放在請求體中請求,從而簡化開發操作

public class JwtInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        Map<String, Object> map = new HashMap<>();
        //獲取請求頭中令牌
        String token = request.getHeader("token");
        try {
            //驗證令牌
            JwtUtils.verifyToken(token);
            //驗證成功,放行請求
            return true;
        } catch (SignatureVerificationException e) {
            e.printStackTrace();
            map.put("msg", "無效簽名!");
        } catch (TokenExpiredException e) {
            e.printStackTrace();
            map.put("msg", "token過期!");
        } catch (AlgorithmMismatchException e) {
            e.printStackTrace();
            map.put("msg", "token算法不一致!");
        } catch (Exception e) {
            e.printStackTrace();
            map.put("msg", "token無效!!");
        }
        //設置狀態
        map.put("state", false);
        //將map轉為json
        String json = new ObjectMapper().writeValueAsString(map);
        // 相應json數據
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().println(json);
        return false;
    }
}

配置過濾器

@Configuration
public class JwtConfig implements WebMvcConfigurer{
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(new JwtInterceptor())
                    //添加攔截路徑
                    .addPathPatterns("/**")
                    //添加放行路徑
                    .excludePathPatterns("/user/login");
        }
}

編寫前端控制器,這裡以兩個為例

@RestController
@RequestMapping("/user")
@Slf4j
public class UserController {
    // 這裡隻做演示,就不寫Service層瞭
    // @Autowired
    // private UserService userService;
    /**
     * @description 登錄功能
     * @author xBaozi
     * @date 0:02 2022/4/1
     * @param user  user對象,默認有值
     **/
    @GetMapping("/login")
    public Map<String,Object> login(User user){
        log.info("用戶名: [{}]",user.getName());
        log.info("密碼: [{}]",user.getPassword());
        Map<String, Object> map = new HashMap<>();
        try{
            Map<String,String> payload =  new HashMap<>();
            payload.put("id",user.getId());
            payload.put("name",user.getName());
            // 生成JWT的令牌
            String token = JwtUtils.createToken(payload);
            map.put("state",true);
            map.put("msg","認證成功");
            // 響應token
            map.put("token",token);
        }catch (Exception e){
            map.put("state",false);
            map.put("msg",e.getMessage());
        }
        return map;
    }
    @PostMapping("/other")
    public Map<String,Object> test(HttpServletRequest request){
        Map<String, Object> map = new HashMap<>();
        //處理自己業務邏輯
        String token = request.getHeader("token");
        DecodedJWT verify = JwtUtils.verifyToken(token);
        log.info("用戶id: [{}]",verify.getClaim("id").asString());
        log.info("用戶name: [{}]",verify.getClaim("name").asString());
        map.put("state",true);
        map.put("msg","請求成功!");
        return map;
    }
}

postman測試

IDEA控制臺查看信息

搞完收工,這裡的token交由前端進行管理保存!!!溜瞭~

到此這篇關於Spring Boot詳解整合JWT教程的文章就介紹到這瞭,更多相關Spring Boot JWT內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: