1 说明
本文将讲述SpringBoot拦截器的编写,通过举一个Token第三方认证的例子让读者了解在SpringBoot中拦截器该如何编写,在讲解拦截器之前,可能有读者会对过滤器和拦截器的区别产生疑问,首先我先介绍一下过滤器和拦截器有哪些区别,具体写代码的时候根据具体场景选择使用。
1.1 过滤器和拦截器的区别
Spring的拦截器与Servlet的Filter有相似之处,比如二者都是AOP编程思想的体现,都能实现权限检查,日志记录等。不同的是:
- 使用范围不同:Filter是Servlet规范规定的,只能用于Web程序中。而拦截器既可以用于Web程序,也可以用于Application、Swing程序中。
- 规范不同:Filter是在Servlet规范中定义的,是Servlet容器支持的。而拦截器是Spring容器内的,是Spring框架支持的。
- 使用的资源不同:同其他的代码块一样,拦截器也是一个Spring的组件,归Spring管理,配置在Spring文件中,因此能使用Spring里的任何资源、对象,例如Service对象、数据源、事务管理等,通过IoC注入到拦截器即可;而Filter则不能。
- 深度不同:Filter只在Servlet前后起作用。而拦截器能够深入到方法前后、异常抛出前后等,因此拦截器的使用具有更大的弹性。所以在Spring构架的程序中,要优先使用拦截器。
1.2 过滤器和拦截器所在的位置
1.3 请求穿透过程
第二步在SpringMVC中是DispatcherServlet来分发请求给不同的Controller,执行的是Servlet的service()方法。
2 例子:Token第三方认证
这里构建一个场景,当一个请求携带appToken来访问程序,程序首先校验appToken是否有效,如果无效就拦截请求,这个验证appToken的过程通过判断方法上是否有@ValidToken注解来决定是否校验appToken
代码清单:
- WebMvcConfig.java
- AppTokenInterceptor.java
- SysLoginController.java
1 WebMvcConfig.java
import com.css.common.utils.SpringContextUtils;
import com.css.modules.file.interceptor.AppTokenInterceptor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
@EnableWebMvc
@Slf4j
public class WebMvcConfig implements WebMvcConfigurer {
/**
* 添加认证拦截器
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
AppTokenInterceptor appTokenInterceptor = SpringContextUtils.getBean(AppTokenInterceptor.class);
registry.addInterceptor(appTokenInterceptor).addPathPatterns("/**");
}
/**
* 将自定义Interceptor添加进IOC容器,不添加没法再其属性类使用依赖注入,注入其他对象实例
*/
@Bean
public AppTokenInterceptor appTokenInterceptor() {
return new AppTokenInterceptor();
}
}
2 AppTokenInterceptor.java
/**
* appToken校验拦截器
*
* @author 一朝风月
* @date 2021/6/21 16:40
*/
@Slf4j
public class AppTokenInterceptor implements HandlerInterceptor {
@Value("${baseUrl}")
private String baseUrl;
@Resource
private RestTemplate restTemplate;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
if (method.getAnnotation(ValidToken.class) == null) {
return true;
}
//从header中获取token
String appToken = request.getHeader("appToken");
//如果header中不存在token,则从参数中获取token
if (StringUtils.isBlank(appToken)) {
appToken = request.getParameter("appToken");
}
if (StringUtils.isEmpty(appToken)) {
setResponseMsg(response, Result.ok("请输入appToken!"));
return false;
}
String url = this.baseUrl + "/sys/sysApplication/validToken";
HttpHeaders headers = new HttpHeaders();
//创建请求头
headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
HttpEntity<String> requestEntity = new HttpEntity<>(appToken, headers);
ResponseEntity<String> responseEntity = this.restTemplate.exchange(
url, HttpMethod.POST, requestEntity, String.class, new LinkedMultiValueMap<>());
JSONObject result = JSONObject.parseObject(responseEntity.getBody());
if (result == null) {
setResponseMsg(response, Result.error("令牌校验失败,返回null"));
return false;
}
if (result.get(Result.DATA) == null) {
log.debug(result.get(Result.MSG).toString());
setResponseMsg(response, Result.error(result.get(Result.MSG).toString()));
return false;
}
if ((boolean) result.get(Result.DATA)) {
return true;
} else {
setResponseMsg(response, Result.ok("令牌校验未通过"));
return false;
}
}
private void setResponseMsg(HttpServletResponse response, Result result) throws IOException {
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
response.getWriter().append(JSONObject.toJSONString(result));
}
}
3 SysLoginController.java(部分代码)
/**
* 应用登录
*/
@ValidToken
@PostMapping("/sys/appLogin")
public Map<String, Object> appLogin(@RequestBody SysLoginForm form) {
//用户信息
SysUserEntity user = sysUserService.queryByUserName(form.getUsername());
//账号不存在、密码错误
if (user == null || !user.getPassword().equals(new Sha256Hash(form.getPassword(), user.getSalt()).toHex())) {
return Result.error("账号或密码不正确");
}
//账号锁定
if (user.getStatus() == 0) {
return Result.error("账号已被锁定,请联系管理员");
}
//生成token,并保存到数据库
return sysUserTokenService.createToken(user.getUserId());
}
3 结果
当令牌过期时
{
"msg": "appToken已过期,请重新获取",
"code": 500
}