拦截器:作用类似于Servlet中的Filter,用于对处理器进行预处理和后处理。
public interface HandlerInterceptor {
default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return true;
}
default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable ModelAndView modelAndView) throws Exception {
}
default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable Exception ex) throws Exception {
}
}
其中 preHandle()
实现处理器预处理,请求会先到这里,当这个方法返回值是true的时候表示处理器可以正常往下执行,返回false,则不会继续执行处理器的代码,一般用来身份验证和鉴权。
postHandle()
是后处理回调方法,也就是控制器完成后执行。
afterCompletion()
在视图渲染完毕时候调用,一般用来进行统一的异常处理,日志处理。
在springboot中可以通过实现HandlerInterceptor接口实现拦截器,这里主要介绍通过预处理做身份鉴权的方法。
- 定义注解
import java.lang.annotation.*;
/***
* token校验拦截器
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface LoginRequired {
}
- 定义token校验方法
@Service
public class AccountAuthProxy {
public boolean verifyToken(Long userId, String token){
//定义校验规则
}
}
- 定义拦截器
import com.xiaomi.gamecenter.misc.socialsquare.annotation.LoginRequired;
import com.xiaomi.gamecenter.misc.socialsquare.config.ConfigService;
import com.xiaomi.gamecenter.misc.socialsquare.constants.Err;
import com.xiaomi.gamecenter.misc.socialsquare.proxy.AccountAuthProxy;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class AccountInterceptor implements HandlerInterceptor {
private static final Logger log = LoggerFactory.getLogger(AccountInterceptor.class);
@Autowired
AccountAuthProxy accountAuthProxy;
public static final String USER_ID_NAME = "userId";
public static final String TOKEN_NAME = "token";
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception{
Err err;
try{
if (handler instanceof HandlerMethod) {
String token = parseTokenFromReq(request);
long uid = parseUserIdFromReq(request);
HandlerMethod method = (HandlerMethod) handler;
LoginRequired option = method.getMethodAnnotation(LoginRequired.class);
//如果未加注解则直接放行
if (option == null) {
return true;
}
boolean pass = accountAuthProxy.verifyToken(uid, token);
log.info("token verify result---userId={}, token={}, pass={}", uid, token, pass);
if (!pass) {
log.info("token verify error---userId={}, token={}", uid, token);
throw new ServiceException(RetCode.LOGIN_EXPIRE);
}
}else {
//处理预检请求
response.setStatus(HttpServletResponse.SC_OK);
return true;
}
}catch (Exception e){
log.error("login interceptor error---", e);
}
return true;
}
/**
* 从HTTP请求解析中得到用户ID,兼容了不同参数名
*/
private long parseUserIdFromReq(HttpServletRequest request) {
if (StringUtils.isNumeric(request.getParameter(USER_ID_NAME))) {
return Long.parseLong(request.getParameter(USER_ID_NAME));
} else {
return 0L;
}
}
/**
* 从HTTP请求参数中解析得到token
*/
private String parseTokenFromReq(HttpServletRequest request) {
String token = null;
if (StringUtils.isNotBlank(request.getParameter(TOKEN_NAME))) {
token = request.getParameter(TOKEN_NAME);
}
return token;
}
}
- 添加拦截器配置
import com.xiaomi.gamecenter.misc.socialsquare.interceptor.AccountInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Bean
public AccountInterceptor accountInterceptor(){
return new AccountInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 添加账号拦截器
registry.addInterceptor(accountInterceptor())
.addPathPatterns("/**");
}
}
- token校验
在需要校验token的接口上面添加注解@LoginRequired
,则在请求接口的时候会走拦截器里面校验token的方法,接口捕获异常,返回前端登录错误的状态码。
例如:
/***
* 获取该用户的帖子列表
*/
@PostMapping("/user/list")
@TokenCheck
public BaseDataVo<UserContentVo> contentList(@RequestBody UserContentReq req){
BaseDataVo<UserContentVo> responseVo = new BaseDataVo<>();
try{
responseVo = contentService.getUserContentList(req.getUserId(), req.getPage(), req.getPageSize());
responseVo.setRetCode(RetCode.SUCCESS);
}catch (ServiceException e){
responseVo.setRetCode(e.getRetCode());
}
return responseVo;
}
```