Spring Boot學習04--使用JWT進行Token驗證
JWT(Json Web Token)是一種認證協議,在前後端分離的專案中,客戶端向服務端傳送的請求存在“跨域”的問題。而在分散式架構體系下,一般會有多個伺服器提供服務。例如使用Nginx進行反向代理,使用輪詢或根據負載的策略對伺服器進行分配時,客戶端每次請求的可能是不同的伺服器。
這兩種情況下,傳統的Session儲存客戶端身份的方式就會有很多的不便。而JWT是解決方案之一。
1. Token的組成
JWT的Token由三部分組成:頭部(Header)、負載(Payload)、驗證簽名(Signature)。
這三部分之間,使用英文句號"."分割,舉例如下:
首先有一個固定的明文的字首:"Bearer "。
"token": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOjUwMCwicmlkIjowLCJpYXQiOjE2MTExOTE3OTksImV4cCI6MTYxMTI3ODE5OX0.dCZFvxwJ0TS_fB_yj_6msveURl6xMYrsaaWraRqOA7Q"
Header預設內容如下:
{ 'alg': "HS256", 'typ': "JWT" }
將其進行Base64編碼,得到的字串為:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9。頭部是緊跟在Bearer 後面的的內容。
Payload中儲存的是有效的資訊,也是一個json格式,例如:
{ 'id': 1, 'username': "zhangsan", 'gender': 0 }
對其進行Base64編碼,得到字串為:ewogICAgJ2lkJzogMSwKICAgICd1c2VybmFtZSc6ICJ6aGFuZ3NhbiIsCiAgICAnZ2VuZGVyJzogMAp9。
Signature是根據兩部分的資訊進行簽名得到的字串,其簽名規則為:
HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
注:這裡加密演算法HMACSHA256由頭部來指定,secret是儲存在服務端的祕鑰。
2.在Spring Boot專案中使用JWT進行Token驗證
首先在pom檔案中新增JWT的座標
<dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.10.3</version> </dependency>
編寫工具類,實現Token的生成和驗證邏輯:
1 package com.example.demo.utils; 2 3 import com.auth0.jwt.JWT; 4 import com.auth0.jwt.JWTCreator; 5 import com.auth0.jwt.JWTVerifier; 6 import com.auth0.jwt.algorithms.Algorithm; 7 import com.auth0.jwt.interfaces.DecodedJWT; 8 9 import java.util.Calendar; 10 import java.util.Date; 11 import java.util.Map; 12 13 public class JWTUtils { 14 15 public static final String secret = "itcast"; 16 17 //生成token 18 public static String getToken(Map<String,String> map){ 19 Calendar instance = Calendar.getInstance(); 20 Date date = instance.getTime(); 21 instance.add(Calendar.DATE,1); 22 23 JWTCreator.Builder builder = JWT.create(); 24 map.forEach( (k,v) -> { 25 builder.withClaim(k,v); 26 }); 27 28 String token = builder 29 .withIssuedAt(date) 30 .withExpiresAt(instance.getTime()) 31 .sign(Algorithm.HMAC256(secret)); 32 33 return token; 34 } 35 36 public static DecodedJWT verify(String token){ 37 JWTVerifier build = JWT.require(Algorithm.HMAC256(secret)).build(); 38 DecodedJWT verify = build.verify(token); 39 return verify; 40 } 41 }
編寫攔截器,實現對HTTP請求中的Token自動驗證:
1 package com.example.demo.interceptors; 2 3 import com.auth0.jwt.interfaces.DecodedJWT; 4 import com.example.demo.utils.JWTUtils; 5 import com.fasterxml.jackson.databind.ObjectMapper; 6 import org.springframework.web.servlet.HandlerInterceptor; 7 8 import javax.servlet.http.HttpServletRequest; 9 import javax.servlet.http.HttpServletResponse; 10 import java.util.HashMap; 11 import java.util.Map; 12 13 public class JWTInterceptor implements HandlerInterceptor { 14 @Override 15 public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { 16 Map<String,Object> map = new HashMap<>(); 17 String token = request.getHeader("Authorization"); 18 try{ 19 DecodedJWT verify = JWTUtils.verify(token); 20 return true; 21 } 22 catch (Exception e){ 23 e.printStackTrace(); 24 map.put("state",false); 25 map.put("msg","請求失敗"); 26 } 27 String json = new ObjectMapper().writeValueAsString(map); 28 response.setContentType("application/json;charset=utf-8"); 29 response.getWriter().println(json); 30 return false; 31 } 32 }
編寫Controller對Token接受請求,並處理:
1 package com.example.demo.controller; 2 3 import com.alibaba.fastjson.JSON; 4 import com.alibaba.fastjson.JSONObject; 5 import com.auth0.jwt.interfaces.Claim; 6 import com.auth0.jwt.interfaces.DecodedJWT; 7 import com.example.demo.model.dto.SPManager; 8 import com.example.demo.model.out.LoginData; 9 import com.example.demo.model.out.LoginResult; 10 import com.example.demo.model.out.Meta; 11 import com.example.demo.service.SPManagerService; 12 import com.example.demo.utils.JWTUtils; 13 14 import org.springframework.beans.factory.annotation.Autowired; 15 import org.springframework.web.bind.annotation.PostMapping; 16 import org.springframework.web.bind.annotation.RequestMapping; 17 import org.springframework.web.bind.annotation.RequestParam; 18 import org.springframework.web.bind.annotation.RestController; 19 20 import javax.servlet.http.HttpServletRequest; 21 import java.util.HashMap; 22 23 @RestController 24 public class ManagerController { 25 26 @Autowired 27 SPManagerService spManagerService; 28 29 @PostMapping("/login") 30 public JSONObject Login(@RequestParam String username, @RequestParam String password) { 31 System.out.println(username + "|" + password); 32 33 LoginData lt = new LoginData(); 34 Meta meta = new Meta(); 35 SPManager model = spManagerService.getManagerByNameAndPwd(username, password); 36 System.out.println(model); 37 if (model != null) { 38 HashMap<String, String> map = new HashMap<>(); 39 map.put("uid", model.getMgId().toString()); 40 map.put("rid", model.getRoleId().toString()); 41 String token = JWTUtils.getToken(map); 42 43 lt.setId(model.getMgId()); 44 lt.setRid(model.getRoleId()); 45 lt.setUsername(model.getMgName()); 46 lt.setEmail(model.getMgEmail()); 47 lt.setMobile(model.getMgMobile()); 48 lt.setToken(token); 49 50 meta.setMsg("登入成功"); 51 meta.setStatus(200); 52 }else{ 53 lt = null; 54 meta.setMsg("登入失敗"); 55 meta.setStatus(400); 56 } 57 LoginResult lr = new LoginResult(); 58 lr.setData(lt); 59 lr.setMeta(meta); 60 61 String json = JSON.toJSONString(lr); 62 JSONObject jsonObject = JSONObject.parseObject(json); 63 return jsonObject; 64 } 65 }
在攔截器JWTInterceptor中已經定義了對request頭[Authorization]中儲存的Token自動驗籤的處理的邏輯,請求只有通過了攔截器的驗證,才能進入到Controller中。這樣在具體的業務邏輯處理時就無需編寫重複的驗證邏輯了。