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的拼装我并不了解,也不擅长所以就耽搁了。