背景
某些前端页面我们需要登陆之后才能看到数据。但是涉及的页面非常多,如果每个请求都做用户验证,写到代码service层就非常臃肿。
实现策略
通过拦截所有请求,对有需要的方法,加上@LoginUser
注解,通过判断标注过的注解方法,则需要能过token获取登陆信息。
第一步:拦截所有请求,实现HandlerInterceptor接口,
@Configuration
public class UserLoginInterceptor implements HandlerInterceptor {
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
}
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
}
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
}
}
第二步:在preHandle 中获取request中,对应的token信息,通过token获取登陆信息(因为登陆过,所以会保存到redis中),获取登陆信息,判断@LoginUser注解过的方法,统一拦截未登录的请求。
Threadlocal保存对应token对应的的登陆信息,在service可以直接获取登陆的用户信息。 ContextHolderUtil.setLoginUser(userLoginVo);
private static final ThreadLocal<UserLoginVo> LOGIN_USER = new ThreadLocal<>(); public static void setLoginUser(UserLoginVo userLoginVo) { LOGIN_USER.set(userLoginVo); }
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 解析用户
String token = request.getParameter("access_token");
if (StringUtils.isNotBlank(token)) {
try {
UserLoginVo userLoginVo = userService.convertToken2User(token);
ContextHolderUtil.setLoginUser(userLoginVo);
LOGGER.info("UserLogin#setLoginUser success token[{}] userLoginVo:[{}]", token, userLoginVo);
} catch (Exception e) {
LOGGER.error("preHandle#setLoginUser", e);
}
} else {
LOGGER.info("UserLogin#setLoginUser token is blank");
}
HandlerMethod handlerMethod = (HandlerMethod) handler;
LoginUser o = handlerMethod.getMethodAnnotation(LoginUser.class);
if (Objects.nonNull(o) && Objects.isNull(userService.getLoginUser())) {
ContextHolderUtil.removeAll();
LOGGER.warn("UserLogin#setLoginUser url need login but token is [{}]", token);
throw new BusinessException(ResponseUtil.unlogin());
}
return true;
}
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
ContextHolderUtil.removeAll();
LOGGER.info("UserLogin#removeLoginUser success");
}
备注:ThreadLocal妙用
ThreadLocal:可保存对应Controller中对应的语言国际化信息,ip信息,服务消耗时间time等信息
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 设置语言
String locale = request.getParameter("lang");
if (StringUtils.isBlank(locale)) {
locale = LocaleUtil.zh;
}
ContextHolderUtil.setClientLocale(locale);
ContextHolderUtil.setClientIp(request.getParameter("client_ip"));
......
......
return ture;
}