攔截器(Interceptor)是一種在應用程序中用于干預、修改或攔截請求和響應的組件,是AOP 編程的一種實踐,和過濾器一樣都是一種具體的AOP實現。它可以在請求被發送到目標處理程序之前或之后,對請求進行預處理或對響應進行后處理。
攔截器通常用于以下目的:
認證和授權:攔截器可以對請求進行身份驗證和權限檢查,確保只有經過認證和授權的用戶才能訪問特定的資源或執行特定的操作。
日志記錄和性能監控:攔截器可以用于記錄請求和響應的日志,以便進行故障排查、性能監控和分析。
請求轉發和重定向:攔截器可以根據特定的條件,對請求進行轉發或重定向至不同的處理程序或頁面。
數據轉換和格式化:攔截器可以對請求和響應的數據進行轉換、格式化或驗證,以滿足特定的需求或規范。
在很多框架和應用程序中都有攔截器的概念,比如在Java中的Spring MVC框架中,可以使用攔截器來攔截和處理HTTP請求;在網絡安全領域,防火墻和入侵檢測系統也可以使用攔截器來攔截和過濾惡意流量。攔截器在應用程序中起到了很重要的作用,增強了系統的安全性、可靠性和可控性。
1.攔截器的編寫方法?
攔截器的編寫比較簡單,一般是根據自己的不同需要繼承不同的攔截器基類。大致分為兩類,一類是基于業務判斷服務的攔截器,比如:日志服務,權限認證,這種攔截器一般繼承自?HandlerInterceptor類;另一類是和配置有關的攔截器,一般繼承自?WebMvcConfigurer 。其中第一類最多,需要重寫 preHandle 和 postHandle 方法,其中 preHandle 方式用于請求執行前的時候,當preHandle 返回? true 才能進行下一個方法,而 postHandle 是作用于請求結束前。afterCompletion 是視圖顯示完成后才會執行。?
第一類,基于 HandlerNterceptor 類的攔截器 ,用于日志服務:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;@Component
public class LoggerInterceptor extends HandlerInterceptorAdapter {//生成日志類private Logger logger = LoggerFactory.getLogger(LoggerInterceptor.class);@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {HandlerMethod handlerMethod = (HandlerMethod) handler;// Returns the name of the HTTP method with which this request was made, for example, GET, POST, or PUT.Method method = handlerMethod.getMethod();//得到方法信息//從當前方法中獲取一個日志注解對象LogAnnotation logAnnotation = method.getAnnotation(LogAnnotation.class);if (logAnnotation != null){long startTime = System.currentTimeMillis();request.setAttribute("startTime",startTime);//設置屬性logger.info("enter"+method.getName()+"method cost time is:"+startTime+" ms");}return true;}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {HandlerMethod handlerMethod = (HandlerMethod) handler;Method method = handlerMethod.getMethod();//得到方法信息LogAnnotation logAnnotation = method.getAnnotation(LogAnnotation.class);if (logAnnotation != null){long endTime = System.currentTimeMillis();long startTime = (long) request.getAttribute("startTime");long periodTime = endTime- startTime;logger.info("Leave "+method.getName()+"method time is:"+endTime+" ms");logger.info("On "+method.getName()+"method cost time is:"+periodTime+" ms");}}
}
將分別計算并輸出進入某個方法和離開某個方法的時間。?
聲明注解 :在需要使用該服務的方法上加上該注解即可使用
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Target(ElementType.METHOD)
@Retention(value = RetentionPolicy.RUNTIME)
public @interface LogAnnotation {String message() default "666";String[] params() default {};}
注冊配置攔截器:實現WebMvcConfigurer接口,并重寫addInterceptors方法
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.WebMvcConfigurer;@Configuration
public class WebConfig implements WebMvcConfigurer {@Autowiredprivate LoggerInterceptor loggerInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(loggerInterceptor).addPathPatterns("/**").excludePathPatterns("/ls");//不攔截的路徑}
}
2.郵箱有效性驗證?
常見的郵箱如 qq,網易,地址形式如 xxx@qq.com ,xxx@163.com?.
總結成正則表達式如下:
"^([a-z0-9A-z]+[-|\\.]?)+[a-z0-9A-z]@([a-z0-9A-Z]+(-[a-z0-9A-Z]+)?\\.)+([a-zA-Z]{2,})$"
用Java編寫一個檢查有效性的例子:
public static boolean matchTest(String email){boolean a = email.matches("^([a-z0-9A-Z]+[-|\\.]?)+[a-z0-9A-Z]@([a-z0-9A-Z]+(-[a-z0-9A-Z]+)?\\.)+([a-zA-Z]{2,})$");System.out.println(a);return a;}
編寫完整的攔截器:
import org.example.service.Testjson;
import org.springframework.stereotype.Component;
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 EmailInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {String email = request.getParameter("email");if (Testjson.matchTest(email)){return true;}else {return false;}}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {}
}
這里只需要重寫 preHandle 方法即可,完成對有效性郵箱地址的判斷效果。
3.Token 檢查攔截器實現
Token檢查是一個健全系統或者服務必須有的一項安全性功能,特別是基于 RESTful 的接口,需要檢查Token來判斷用戶是否已經登陸或者驗證身份,利用Token可以獲取進一步的網絡資源和權限。
一般我們會使用 JWT 驗證方式,JSON Web Token 簡稱 JWT。本質上是一段字符串,由以下三個部分構成。
- 頭部: JWT 基本信息,包括算法。
- 荷載: 自定義數據,包括用戶標識,如 userid。
- 簽名: 前面兩者通過一定加密算法后計算所得其簽名字符串。
引入 JWT 依賴到 pom.xml 文件中,代碼如下:
<dependency><groupId>com.auth0</groupId><artifactId>java-jwt</artifactId><version>3.4.0</version></dependency>
更新前端攔截器配置,處理所有請求:
@Configuration
public class WebConfig implements WebMvcConfigurer {@Autowiredprivate LoggerInterceptor loggerInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(loggerInterceptor).addPathPatterns("/**").excludePathPatterns("/ls");//不攔截的路徑registry.addInterceptor(authenticationInterceptor()).addPathPatterns("/**");}@Beanpublic AuthenticationInterceptor authenticationInterceptor(){return new AuthenticationInterceptor();}
}
新建兩個注解,用于判斷是否進行Token驗證,首先編寫需要登陸驗證的注解UserLoginToken:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** 需要登陸才能操作得注解*/
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface UserLoginToken {boolean required() default true;
}
用于跳過驗證的注解:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface PassToken {boolean required() default true;
}
檢查Token的攔截器:
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import org.example.Exception.BusinessException;
import org.example.pojo.User;
import org.example.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;@Component
public class AuthenticationInterceptor implements HandlerInterceptor {@AutowiredUserService userService;@AutowiredRedisUtil redisUtil;@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {String token = request.getHeader("token");//排除訪問的是靜態資源,而不是映射訪問if (!(handler instanceof HandlerMethod)){return true;}//獲取訪問的方法HandlerMethod handlerMethod = (HandlerMethod) handler;Method method = handlerMethod.getMethod();//是否被跳過驗證注解if (method.isAnnotationPresent(PassToken.class)){PassToken passToken = method.getAnnotation(PassToken.class);if (passToken.required()){return true;}}if (method.isAnnotationPresent(UserLoginToken.class)){UserLoginToken userLoginToken = method.getAnnotation(UserLoginToken.class);if (userLoginToken.required()){if (token == null){throw new BusinessException("4001","no token");}String userId;try{userId = JWT.decode(token).getAudience().get(0);}catch (Exception e){throw new BusinessException("4003","decode token fails");}//Check the expire of tokenString tokenKey = userId+":"+token;boolean hasExisted = redisUtil.hasKey(tokenKey);System.out.println("exit or not: "+hasExisted);if (!hasExisted){throw new BusinessException("4005","token expired!");}int userID = Integer.parseInt(userId);System.out.println("userId is:"+userID);User user =userService.findUserById(userID);if (user == null){throw new RuntimeException("user not exits");}try {JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(user.getPassword()+"MTest!76&sQ^")).build();jwtVerifier.verify(token);//設置當前登錄用戶LoginUser loginUser = new LoginUser();loginUser.setID((long)userID);UserContext.setUser(loginUser);}catch (JWTVerificationException e){throw new BusinessException("4002","invalid token");}}}return true;}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {}
}
使用起來也很方便,在需要與不需要進行 Token 驗證的接口前面加上對應的注釋即可。
補充:
?
handler
?參數是 Spring MVC 框架傳遞給你的?preHandle
?方法的一個對象,它代表當前請求即將要調用的控制器邏輯。在 Spring MVC 中,這個?handler
?對象可以是多種類型,但通常它是?HandlerMethod
?的一個實例。當一個請求被映射到某個控制器的方法時,這個方法就被包裝為?
HandlerMethod
?對象。HandlerMethod
?對象包含了這個方法以及包含這個方法的控制器的信息,允許你在攔截器中對即將執行的控制器方法進行一些前處理。在某些情況下,
handler
?可能不是?HandlerMethod
?的實例。例如,當請求是針對靜態資源時,或者是某個請求沒有被映射到任何的控制器上的方法時,handler
?可能是框架中其他類型的處理器,這取決于你的應用配置和請求的具體類型。這就是為什么在?preHandle
?方法中進行?instanceof HandlerMethod
?檢查是一種常見的做法,以確保你正在處理的是一個具體的控制器方法。如果是,則可以安全地轉型到?HandlerMethod
?并進行進一步的處理。在?
preHandle
?方法的上下文中,你可以對?handler
?參數進行各種檢查和操作。例如,你可以檢查用戶是否有權限執行與?handler
?對應的控制器方法,你可以修改請求或響應,或者決定是否繼續執行請求處理鏈(通過返回?true
?或者?false
)。
在Spring MVC框架中:
靜態資源請求:
? 這些請求通常不需要經過復雜的處理,它們被映射到服務器上存儲靜態文件的目錄。例如在Spring Boot中,靜態資源可以放置在 `/static`、`/public` 等默認的資源目錄中,這些目錄中的資源可以直接通過URL被訪問。在這種情況下,與靜態資源對應的`handler`可能就不是一個`HandlerMethod`,而是其他用于處理靜態資源的類的實例。數據請求:
? 這些請求需要后端處理,如執行數據庫操作、進行計算或調用其他接口等。在Spring MVC中,這些請求被映射到控制器類中的特定方法上,這些方法通過注解(如`@RequestMapping`或其特化版本比如`@GetMapping`、`@PostMapping`等)來標注。這時候傳遞給`preHandle`方法的`handler`參數就是一個`HandlerMethod`實例,代表被映射的那個方法。因此,在攔截器的`preHandle`方法中,進行`handler instanceof HandlerMethod`檢查可以幫助你明確該次請求是需要Spring MVC通過控制器中的方法處理,還是僅僅是為了獲取靜態資源。這可以確保你的攔截器邏輯只應用于實際需要攔截的后端處理請求,而不是靜態資源請求。