一、拦截器介绍
拦截器(Interceptor)依赖 Spring的WEB框架,在SpringMVC框架中是配置在SpringMVC的配置文件中,在SpringBoot项目中也可以采用注解的形式实现。
拦截器是 AOP 的一种应用,底层采用 Java 的反射机制来实现的。与过滤器一个很大的区别是在拦截器中可以注入 Spring 的 Bean,能够获取到各种需要的 Service 来处理业务逻辑,而过滤器则不行,这里我们只讲一下SpringBoot中拦截器的实现和应用。
二、拦截器应用
java代码中要实现过滤器,需要先实现一下HandlerInterceptor接口:
preHandle
调用时间:Controller方法处理之前
执行顺序:链式Intercepter情况下,Intercepter按照声明的顺序一个接一个执行
若返回false,则中断执行,注意:不会进入afterCompletion
postHandle
调用前提:preHandle返回true
调用时间:Controller方法处理完之后,DispatcherServlet进行视图的渲染之前,也就是说在这个方法中你可以对ModelAndView进行操作
执行顺序:链式Intercepter情况下,Intercepter按照声明的顺序倒着执行。
备注:postHandle虽然post打头,但post、get方法都能处理
afterCompletion
调用前提:preHandle返回true
调用时间:DispatcherServlet进行视图的渲染之后
备注:多用于清理资源
interceptor 的执行顺序
1. 请求到达 DispatcherServlet
2. DispatcherServlet 发送至 Interceptor ,执行 preHandle
3. 请求达到 Controller
4. 请求结束后,postHandle 执
5. afterCompletion,请求结束后调用,可以用来统计请求耗时等
三、拦截器配合自定义注解代码实现
在实际项目中,比较常用的是拦截器配合自定义注解,来判断某些特殊方法的请求是否需要登录状态,或者是否有指定角色权限,下面就通过自定义自定义注解和拦截器来实现简单的校验登录状态和admin角色权限。
1. 登录实体类LoginUser
@Data
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class LoginUser {
/**
* 用户id
*/
private Long userId;
/**
* 用户名称
*/
private String name;
}
2. 自定义两个注解,一个Login注解,一个Admin注解
loginApi
import java.lang.annotation.*;
/**
* 验证登录状态的自定义注
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface LoginApi {
/**
* 是否需要登录
*/
boolean value() default true;
}
AuthCheckApi
import java.lang.annotation.*;
/**
* 验证方法访问权限的自定义注解
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AuthCheckApi {
/**
* 是否必须admin权限,默认为true
*/
boolean value() default true;
}
3. 实现两个拦截器,分别校验Login状态和Admin权限
LoginInterceptor
/**
* 登录验证过滤器
*/
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception{
System.out.println("开始拦截 LoginInterceptor.........");
//业务代码
LoginApi annotation = ((HandlerMethod)handler).getMethodAnnotation(LoginApi.class);
if (annotation == null) {
return true;
}
//业务代码
boolean needLogin = annotation.value();
if (!needLogin){
//不需要登录,直接跳过
return true;
}
//请求单点登录系统,根据request中的token获取登录信息
LoginUser loginUser = getLoginUser();
if (loginUser == null) {
System.out.println("LoginInterceptor 用户当前无登录状态!");
sendFailedResp(response, "not login");
return false;
}
request.setAttribute("loginUser", loginUser);
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable ModelAndView modelAndView) throws Exception{
System.out.println("结束 LoginInterceptor.........");
//业务代码
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable Exception ex) throws Exception {
System.out.println("完全结束 LoginInterceptor.........");
}
/**
* 获取用户登录信息
*/
private LoginUser getLoginUser() {
//模拟请求单点登录系统,这里直接返回用户
return new LoginUser(10086L, "张三");
}
/**
* 返回错误信息
*/
private void sendFailedResp(HttpServletResponse response, String respStr) throws IOException {
response.setContentType("application/json;charset=UTF-8");
PrintWriter pw = response.getWriter();
try {
pw.write(respStr);
} finally {
pw.flush();
pw.close();
}
}
}
AuthInterceptor
/**
* 权限校验过滤器
*/
@Component
public class AuthInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception{
System.out.println("开始拦截 AuthInterceptor.........");
AuthCheckApi annotation = ((HandlerMethod)handler).getMethodAnnotation(AuthCheckApi.class);
if (annotation == null) {
return true;
}
//业务代码
boolean needAdmin = annotation.value();
if (!needAdmin) {
//检查用户是否有admin权限
return true;
}
LoginUser loginUser = (LoginUser) request.getAttribute("loginUser");
if (loginUser == null) {
System.out.println("AuthInterceptor 用户当前无登录状态!");
sendFailedResp(response, "permission deny");
return false;
}
boolean hasAdminAuth = getAdminAuth(loginUser);
if (!hasAdminAuth) {
System.out.println("AuthInterceptor 用户没有admin权限!");
sendFailedResp(response, "permission deny");
return false;
}
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable ModelAndView modelAndView) throws Exception{
System.out.println("结束 AuthInterceptor.........");
//业务代码
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable Exception ex) throws Exception {
System.out.println("完全结束 AuthInterceptor.........");
}
private boolean getAdminAuth(LoginUser loginUser) {
//业务逻辑,根据登录人信息,调用接口判断是否有admin权限
System.out.println("业务逻辑,根据登录人信息,调用接口判断是否有admin权限,LoginUser: "+loginUser);
return true;
}
/**
* 返回错误信息
*/
private void sendFailedResp(HttpServletResponse response, String respStr) throws IOException {
response.setContentType("application/json;charset=UTF-8");
PrintWriter pw = response.getWriter();
try {
pw.write(respStr);
} finally {
pw.flush();
pw.close();
}
}
}
4. 配置拦截器,注意配置顺序,Login拦截器需要再Admin拦截器前面
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
@Configuration
public class WebMvcInterceptorConfig extends WebMvcConfigurationSupport {
@Autowired
private LoginInterceptor loginInterceptor;
@Autowired
private AuthInterceptor authInterceptor;
@Override
protected void addInterceptors(InterceptorRegistry registry) {
/**
* 多个拦截器组成一个拦截器链
* 注册顺序就是拦截器执行书序
* addPathPatterns 用于添加拦截规则,/**表示拦截所有F请求
* excludePathPatterns 用户排除拦截
*/
registry.addInterceptor(loginInterceptor).addPathPatterns("/**")
.excludePathPatterns("/stuInfo/getAllStuInfoA","/account/register");
registry.addInterceptor(authInterceptor).addPathPatterns("/**")
.excludePathPatterns("/stuInfo/getAllStuInfoA","/account/register");
super.addInterceptors(registry);
}
}
5. 在需要校验请求权限的方法上添加上自定义注解
@LoginApi()
@AuthCheckApi()
@GetMapping(value = "/hello/world")
public String helloWorld() {
return "Hello World!!!";
}