JWT实战

时间:2023-03-09 14:22:26
JWT实战
JWT实战
2018年03月02日 22:36:21
阅读数:129

JWT认证流程

先来回顾下JWT的流程,jwt是存储在客户端的,服务器不需要存储jwt;客户端每次发送请求时携带token,然后到服务端验证token是否正确,是否过期,然后解码出携带的用户信息。

存在的问题 
1、Token失效问题: 
比如在浏览器端通过用户名/密码验证获得签名的Token被木马窃取。即使用户登出了系统,黑客还是可以利用窃取的Token模拟正常请求可用它访问服务器,而服务器端对此完全不知道,(因为JWT机制是无状态的),直到过期,中间服务器无法控制它.

2、 app类Token的有效时间

    token的有效时间:

        1. 如果 app 是新闻类/游戏类/聊天类等需要长时间用户粘性的. 一般可以设置1年的有效时间!

        2. 如果 app 是 支付类/银行类的. 一般token只得有效时间比较短: 15分钟左右!
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

我们的解决方法是: 
服务端使用Redis缓存服务器签发给已登录用户的Token, 
每次客户端发送请求时到redis中查该用户 请求的token 和 redis存的token是否一致,不一致不允许token登录, 
如果一致,判断这个token是否可以用(主要防止修改密码和注销操作的token没失效问题) 
最后返回用户信息 
当用户修改密码和注销时直接将redis中该用户的Token设置失效。下次通过token登录,会提醒token失效,要重新登录,我们重新生成一个新的token给用户。通过redis存储token,实现主动控制 token过期失效的问题了。

封装的JWT工具类 部分代码 
生成Token码

  /**
* 生成jwt token
* @param userId
* @param expireTime 过期时间戳
* @return
*/
public static String makeToken(String userId,Date expireTime){
long nowMillis = System.currentTimeMillis();
Date now=new Date(nowMillis);//签发时间精度:毫秒
try {
Algorithm algorithm = Algorithm.HMAC256(MyConstance.JWT_SECRET);
return JWT.create().withIssuer(userId).withIssuedAt(now).withExpiresAt(expireTime).sign(algorithm);
} catch (UnsupportedEncodingException exception){
exception.printStackTrace();
} catch (JWTCreationException exception){
exception.printStackTrace();
}
return null;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

解码和验证Token码


/**
* 校验toekn是否有效
* @param userId
* @param token
* @return
*/
public static boolean verifyToken(String userId,String token){
boolean active = true;
try {
Algorithm algorithm = Algorithm.HMAC256(MyConstance.JWT_SECRET);//声明签名所用的算法和秘钥
JWTVerifier verifier = JWT.require(algorithm).withIssuer(userId).build();
verifier.verify(token);
} catch (TokenExpiredException exception){
//System.out.println("--- token 过期");
active = false;
} catch (JWTDecodeException exception){
//System.out.println("--- token 无效");
active = false;
} catch (UnsupportedEncodingException exception){
//System.out.println("--- token 无效");
active = false;
} catch (JWTVerificationException exception){
//System.out.println("--- token 错误");
active = false;
}
return active;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

登录

@ApiOperation(value = "登录")
@RequestMapping(value = "/loginWEB", method = RequestMethod.POST)
public ResponseEntity<BaseResult> loginWEB(HttpServletRequest request, HttpServletResponse response,
@ApiParam("用户名") @RequestParam(value = "username", required = false) String username,
@ApiParam("密码") @RequestParam(value = "password", required = false) String password) { XkbbUser user=userService.getBy("username", username);
//账号密码校验业务
if(user==null) {
return buildFailedInfo(ApiConstance.USER_NOT_EXIST);
}
if(!password.equals(user.getPassword())) {
return buildFailedInfo(ApiConstance.PASSWORD_ERROR);
}
//获取用户id
String userId=user.getId();
//设置token过期时间为30天之后
Date expireTime = Tool.datePlu(new Date(), 30);
//生成JWT(JSon Web Token)
String token=TokenUtil.makeToken(userId, expireTime);
//更新缓存里面该用户的token:
// 如果已登录,则使其Token失效
TokenUtil.updateTokenAPP(userId, token);
Map<String, Object> map = new HashMap<String, Object>();
map.put("token", token);//服务器生成的token
return buildSuccessInfo(map);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

MyConstance类部分全局常量定义 
/** 
* token的缓存map名 
*/ 
public static final String KEY_TOKEN_MAP = “key_token_map”; 
/** 
* JWT生成token时加密用的secret 
*/ 
public static final String JWT_SECRET = “3MZq0BYyGcXYoXjhS4QbAM+2YdlLCwKRr2gvVJOJ”;

/**
* 用户登录校验
* @param userId
* @param token
* @return
*/
//到redis中查该用户 请求的token 和 redis存的token是否一致,不一致不允许token登录,
//再次根据创建时间,判断这个token是否可以用(主要防止出现修改密码和注销操作的token没失效问题)
//失效则Redis中删除该token
public static boolean isLogin(String userId,String token){
if(Tool.isNotBlank(userId)) {
if(Tool.isNotBlank(token)) {
//到redis中查该用户 请求的token 和 redis存的token是否一致,不一致不允许token登录
String existValue = (String) CacheUtil.hget(MyConstance.KEY_TOKEN_MAP, userId);
//System.out.println("existValue:"+existValue);
if(Tool.isNotBlank(existValue) && existValue.equals(token)) {
//再次根据创建时间,判断这个token是否可有效(主要防止出现修改密码和注销操作的token没失效问题)
boolean isLogin = verifyToken(userId,token);
//失效则Redis中删除该token
if(!isLogin){
CacheUtil.hdel(MyConstance.KEY_TOKEN_MAP,userId);
}
return isLogin;
}
}
}
return false;
} /**
* 清除用户缓存token
* @param userId
* @return
*/
public static String clearToken(String userId){ CacheUtil.hdel(MyConstance.KEY_TOKEN_MAP, userId); } /**
* 更新用户token
* @param userId
* @param value
*/
public static void updateToken(String userId,String value){
clearToken(userId);//清除缓存中旧的Token
CacheUtil.hset(MyConstance.KEY_TOKEN_MAP, userId, value);//缓存新的Token }