1 @RestController 2 @RequestMapping("/say") 3 public class HelloController { 4 5 @JwtValidation 6 @RequestMapping(value = {"/hello/{name}"}, method = RequestMethod.GET) 7 public ResponseEntity<?> hello(@PathVariable("name") String name) { 8 9 return new ResponseEntity<HelloModel>(new HelloModel(name), HttpStatus.OK); 10 } 11 }
1 @Data 2 public class HelloModel implements Serializable { 3 private String sentence; 4 5 public HelloModel() { } 6 7 public HelloModel(String name) { 8 this.sentence = "Hello " + name; 9 } 10 }
1 @Retention(RetentionPolicy.RUNTIME) 2 @Target(ElementType.METHOD) 3 public @interface JwtValidation { 4 }
spring AOP 不是這裡說明的重點,所以,直接看 @AspectJ 裡的程式怎麼檢查 JWT token。
1 @Aspect 2 @Log4j 3 public class JwtValidator { 4 @Value("${jwt.key}") 5 protected String key; 6 7 @Inject 8 protected HttpServletRequest httpRequest; 9 10 //@Around("execution(* idv.steven.demo.rest.controller.*.*(..))") 11 @Around("@annotation(idv.steven.demo.JwtValidation)") 12 public ResponseEntity<?> validate(ProceedingJoinPoint jp) { 13 String token = httpRequest.getHeader("Authorization"); 14 15 if (!StringUtils.isEmpty(token)) { 16 try { 17 token = token.replace("Bearer ", ""); 18 19 //當 client 與 server 的 key 不同,會產生 exception。 20 Jws<Claims> jws = Jwts.parser() 21 .setSigningKey(DatatypeConverter.parseBase64Binary(key)) 22 .parseClaimsJws(token); 23 24 Claims body = jws.getBody(); //payload 25 Date issuedAt = body.getIssuedAt(); 26 27 if (issuedAt == null) { 28 return new ResponseEntity<ResponseModel>(new ResponseModel(), HttpStatus.REQUEST_TIMEOUT); 29 } 30 else { 31 //由 client 發出 request 到 server 端,最多可以歷經 30 秒! (各機器不一定有校時,要有一點容許誤差!) 32 int diffSec = DateUtil.diffSec(issuedAt); 33 if (diffSec > 30) { 34 return new ResponseEntity<ResponseModel>(new ResponseModel(), HttpStatus.REQUEST_TIMEOUT); 35 } 36 } 37 } 38 catch (Exception ex) { 39 log.error(ex.getMessage(), ex); 40 return new ResponseEntity<ResponseModel>(new ResponseModel(), HttpStatus.UNAUTHORIZED); 41 } 42 } 43 else { 44 log.error("JWT token not found"); 45 return new ResponseEntity<ResponseModel>(new ResponseModel(), HttpStatus.UNAUTHORIZED); 46 } 47 48 try { 49 return (ResponseEntity<?>) jp.proceed(); 50 } catch (Throwable e) { 51 return new ResponseEntity<ResponseModel>(new ResponseModel(), HttpStatus.SERVICE_UNAVAILABLE); 52 } 53 } 54 }
第 4、5 行,這是 spring framework 提供的功能,spring 會從設定檔 (application.properties 或其它 properties 檔) 載入 jwt.key 的值到 key 這個變數。當然,我在某個標有 @Configuration 的類別前會有 @PropertySource("classpath:application.properties") 這樣的標注。
第 13 行,從 http 的 header 中的 Authorization 取出 JWT token,還記得上一篇有提到,在 token 前會有 "Bearer " 的字串,在第 17 行將它移除,第 20 ~ 22 行是用 key 驗證 token 中的 header、payload 是否被竄改,如果沒有,將所有欄位的值放到 jws 那個變數裡,方便程式人員處理,這裡使用的是 JJWT 這個 open source,算是目前比較流行的。在 24 ~ 46 行就是在檢查 token 裡的一些值,不正確回覆非 HttpStatus.OK (200) 的值,這裡其實只檢查有沒有逾時。
接下來看一下測試程式怎麼寫 …
1 @Test 2 public void hello() { 3 String url = "http://localhost:8080/demo/say/hello/Steven"; 4 5 Date iat = Calendar.getInstance().getTime(); 6 7 String compactJws = Jwts.builder() 8 .setIssuedAt(iat) 9 .signWith(SignatureAlgorithm.HS512, DatatypeConverter.parseBase64Binary(key)) 10 .compact(); 11 12 HttpHeaders headers = new HttpHeaders(); 13 headers.set("Authorization", "Bearer " + compactJws); 14 15 JSONObject parm = new JSONObject(); 16 HttpEntity<JSONObject> entity = new HttpEntity<JSONObject>(parm, headers); 17 18 try { 19 ResponseEntity<?> response = rest.exchange(url, HttpMethod.GET, entity, HelloModel.class); 20 HelloModel body = (HelloModel) response.getBody(); 21 System.out.println(body.getSentence()); 22 System.out.println("status code = " + response.getStatusCode()); 23 } 24 catch (HttpClientErrorException ex) { 25 System.out.println("status code = " + ex.getStatusCode()); 26 } 27 }
沒有留言:
張貼留言