一,提供一个login接口获取token:
1,通过jwt的方式生成token:
private static SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS512;
//用于进行签名的秘钥
private static String SECRET = "QWEQDADW";
/**
* 生成加密解密key
*
* @return
*/
private static Key deserializeKey() {
byte[] decodedKey = Base64.getDecoder().decode(SECRET);
Key key = new SecretKeySpec(decodedKey, TokenUtils.signatureAlgorithm.getJcaName());
return key;
}
/**
* 生成token信息.
*
* @param claims 数据声明(Claim)其实就是一个Map,比如我们想放入用户名,
* 可以简单的创建一个Map然后put进去
* @return
* @throws Exception
*/
private static String generateToken(Map<String, Object> claims, Boolean setExpiration) throws Exception {
Key key = deserializeKey();
//设置过期时间为10分钟
Date ecpiration = new Date(System.currentTimeMillis() + 600000L);
JwtBuilder jwtBuilder = Jwts.builder();
jwtBuilder.setClaims(claims);
if (setExpiration) {
jwtBuilder.setExpiration(ecpiration);
}
jwtBuilder.signWith(SignatureAlgorithm.HS512, key); //采用什么算法是可以自己选择的,不一定非要采用HS512
String result = jwtBuilder.compact();
return result;
}
/**
* 解析token信息.
*
* @param token 要解析的token信息
* @return
* @throws Exception
*/
public static Optional<Claims> getClaimsFromToken(String token) {
Claims claims;
Key key = deserializeKey();
try {
claims = Jwts.parser()
.setSigningKey(key)
.parseClaimsJws(token)
.getBody();
return Optional.of(claims);
} catch (Exception e) {
return Optional.empty();
}
}
/**
* 验证token是否过期
*
* @param tooken 要解析的token信息
* @return true 表示过期,false表示不过期,如果没有设置过期时间,则也不认为过期
* @throws Exception
*/
private static boolean isExpired(String tooken) throws Exception {
Optional<Claims> claims = getClaimsFromToken(tooken);
if (claims.isPresent()) {
Date expiration = claims.get().getExpiration();
return expiration.before(new Date());
}
return true;
}
/**
* 获取tooken中的参数值
*
* @return
* @throws Exception
*/
private static Map<String, Object> extractInfo(String token) throws Exception {
Optional<Claims> claims = getClaimsFromToken(token);
if (claims.isPresent()) {
Map<String, Object> info = new HashMap<String, Object>();
Set<String> keySet = claims.get().keySet();
//通过迭代,提取token中的参数信息
Iterator<String> iterator = keySet.iterator();
while (iterator.hasNext()) {
String key = iterator.next();
Object value = claims.get().get(key);
info.put(key, value);
}
return info;
}
return null;
}
public static String getToken(Map map){
try {
map.put("CREATE_TIME",System.currentTimeMillis());
return generateToken(map,false);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
2,在获取token时,将有效的token 以key-value的形式key:token,value:用户信息 放入redis,此token 过期时间稍微长点1-2h都行(根据实际业务设置token有效期)。
二,网关验证:
1,将token放在请求头或者cookie中。
2,拦截获取token,获取到的token,去redis验证是否存在此token,存在,使用用户信息验证此用户是否是有效合法的。
3,提取token中用户信息。
4,验证是否需要刷新token,这里利用用户id在本地缓存获取上一次调用的时间与当前时间对比。
5,利用当前时间比较上一次调用的时间差大于等于两分钟(此处允许两分钟,是考虑到当前的这个请求没有正常返回token的情况,这样刷新前的token 才继续有效使用,不然会要求用户进行登陆(无痕就不存在了))就进行刷新token。
6,token刷新(生成新的token)后紧接着会将上个token从redis中移除,并将这个新token ,set到redis中,反之不会生成新token,也不会将token从redis中删除。
7,将有效的token 通过response.addheaders("token",token)的方式返回给前端,前端每次从response header中获取有效的token,进行下一次调用的有效token来源(通过此方式传递token 的好处在于不需要暴露刷新接口,可以避免被恶意调用刷新token进行攻击)。
codeing:
@Slf4j
@Component
public class LoginInterceptor extends HandlerInterceptorAdapter {
@Autowired
private UserService UserService;
@Autowired
private RedisCache redisCache;
private Map<Long, User> cacheMap = new HashMap();
private Map<Long ,Long> cacheExpirationTime = new HashMap<>();
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
String basePath = request.getContextPath();
String path = request.getRequestURI();
if (!doLoginInterceptor(path, basePath)) {//是否进行登陆拦截
return true;
}
String token = request.getHeader(Constant.AUTHORIZATION);
log.info("==========登陆token信息[{}]", JSON.toJSONString(token));
if (StringUtils.isNotBlank(token)) {
Object userId = redisCache.checkToken(token);
if (Objects.isNull(userId)) {
log.info("==========当前token 缓存中已经过期[{}]", token);
response(response, ResultUtils.error(new AuthServiceException(ResponseCommon.USER_LOGIN)));
return false;
} else if (!verifyLoginInfo(userId)) {
log.info("==========当前token信息存在异常[{}]", token);
response(response, ResultUtils.error(new AuthServiceException(ResponseCommon.USER_LOGIN_EXCEPTION)));
return false;
}
addResponseHeaders(response, token, userId);
} else {
log.info("==========请求信息没有token");
response(response, ResultUtils.error(new AuthServiceException(ResponseCommon.USER_LOGIN)));
return false;
}
log.info("用户已登录,userName:" + TokenUtils.extractInfo(token));
return true;
}
private void addResponseHeaders(HttpServletResponse response, String token, Object userId) {
String newToken = refreshToken(token, userId);
response.setHeader("Access-Control-Expose-Headers", Constant.AUTHORIZATION);
response.setHeader(Constant.AUTHORIZATION, newToken);
}
/**
* 是否进行登陆过滤
*
* @param path
* @param basePath
* @return
*/
private boolean doLoginInterceptor(String path, String basePath) {
path = path.substring(basePath.length());
log.info("request url: {}", path);
List<String> notLoginPaths = Arrays.asList(CommonUrlConstant.USER_LOGIN");
if (notLoginPaths.contains(path)) {
return false;
}
return true;
}
private void response(HttpServletResponse response, ApiResultModel message) {
response.setCharacterEncoding(StandardCharsets.UTF_8.toString());
response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
PrintWriter printWriter = null;
try {
printWriter = response.getWriter();
printWriter.println(JSON.toJSONString(message));
printWriter.flush();
} catch (IOException e) {
log.error("拦截器响应异常,respJson:" + message, e);
} finally {
if (printWriter != null) {
printWriter.close();
}
}
}
/**
* 返回false 不通过
*
* @param userId
* @return
*/
private Boolean verifyLoginInfo(Object userId) {
try {
if (Objects.isNull(userId)) {
return false;
}
if (cacheMap.size() > 500) {
cacheMap.clear();
}
User userInfo = cacheMap.get(Long.parseLong(userId.toString()));
if (Objects.isNull(userInfo)) {
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper();
queryWrapper.eq(User::getId, userId);
userInfo = userService.getOne(queryWrapper);
if (Objects.nonNull(userInfo)){
cacheMap.put(userInfo.getId(), userInfo);
}
}
log.info("=============校验当前用户登陆信息[{}]查询结果[{}] 校验结果[{}]", userId, JSON.toJSONString(userInfo), Objects.nonNull(userInfo));
addInfo(userInfo);
return Objects.nonNull(userInfo);
} catch (Exception e) {
log.info("=============校验当前用户登陆信息[{}] 校验异常[{}]", userId, e);
}
return false;
}
/**
* 添加用户信息 *
* @param user
*/
private static void addInfo(User user) {
if (Objects.nonNull(user)) {
LoginReq loginReq = new LoginReq();
BeanCopier beanCopier = BeanCopier.create(User.class, LoginReq.class, Boolean.FALSE);
beanCopier.copy(user, loginReq, null);
loginReq.setUserId(user.getId());
HolderUtil.add(loginReq);
}
}
/**
* 刷新token
*
* @param token
* @param userIdObject
* @return
*/
private String refreshToken(String token, Object userIdObject) {
if (!isRefreshToken(token)) {
log.info("使用旧token[{}]",token);
return token;
}
if (oldTokenHandler(token)) {
User user = cacheMap.get(userIdObject);
if (Objects.nonNull(user)) {
String newToken = getToken(user);
redisCache.setToken(newToken, user.getId());
log.info("使用新token[{}]",newToken);
return newToken;
}
}
log.info("使用旧token[{}]",token);
return token;
}
/**
* 处理旧token
*
* @param oleToken
* @return
*/
private boolean oldTokenHandler(String oleToken) {
return redisCache.remove(oleToken);
}
/**
* 获取token
*
* @param user
* @return
*/
private String getToken(User user) {
return TokenUtils.getToken(user.getName(), user.getId().toString(), user.getPassword());
}
/**
* 提取用户信息
*
* @param token
* @return
*/
private static User extractTokenInfo(String token) {
try {
Map info = TokenUtils.extractInfo(token);
return ConvertUtil.objectConvertClass(info, User.class);
} catch (Exception e) {
log.info("extract token info fail[{}] token info[{}]", e, token);
}
return null;
}
/**
* 相差时间(分钟)
*
* @param user
* @return
*/
private Long tokenRemainingTime(User user) {
Long cacheTime = cacheExpirationTime.get(user.getId());
if (Objects.isNull(cacheTime)){
cacheTime = user.getCreateTime();
}
Long currentTime = System.currentTimeMillis();
Long differenceTime = (currentTime / DateUtil.ONE_MINUTES_MILLS) - (cacheTime / DateUtil.ONE_MINUTES_MILLS);
cacheExpirationTime.put(user.getId(),currentTime);
return differenceTime;
}
/**
* 是否刷新token(token 过期时间小于2分钟刷新)
* @param token
* @return
*/
private Boolean isRefreshToken(String token) {
User user = extractTokenInfo(token);
if (Objects.isNull(user)) {
return false;
}
Long differenceTime = tokenRemainingTime(user);
log.info("剩余时间[{}]",differenceTime);
if (differenceTime >= 2) {
return true;
}
return false;
}
}