|
@@ -0,0 +1,141 @@
|
|
|
+package cn.rankin.apiweb.intercepter;
|
|
|
+
|
|
|
+import cn.rankin.apiweb.code.ApiWebCode;
|
|
|
+import cn.rankin.apiweb.security.JwsToken;
|
|
|
+import cn.rankin.apiweb.security.JwsTokenService;
|
|
|
+import cn.rankin.apiweb.vo.DeviceUserVo;
|
|
|
+import cn.rankin.common.utils.api.model.BaseCode;
|
|
|
+import cn.rankin.common.utils.constant.RedisKey;
|
|
|
+import cn.rankin.common.utils.service.RedisService;
|
|
|
+import cn.rankin.common.utils.util.HttpUtil;
|
|
|
+import com.alibaba.fastjson.JSON;
|
|
|
+import org.slf4j.Logger;
|
|
|
+import org.slf4j.LoggerFactory;
|
|
|
+import org.springframework.beans.factory.annotation.Autowired;
|
|
|
+import org.springframework.core.NamedThreadLocal;
|
|
|
+import org.springframework.http.HttpMethod;
|
|
|
+import org.springframework.stereotype.Component;
|
|
|
+import org.springframework.util.StringUtils;
|
|
|
+import org.springframework.web.servlet.HandlerInterceptor;
|
|
|
+import org.springframework.web.servlet.ModelAndView;
|
|
|
+
|
|
|
+import javax.servlet.http.HttpServletRequest;
|
|
|
+import javax.servlet.http.HttpServletResponse;
|
|
|
+
|
|
|
+@Component
|
|
|
+public class LoginInterceptor implements HandlerInterceptor {
|
|
|
+
|
|
|
+ private static final Logger logger = LoggerFactory.getLogger(LoginInterceptor.class);
|
|
|
+
|
|
|
+ private static final String LOGIN_TOKEN_FORMAT_KEY = RedisKey.LOGIN_TOKEN_FORMAT_KEY;
|
|
|
+
|
|
|
+ private static final String ERROR_LOGIN_HEADER = "NO_LOGIN_SIGN";
|
|
|
+
|
|
|
+ private NamedThreadLocal<Long> startTimeThreadLocal = new NamedThreadLocal<Long>("StopWatch-StartTime");
|
|
|
+
|
|
|
+ private static final long REFRESH_INTERVAL = 2 * 60 * 60 * 1000;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ RedisService<String, Object> redisService;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private JwsTokenService jwsTokenService;
|
|
|
+
|
|
|
+ // 忽略options请求,默认为true
|
|
|
+ private boolean ignoreOptions = true;
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
|
|
|
+
|
|
|
+ //判断 OPTIONS 请求
|
|
|
+ if (ignoreOptions && HttpMethod.OPTIONS.matches(request.getMethod())) {
|
|
|
+ logger.info("OPTIONS 请求 忽略 返回200");
|
|
|
+ request.setAttribute("NO_LOGIN_SIGN", "OPTIONS");
|
|
|
+ responseOutWithJson(request, response);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ //记录开始时间
|
|
|
+ startTimeThreadLocal.set(System.currentTimeMillis());//线程绑定变量(该数据只有当前请求的线程可见)
|
|
|
+
|
|
|
+ //登录请求不拦截
|
|
|
+ String url = request.getServletPath();
|
|
|
+ if (url.equals("/login") || url.equals("/login/")) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ String token = request.getHeader("Authentication");
|
|
|
+ logger.info("请求开始 url={} token={}", url, token);
|
|
|
+ if (StringUtils.isEmpty(token)) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ JwsToken jwsToken = jwsTokenService.parse(token);
|
|
|
+ if (jwsToken == null) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ String userName = jwsToken.getUserName();
|
|
|
+
|
|
|
+ //因为缓存了用户id和设备id
|
|
|
+ DeviceUserVo du = (DeviceUserVo) redisService.get(String.format(LOGIN_TOKEN_FORMAT_KEY, userName));
|
|
|
+ if (null == du) {
|
|
|
+ logger.error("验证 token 异常: token={}", JSON.toJSONString(jwsToken));
|
|
|
+ request.setAttribute(ERROR_LOGIN_HEADER, "ERROR_TOKEN");
|
|
|
+ responseOutWithJson(request, response);
|
|
|
+ return false;
|
|
|
+ }else if (!jwsToken.getDeviceId().equals(du.getDevice())) {
|
|
|
+ logger.error("device not match, token={}", JSON.toJSONString(jwsToken));
|
|
|
+ request.setAttribute(ERROR_LOGIN_HEADER, "NOT_MATCH");
|
|
|
+ responseOutWithJson(request, response);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ //查询到结果 如果存在 token 对应的 user
|
|
|
+ // 1.判断 如果有效期小于 2小时
|
|
|
+ logger.info("token check success: {}", JSON.toJSONString(du));
|
|
|
+
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
|
|
|
+
|
|
|
+ logger.info("请求结束 url={}", request.getRequestURI());
|
|
|
+ long beginTime = startTimeThreadLocal.get();//得到线程绑定的局部变量(开始时间)
|
|
|
+ long consumeTime = System.currentTimeMillis() - beginTime;//消耗的时间
|
|
|
+ if (consumeTime > 500) {//此处认为处理时间超过500毫秒的请求为慢请求
|
|
|
+ logger.warn(" 请求: {} consume {} millis", request.getRequestURI(), consumeTime);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ protected void responseOutWithJson(HttpServletRequest request, HttpServletResponse response) {
|
|
|
+ BaseCode code = ApiWebCode.SERVER_ERROR;
|
|
|
+ logger.error("请求参数详情 {}", JSON.toJSONString(request.getParameterMap()));
|
|
|
+ if (!StringUtils.isEmpty(request.getAttribute(ERROR_LOGIN_HEADER))) {
|
|
|
+ String sign = (String) request.getAttribute(ERROR_LOGIN_HEADER);
|
|
|
+ switch (sign) {
|
|
|
+ case "ERROR_REQUEST":
|
|
|
+ code = ApiWebCode.UNAUTHORIZED;
|
|
|
+ break;
|
|
|
+ case "ERROR_TOKEN":
|
|
|
+ code = ApiWebCode.INVALID_TOKEN;
|
|
|
+ break;
|
|
|
+ case "OPTIONS":
|
|
|
+ code = ApiWebCode.OK;
|
|
|
+ break;
|
|
|
+ case "NOT_MATCH":
|
|
|
+ code = ApiWebCode.DEVICE_BOUND_ERROR;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ HttpUtil.responseOutWithJson(request, response, code);
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setIgnoreOptions(boolean ignoreOptions) {
|
|
|
+ this.ignoreOptions = ignoreOptions;
|
|
|
+ }
|
|
|
+}
|