JWT json web token
Json web token (JWT), 是為了在網絡應用環境間傳遞聲明而執行的一種基於JSON的開放標准(
(RFC 7519).該token被設計為緊湊且安全的,特別適用於分布式站點的單點登錄(SSO)場景。JWT的聲明一般被用來在身份提供者和服務提供者間傳遞被認證的用戶身份信息,以便於從資源服務器獲取資源,也可以增加一些額外的其它業務邏輯所必須的聲明信息,該token也可直接被用於認證,也可被加密。
綜合了網上一些現有帖子寫了一個工具類和攔截器
工具類
View Code
1 public class JWTUtil { 2 //密匙 3 private static final String TOKEN_SECRET = "privateKey"; 4 5 /** 6 * 創建jwt 7 * @param subject {id:100,name:xiaohong} 用戶id,用戶的name,用戶的pasword都可以 8 * @param liveMillis 存活時間 9 * @return jwt 10 */ 11 12 public static String setToken(String subject, 13 long liveMillis ){ 14 //生成隨機的32位的jwtId 15 String jwtId= UUID.randomUUID().toString().replace("-", "").toUpperCase(); 16 //指定簽名的時候使用的加密算法,就是header 17 SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256; 18 //設置簽證頒發時間 19 long nowMillis = System.currentTimeMillis(); 20 Date now = new Date(nowMillis); 21 //創建payload的私有聲明 22 Map<String,Object> claims = new HashMap<String,Object>(); 23 claims.put("uid", "DSSFAWDWADAS..."); 24 claims.put("user_name", "admin"); 25 claims.put("nick_name","DASDA121"); 26 //創建JWT 27 JwtBuilder builder = Jwts.builder().setClaims(claims) 28 //設置jwt的id 29 .setId(jwtId) 30 //設置簽證時間 31 .setIssuedAt(now) 32 //設置這個jwt的持有人,譬如加上持有人的id 33 .setSubject(subject) 34 //設置簽名算法以及密匙 35 .signWith(signatureAlgorithm, TOKEN_SECRET); 36 //設置過期時間 37 if(liveMillis >= 0){ 38 long endMillis = nowMillis + liveMillis; 39 Date end = new Date(endMillis); 40 builder.setExpiration(end); 41 } 42 return builder.compact(); 43 } 44 45 /** 46 * 解析jwt,變成你能驗證的樣子 47 * @param jwt 48 * @return 49 */ 50 public static Claims parsJwt(String jwt){ 51 Claims claims = null; 52 try { 53 claims = Jwts.parser() 54 .setSigningKey(TOKEN_SECRET) 55 .parseClaimsJws(jwt).getBody(); 56 } catch (ExpiredJwtException e) { 57 System.out.println("Token已過期"); 58 } catch (UnsupportedJwtException e) { 59 System.out.println("Token格式錯誤"); 60 } catch (MalformedJwtException e) { 61 System.out.println("Token沒有被正確構造"); 62 } catch (SignatureException e) { 63 System.out.println("簽名失敗"); 64 } catch (IllegalArgumentException e) { 65 System.out.println("非法參數異常"); 66 } 67 return claims; 68 } 69 70 /** 71 *自定義想要驗證的東西,你存在subject里面的東西。 72 * @param token 73 * @return 74 */ 75 public static Boolean isToken(String subject, String token){ 76 //獲取解析后的屬性體 77 try { 78 Claims claims = null; 79 claims = parsJwt(token); 80 //下面判斷這個token除了格式之外你想判斷的東西。測試自定義的subject為"{id:100,name:xiaohong}" 81 if( subject.equals(claims.getSubject())){ 82 return true; 83 }else{ 84 return false; 85 } 86 } catch (Exception e) { 87 return false; 88 } 89 90 } 91 92 }
攔截器
1 @Component 2 public class JwtFilter implements HandlerInterceptor { 3 private static String[] IGNORE_URL = {"/user/login"}; 4 private static Logger log = LoggerFactory.getLogger(JwtFilter.class); 5 @Override 6 public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { 7 System.out.println("-----------------開始進行地址攔截------------------------"); 8 String token = request.getHeader("token"); 9 String username = String.valueOf(GetRequestBody.get(request, "username")); 10 if(!"".equals(token) && token != null){ 11 if(!JWTUtil.isToken(username, token)){ 13 return false; 14 } 15 } 16 17 return true; 18 } 19 @Override 20 public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception { 21 22 } 23 @Override 24 public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception { 25 26 } 27 }
controller使用情況
1 @RequestMapping("/user") 2 public class UserController { 3 private static final long TOKEN_LIVE = 15 * 60 * 1000; 4 @Autowired 5 private UserService userService; 6 @Autowired 7 private RedisUtil redisUtil; 8 9 /** 10 * 返回兩個參數 0 ,1 1表示驗證通過,0 表示驗證失敗 11 * @param username 12 * @param password 13 * @param request 14 * @param response 15 * @return 16 */ 17 @RequestMapping("/login") 18 public R login(String username , String password, HttpServletRequest request, HttpServletResponse response) throws IOException { 19 User user = null; 20 List<User> list = userService.sel(username, null); 21 if(list.size() == 0 || list.isEmpty()){ 22 return R.ok("0"); 23 } 24 System.out.println(request.getHeader("token")); 25 if(redisUtil.get(username) == null){ 26 user = list.get(0); 27 System.out.println("數據庫查詢"); 28 if(user != null && user.getPassword().equals(password)){ 29 redisUtil.set(username, user); 30 redisUtil.expire(username, 15 * 60 * 1000); 31 response.setHeader("token", JWTUtil.setToken(username, 5 * 1000)); 32 System.out.println(JWTUtil.setToken(username, 5 * 1000)); 33 return R.ok("1"); 34 }else{ 35 return R.ok("0"); 36 } 37 }else{ 38 user = (User) redisUtil.get(username); 39 System.out.println("緩存查詢"); 40 if(user != null && user.getPassword().equals(password)){ 41 return R.ok("1"); 42 }else{ 43 return R.ok("0"); 44 } 45 } 46 } 47 }
結合了緩存 來做,在這里也把緩存工具類放上

1 import org.springframework.beans.factory.annotation.Autowired; 2 import org.springframework.data.redis.core.RedisTemplate; 3 import org.springframework.stereotype.Component; 4 5 6 import java.util.List; 7 import java.util.Map; 8 import java.util.Set; 9 import java.util.concurrent.TimeUnit; 10 11 @Component 12 public class RedisUtil { 13 @Autowired 14 private RedisTemplate redisTemplate; 15 //===================================common================================== 16 17 /** 18 * 指定緩存過期時間 19 * @param key 主鍵 20 * @param time 過期時間 秒 21 * @return 22 */ 23 public boolean expire(String key, long time){ 24 try{ 25 if(time > 0){ 26 redisTemplate.expire(key, time, TimeUnit.SECONDS); 27 } 28 return true; 29 }catch(Exception e){ 30 e.printStackTrace(); 31 return false; 32 } 33 } 34 35 /** 36 * 獲取過期時間 37 * @param key 主鍵 38 * @return long時間 39 */ 40 public long getExpire(String key){ 41 return redisTemplate.getExpire(key); 42 } 43 44 /** 45 * 判斷key是否存在 46 * @param key 47 * @return 48 */ 49 public boolean hasKey(String key){ 50 try{ 51 return redisTemplate.hasKey(key); 52 }catch (Exception e){ 53 return false; 54 } 55 } 56 //============================String====================================== 57 58 /** 59 * 存入緩存 60 * @param key 61 * @param value 62 * @return 63 */ 64 public boolean set(String key, Object value){ 65 try{ 66 redisTemplate.opsForValue().set(key, value); 67 return true; 68 }catch(Exception e){ 69 e.printStackTrace(); 70 return false; 71 } 72 } 73 /** 74 * 刪除緩存 75 * 未完成!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 76 * @param key 主鍵可以單個或者集合 77 */ 78 public void del(String... key){ 79 if(key.length > 0 && key != null){ 80 if(key.length == 1 ){ 81 redisTemplate.delete(key); 82 }else{ 83 redisTemplate.delete(key); 84 } 85 } 86 } 87 88 /** 89 * 獲取緩存 90 * @param key 91 * @return 92 */ 93 public Object get(String key){ 94 return key == null ? null : redisTemplate.opsForValue().get(key); 95 } 96 97 /** 98 * 自增 99 * @param key 主鍵 100 * @param delta 增加多少(>0) 101 * @return 102 */ 103 public long incr(String key, long delta){ 104 if(delta < 0){ 105 throw new RuntimeException("遞增因子必須大於零!"); 106 }else{ 107 return redisTemplate.opsForValue().increment(key, delta); 108 } 109 } 110 111 /** 112 * 遞減 113 * @param key 主鍵 114 * @param delta 遞減多少(>0) 115 * @return 116 */ 117 public long decr(String key, long delta){ 118 if(delta < 0){ 119 throw new RuntimeException("遞減因子必須大於零!"); 120 }else{ 121 return redisTemplate.opsForValue().decrement(key, -delta); 122 } 123 } 124 //==============================hash============================================ 125 126 /** 127 * HashGet 128 * @param key 鍵 不為 null 129 * @param item 項 不為 null 130 * @return 131 */ 132 public Object hGet(String key, String item){ 133 return redisTemplate.opsForHash().get(key, item); 134 } 135 136 /** 137 * 獲取hashkey對應的所有key 138 * @param key 139 * @return 140 */ 141 public Map<Object, Object> hmGet(String key){ 142 return redisTemplate.opsForHash().entries(key); 143 } 144 145 /** 146 * hashset 147 * @param key 鍵 148 * @param map 對應的多個鍵值 149 * @return 150 */ 151 public boolean hSet(String key, Map<String, Object> map){ 152 try{ 153 redisTemplate.opsForHash().putAll(key, map); 154 return true; 155 }catch(Exception e){ 156 e.printStackTrace(); 157 return false; 158 } 159 } 160 161 /** 162 * hashset,並設置過期時間 163 * @param key 鍵 164 * @param map 對應的多個鍵 165 * @param time 過期時間 166 * @return 167 */ 168 public boolean hmSet(String key, Map<String, Object> map, long time){ 169 try{ 170 redisTemplate.opsForHash().putAll(key, map); 171 if(time > 0){ 172 expire(key, time); 173 } 174 return true; 175 }catch(Exception e){ 176 e.printStackTrace(); 177 return false; 178 } 179 } 180 181 /** 182 * 向緩存hash表中插入數據,如果表不存在則創建這個表 183 * @param key 鍵 184 * @param item map 鍵 185 * @param value map值 186 * @return 187 */ 188 public boolean hSet(String key, String item, Object value){ 189 try{ 190 redisTemplate.opsForHash().put(key, item, value); 191 return true; 192 }catch(Exception e){ 193 e.printStackTrace(); 194 return false; 195 } 196 } 197 198 /** 199 * 向緩存中的map存入數據,如果map不存在則創建這個表,同時設置過期時間 200 * @param key 鍵 201 * @param item map鍵 202 * @param value value 203 * @param time 過期時間,如果這個map已經有過期時間,則會重置這個過期時間 204 * @return 205 */ 206 public boolean hSet(String key, String item, Object value, long time){ 207 try{ 208 redisTemplate.opsForHash().put(key, item, value); 209 if(time > 0){ 210 expire(key, time); 211 } 212 return true; 213 }catch(Exception e){ 214 e.printStackTrace(); 215 return false; 216 } 217 } 218 219 /** 220 * hash刪除數據 221 * @param key 鍵 不為null 222 * @param values 值 不為null 223 */ 224 public void hDel(String key, Object... values){ 225 redisTemplate.opsForHash().delete(key, values); 226 } 227 228 /** 229 *判斷是否含有某個鍵 230 * @param key 不為null 231 * @param item 不為null 232 * @return 233 */ 234 public boolean hHasKey(String key, String item){ 235 return redisTemplate.opsForHash().hasKey(key, item); 236 } 237 238 /** 239 * hash遞增,如果不存在則創建,並把新增的值返回 240 * @param key 鍵 241 * @param item map鍵 242 * @param by 增加多少(>0) 243 * @return 244 */ 245 public double hIncr(String key, String item, double by){ 246 return redisTemplate.opsForHash().increment(key, item, by); 247 } 248 249 /** 250 * hash遞減不存在就創建,並把新增的值返回 251 * @param key 鍵 252 * @param item map鍵 253 * @param by 遞減因子(>0) 254 * @return 255 */ 256 public double hDecr(String key, String item, double by){ 257 return redisTemplate.opsForHash().increment(key, item, -by); 258 } 259 //================================set===================================== 260 261 /** 262 * 獲取key的所有set 263 * @param key 264 * @return 265 */ 266 public Set<Object> sGet(String key){ 267 try{ 268 return redisTemplate.opsForSet().members(key); 269 }catch(Exception e){ 270 e.printStackTrace(); 271 return null; 272 } 273 } 274 275 /** 276 * 判斷key值是否含有value 277 * @param key 278 * @param value 279 * @return 280 */ 281 public boolean sHasKey(String key, Object value){ 282 try{ 283 return redisTemplate.opsForSet().isMember(key, value); 284 }catch(Exception e){ 285 e.printStackTrace(); 286 return false; 287 } 288 } 289 290 /** 291 *將數據存入set緩存中 292 * @param key 鍵 293 * @param values 值 294 * @return 成功個數 295 */ 296 public long sSet(String key, Object... values){ 297 try{ 298 return redisTemplate.opsForSet().add(key, values); 299 }catch(Exception e){ 300 e.printStackTrace(); 301 return 0; 302 } 303 } 304 305 /** 306 * 向緩存中添加數據,並設置過期時間,如果已經設置過期時間則重置 307 * @param key 鍵 308 * @param time 過期時間 309 * @param values 值 310 * @return 成功個數 311 */ 312 public long sSetAndTime(String key, long time, Object... values){ 313 try{ 314 long count = redisTemplate.opsForSet().add(key,values); 315 if(time > 0){ 316 expire(key, time); 317 } 318 return count; 319 }catch(Exception e){ 320 e.printStackTrace(); 321 return 0; 322 } 323 } 324 325 /** 326 * 獲取set的長度 327 * @param key 328 * @return 329 */ 330 public long sGetSetSize(String key){ 331 try{ 332 return redisTemplate.opsForSet().size(key); 333 }catch(Exception e){ 334 e.printStackTrace(); 335 return 0; 336 } 337 } 338 339 /** 340 * 移除值為value的 341 * @param key 鍵 342 * @param values 值 可以是多個 343 * @return 移除的個數 344 */ 345 public long sRemove(String key, Object... values){ 346 try{ 347 long count = redisTemplate.opsForSet().remove(key, values); 348 return count; 349 }catch(Exception e){ 350 e.printStackTrace(); 351 return 0; 352 } 353 } 354 //=====================list=============================== 355 356 /** 357 * 獲取list緩存 358 * @param key 鍵 359 * @param start 開始 360 * @param end 結束 0 到 -1 所有值 361 * @return 362 */ 363 public List<Object> lGet(String key, long start, long end){ 364 try{ 365 return redisTemplate.opsForList().range(key, start, end); 366 }catch(Exception e){ 367 e.printStackTrace(); 368 return null; 369 } 370 } 371 372 /** 373 * 獲取list的長度 374 * @param key 鍵 375 * @return list長度 376 */ 377 public long lGetListSize(String key){ 378 try{ 379 return redisTemplate.opsForList().size(key); 380 }catch(Exception e){ 381 e.printStackTrace(); 382 return 0; 383 } 384 } 385 386 /** 387 * 通過index索引來獲取value 388 * @param key 鍵 389 * @param index index 0 是第一個元素,-1是最后一個元素,-2是倒數第二個元素 390 * @return 391 */ 392 public Object lGetIndex(String key, long index){ 393 try{ 394 return redisTemplate.opsForList().index(key, index); 395 }catch(Exception e){ 396 e.printStackTrace(); 397 return null; 398 } 399 } 400 401 /** 402 * 向list中插入數據 403 * @param key 鍵 404 * @param value 值 405 * @return 406 */ 407 public boolean lSet(String key, Object value){ 408 try{ 409 redisTemplate.opsForList().rightPush(key, value); 410 return true; 411 }catch(Exception e){ 412 e.printStackTrace(); 413 return false; 414 } 415 } 416 417 /** 418 * 向list中插入value 419 * @param key 鍵 420 * @param value 值 421 * @param time 過期時間 422 * @return 423 */ 424 public boolean lSetAndTime(String key, Object value, long time){ 425 try{ 426 redisTemplate.opsForList().rightPush(key, value); 427 if(time > 0){ 428 expire(key, time); 429 } 430 return true; 431 }catch(Exception e){ 432 e.printStackTrace(); 433 return false; 434 } 435 } 436 437 /** 438 * 重寫方法,設置list 439 * @param key 440 * @param list 441 * @return 442 */ 443 public boolean lSet(String key, List<Object> list){ 444 try{ 445 redisTemplate.opsForList().rightPush(key, list); 446 return true; 447 }catch(Exception e){ 448 e.printStackTrace(); 449 return false; 450 } 451 } 452 453 /** 454 * 插入list並設置過期時間 455 * @param key 鍵 456 * @param list value 457 * @param time 過期時間 458 * @return 459 */ 460 public boolean lSetAndTime(String key, List<Object> list, long time){ 461 try{ 462 redisTemplate.opsForList().rightPush(key,list); 463 if(time > 0){ 464 expire(key, time); 465 } 466 return true; 467 }catch(Exception e){ 468 e.printStackTrace(); 469 return false; 470 } 471 } 472 473 /** 474 * 更新數據 475 * @param key 476 * @param index 477 * @param value 478 * @return 479 */ 480 public boolean lUpdateIndex(String key, long index, Object value){ 481 try{ 482 redisTemplate.opsForList().set(key, index, value); 483 return true; 484 }catch(Exception e){ 485 e.printStackTrace(); 486 return false; 487 } 488 } 489 490 /** 491 * 移除count個值是value的 492 * @param key 鍵 493 * @param count 移除數量 494 * @param value 值 495 * @return 496 */ 497 public long lRemove(String key, long count, Object value){ 498 try{ 499 long num = redisTemplate.opsForList().remove(key, count, value); 500 return num; 501 }catch(Exception e){ 502 e.printStackTrace(); 503 return 0; 504 } 505 } 506 }
遇到的問題
1.上手是邏輯混亂,不知道token的頒發和判斷在哪里發生
后來確定 controller負責token的頒發,攔截器來判斷token是否合法和過期情況。
2.我把user.username屬性作為判斷token構造的一個屬性,但是我不知道怎么在request的body中獲取這個username屬性
對此我直接封裝了一個方法,把request和你想要的屬性當作參數傳入,然后獲取參數的屬性。
1 import javax.servlet.http.HttpServletRequest; 2 import java.util.Enumeration; 3 import java.util.HashMap; 4 import java.util.Map; 5 6 public class GetRequestBody { 7 public static Object get(HttpServletRequest request, String string) { 8 Map<String,Object> map = new HashMap<String,Object>(); 9 Enumeration paramNames = request.getParameterNames(); 10 while (paramNames.hasMoreElements()) { 11 String paramName = (String) paramNames.nextElement(); 12 13 String[] paramValues = request.getParameterValues(paramName); 14 if (paramValues.length >0) { 15 String paramValue = paramValues[0]; 16 if (paramValue.length() != 0) { 17 map.put(paramName, paramValue); 18 } 19 } 20 } 21 return map.get(string); 22 23 } 24 25 }
3.我認為user第一次驗證的時候token都為空,所以當token為空的時候我選擇了直接放行,但是總覺得不是這么回事,所以我覺得這是一個很大的問題。我在想要不要定義一個統一的默認token當user第一次來的時候帶着這個token來進行判斷。
但是前端token的拼裝我並不了解,也不擅長所以就耽擱了。