上周看了一下jwt以前公司的開發都是使用session共享的方法。現在主流的兩種方式一種是把登錄信息保存在服務器,另一種則是把信息保存在客戶端。在使用session 存儲的時候會遇到很多的問題,隨着項目越來越多工作量會變得越來越大。現在公司要開始一個新的項目,索性就開始使用jwt,數據保存在客戶端每一次請求都回傳給服務器驗證一下。
本文分為兩部分第一部分簡單介紹一下JWT,第二部分簡單介紹一下使用spring boot+jwt構建一個項目。
一、什么是JWT?
JWT全程JSON Web tokens 主要由 三部分組成通過“.”分割開,三部分分別是Header、Payload、Signature因此一個完成的JWT經典結構體應該是xxxx.yyyy.zzzzz
1.Header (頭)
頭部是一個包括了兩部分的JSON 一部分是簽名的算法(alg)通常使用HS256或者RSA,這里基本上都是使用HS256,還有一個部分是簽名類型(typ)即JWT
例如:
{ "alg": "HS256", "typ": "JWT" }
然后把這個JSON Base64Url,形成第一部分。
2.Payload (數據)
第二部分也是一個JSON對像,官方提供了幾個數據字段 iss (issuer):簽發人、exp (expiration time):過期時間、sub (subject):主題、aud (audience):受眾、nbf (Not Before):生效時間、iat (Issued At):簽發時間、jti (JWT ID):編號
除了官方字段還可以定義私有字段
如例:
{ "sub": "1234567890", "name": "John Doe", "admin": true }
然后和第一部分一樣使用Base64Url算法轉化一下,由於這部分直接是暴露出去的顧不應該放比較重要的數據。
3.Signature
Signature 部分是對前兩部分的簽名,防止數據篡改。
首先,需要指定一個密鑰(secret)。這個密鑰只有服務器才知道,不能泄露給用戶。然后,使用 Header 里面指定的簽名算法(默認是 HMAC SHA256),按照下面的公式產生簽名。
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
算出簽名以后,把 Header、Payload、Signature 三個部分拼成一個字符串,每個部分之間用"點"(.
)分隔,就可以返回給用戶。
關於jwt的介紹就到這里 ,參考了網上的其他人博客和JWT官網想要了解的更加詳細可以前去官網仔細閱讀一下。
二、構建demo
首先新建一個spring boot 項目 pom文件如下
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.7.0</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.58</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>
然后新建一個實體類user 包括了id,name,password和一個EntRespon用於網絡之間消息的傳輸。
public class User { private String id; private String name; private String password; public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } }
public class EntRespson { private int code; private String resultMsg; private Object data; public EntRespson() { this.code=0; } public int getCode() { return code; } public void setCode(int code) { this.code = code; } public String getResultMsg() { return resultMsg; } public void setResultMsg(String resultMsg) { this.resultMsg = resultMsg; } public Object getData() { return data; } public void setData(Object data) { this.data = data; } }
然后新建一個controller 先寫一個測試方法 test 先測試沒有問題 。然后我們需要完成一個JWT加密解密的方法,以下使我們的一個簡單的JWT加密解密的方法。
public class JwtUtils { public static final String TOKEN_HEADER = "Authorization"; public static final String TOKEN_PREFIX = "Bearer "; private static final String SECRET = "jwtdemo"; private static final String ISS = "echisan"; // 過期時間 private static final long EXPIRATION = 360000l; /** * 加密 jwt token * @param id * @return */ public static String encode(String id) { Algorithm algorithm = Algorithm.HMAC256(SECRET); String token = JWT.create() //設置過期時間為一個小時 .withExpiresAt(new Date(System.currentTimeMillis() + EXPIRATION)) //設置負載 .withJWTId(id) .sign(algorithm); return token; } /** * 解密 jwt toke * @param token * @return */ public static String decode(String token) { if (token == null || token.length() == 0) { throw new RuntimeException("token為空:" + token); } Algorithm algorithm = Algorithm.HMAC256(SECRET); JWTVerifier jwtVerifier = JWT.require(algorithm).build(); DecodedJWT decodedJWT = jwtVerifier.verify(token); return decodedJWT.getId(); } }
由於我們這demo沒有連接數據庫所以我把用戶信息寫在配置文件里面了 application.properties 如下
demo.name=lee demo.password=123 demo.id=1
然后我們完成一個讀取配置文件的類
@Configuration @PropertySource("classpath:application.properties") public class UserConfig { @Value("${demo.name}") private String name; @Value("${demo.password}") private String password; @Value("${demo.id}") private String id; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getId() { return id; } public void setId(String id) { this.id = id; } }
這個時候完成controller 里面的登錄方法,在登錄完成后 生成一個token返還給前端這個就是一個獲取受保護的憑證。
@RestController public class CtrlUser { @Autowired private UserConfig config; @RequestMapping("test1") public String test() { return "ok test" + config.getName(); } @RequestMapping(value = "login", method = RequestMethod.POST) public EntRespson login(@RequestBody User user) { EntRespson entRespson = new EntRespson(); try { String usename = config.getName(); String pwds = config.getPassword(); if (!(user.getName().equals(usename) && user.getPassword().equals(pwds)))throw new Exception("賬戶密碼錯誤"); //登錄成功獲得token String token = JwtUtils.encode(config.getId()); JSONObject jsonObject = new JSONObject(); jsonObject.put("token",token); entRespson.setData(jsonObject); } catch (Exception e) { entRespson.setCode(-1); entRespson.setResultMsg(e.getMessage()); } return entRespson; } }
現在我們還是完成一個簡單的攔截器的功能。
public class Interceptor implements HandlerInterceptor { @Autowired private UserConfig config; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token = request.getHeader("token");// HttpServletRequest 請求頭中取出 token if (!(handler instanceof HandlerMethod)) { return true; } // HandlerMethod handlerMethod = (HandlerMethod) handler; // Method method = handlerMethod.getMethod(); // //檢查是否有passtoken注釋,有則跳過認證 // if (method.isAnnotationPresent(SkipToken.class)) { // SkipToken passToken = method.getAnnotation(SkipToken.class); // if (passToken.required()) { // return true; // } // } String id = JwtUtils.decode(token); if (id.equals(config.getId())) return true; return false; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { } }
把攔截器添加到配置類中
@Configuration public class InterceptorConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(getInterceptor()) .addPathPatterns("/**") //配置攔截所有請求 .excludePathPatterns("/login"); // 不攔截登錄請求。 } @Bean public Interceptor getInterceptor(){ return new Interceptor(); } }
好了 到此為止我們的demo已經完成了我們開始測試我們是demo。
啟動完成之后我們先試試127.0.0.1:8080/test1
這里可以看到我們請求被拒絕了 ,然后我們測試一下登錄接口。
到這里我們已經獲取到了token,前端在獲取了token之后前端是邏輯是需要在每一次請求的Headers里面增加一個token,我們在上一個測試接口里面加上token重新請求一下
好的在這里我們已經能看到了我們帶有token的請求已經訪問成功了。
最后附上源碼地址:https://github.com/llcin/spring_tool/tree/master/mydemo