HandlerInterceptorAdapter 是 Spring MVC 中的拦截器(Interceptor)类,用于拦截请求的处理流程,包括请求的预处理、后处理和渲染视图等操作。它可以用于实现一些全局性的处理逻辑,例如日志记录、权限验证、请求参数预处理等
预处理回调方法,实现处理器的预处理。可以在这里进行权限验证、请求参数处理等。
后处理回调方法,实现处理器(controller)的后处理,但在渲染视图之前,此时我们可以通过modelAndView对模型数据进行处理或对视图进行处理
整个请求处理完毕回调方法,即在视图渲染完毕时回调,如性能监控中我们可以在此记录结束时间并输出消耗时间,还可以进行一些资源清理,类似于try-catch-finally中的finally,但仅调用处理器执行链中
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class MyInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
// 在请求处理之前执行,可以进行权限验证等操作
System.out.println("Pre Handle method is Calling");
return true; // 返回true表示继续执行后续操作,返回false将中断请求处理流程
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
org.springframework.web.servlet.ModelAndView modelAndView) throws Exception {
// 在请求处理之后、视图渲染之前执行,可以修改数据模型等操作
System.out.println("Post Handle method is Calling");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
// 在整个请求完成之后执行,可以用于清理资源等操作
System.out.println("After Completion method is Calling");
}
}
在上述示例中,MyInterceptor 继承了 HandlerInterceptorAdapter 类,并重写了其三个方法(preHandle、postHandle 和 afterCompletion)
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 WebMvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**");
}
}
在上述配置中,addInterceptors 方法中注册了 MyInterceptor 拦截器,并设置了拦截的路径为 “/**”,表示拦截所有请求。你可以根据实际需求修改拦截的路径
@Retention(RUNTIME)
@Target(METHOD)
public @interface ApiPermission {
String value() default "";//默认为空,因为名字是value,实际操作中可以不写"value="
}
@Component
public class PermissionInterceptor implements HandlerInterceptor {
private static final Logger LOGGER = LoggerFactory.getLogger(PermissionInterceptor.class);
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
LOGGER.info("进入拦截器");
if(handler instanceof HandlerMethod) {
HandlerMethod h = (HandlerMethod)handler;
ApiPermission apiPermission= h.getMethodAnnotation(ApiPermission.class);
if (easyLog != null) {
//TODO 判断当前用户是否拥有权限信息
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
// 创建一个JSON对象
JSONObject jsonObject = new JSONObject();
jsonObject.put("code", "403");
jsonObject.put("message", "用户无权限操作");
// 设置响应内容类型为JSON,使用UTF-8编码(不使用中文会乱码)
response.setContentType("application/json;charset=UTF-8");
response.getWriter().print(jsonObject.toString());
return false;
}
}
return HandlerInterceptor.super.preHandle(request, response, handler);
}
}
获取controller类方法注解: h.getMethod().getDeclaringClass().getAnnotation(ApiPermission.class);
@Component
public class WebMvcConfig implements WebMvcConfigurer {
@Autowired
private PermissionInterceptor permissionInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(permissionInterceptor).addPathPatterns("/**");;
}
}
@Controller
@RequestMapping("/api/user")
public class UserController {
@Autowired
private UserService userService;
@ApiPermission("user:list")
@RequestMapping(value = "/list", method=RequestMethod.GET)
@ResponseBody
public ApiResult<PageResult<Page<User>>> listByPage(User user, PageResult pageResult) {
Page<User> page = this.userService.findListByPage(user, new Page(pageResult.getPageNum(), pageResult.getPageSize()));
return ApiResult.wrapPage(page);
}
}
签名是在Web开发中常用的一种安全验证方式,用于确保请求的来源和完整性
方法内部的步骤如下:
最终,该方法返回一个基于给定HTTP请求方法、URI、时间戳和密钥生成的签名字符串。该签名字符串可以用于在服务器端验证请求的合法性,确保请求的来源和完整性,防止恶意请求和数据篡改。
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
/**
* 服务认证签名工具类
*/
public class ServiceSignUtil {
public static final String HMAC_SHA1_ALGORITHM = "HmacSHA1";
public static final String HMAC_SHA256_ALGORITHM = "HmacSHA256";
// 定义其他算法常量...
private ServiceSignUtil() {
}
/**
* 生成服务认证签名
*
* @param httpMethod HTTP请求方法,例如GET、POST等
* @param uri 请求的URI(路径)
* @param utc 请求的时间戳(通常是UTC格式的时间戳)
* @param secretKey 用于签名的密钥
* @param algorithm 签名算法,使用上述定义的常量
* @return 返回生成的签名字符串
* @throws NoSuchAlgorithmException 如果指定的算法不存在
* @throws InvalidKeyException 如果密钥无效
*/
public static String signature(String httpMethod, String uri, String utc, String secretKey, String algorithm)
throws NoSuchAlgorithmException, InvalidKeyException {
String toSign = httpMethod + uri + utc;
Mac mac = Mac.getInstance(algorithm);
mac.init(new SecretKeySpec(secretKey.getBytes(StandardCharsets.UTF_8), algorithm));
byte[] signatureBytes = mac.doFinal(toSign.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(signatureBytes).trim();
}
}
public class ApiSecurityInterceptor extends HandlerInterceptorAdapter {
private static final Logger LOGGER = LoggerFactory.getLogger(ApiSecurityInterceptor.class);
private String accessKey = "ak";
private String secretKey = "sk";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
String accessId = request.getHeader(RemoteCallConstants.HEADER_ACCESS_ID);
String signature = request.getHeader(RemoteCallConstants.HEADER_SIGNATURE);
String utc = request.getHeader(RemoteCallConstants.HEADER_TIMESTAMP);
// 微服务标识暂时不用
//String appService = request.getHeader(RemoteCallConstants.HEADER_APP_SERVICE);
// 鉴权信息为空则认为时非法请求
if (StringUtils.isBlank(accessId) || StringUtils.isBlank(signature) || StringUtils.isBlank(utc)) {
throw new RuntimeException("MessageEnum.ILLEGAL_REQUEST");
}
DateTime dateTime = DateUtil.parseUTC(utc);
// 5分钟内请求有效,超过则认为请求过期
if (DateTime.now().between(dateTime).between(DateUnit.MINUTE) >= 5L) {
throw new RuntimeException("MessageEnum.REQUEST_HAS_EXPIRED");
}
// access-key不匹配则为鉴权失败
if (!StringUtils.equals(accessKey, accessId)) {
throw new RuntimeException("MessageEnum.API_AUTHENTICATION_FAILED");
}
String uri = request.getServletPath();
String httpMethod = request.getMethod();
try {
String currentSignature = ServiceSignUtil.signature(httpMethod, uri, utc, secretKey, "HmacSHA256");
// 签名不一致
if (!StringUtils.equals(currentSignature, signature)) {
throw new RuntimeException("MessageEnum.API_AUTHENTICATION_FAILED");
}
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
// 签名生成失败
throw new RuntimeException("MessageEnum.API_AUTHENTICATION_FAILED");
}
return true;
}
public String getAccessKey() {
return accessKey;
}
public void setAccessKey(String accessKey) {
this.accessKey = accessKey;
}
public String getSecretKey() {
return secretKey;
}
public void setSecretKey(String secretKey) {
this.secretKey = secretKey;
}
}
public class RemoteCallConstants {
public static final String HEADER_ACCESS_ID = "accessId";
public static final String HEADER_TIMESTAMP = "timestamp";
public static final String HEADER_SIGNATURE = "signature";
public static final String QUERY_LANG = "lang";
public static final String UTC_TIME_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";
private RemoteCallConstants() {
}
}
@Configuration
public class InterceptorConfig {
private static final Logger LOGGER = LoggerFactory.getLogger(InterceptorConfig.class);
@Value("${easy-api.security.path-patterns:/**/api/**}")
private String pathPatterns;
/**
* @ConfigurationProperties的第二种用法@Bean + @ConfigurationProperties
* 注意!对应bean中的属性需要setter/getter方法,否则无法注入
*/
@Bean
@ConfigurationProperties(prefix = "easy-api.security")
public ApiSecurityInterceptor apiSecurityInterceptor() {
return new ApiSecurityInterceptor();
}
@Bean
public WebMvcConfigurer apiSecurityInterceptorConfigurer() {
LOGGER.info("【easy-mode】@EnableEasyApiSecurity[apiSecurityInterceptor]--拦截:{}", pathPatterns);
return new WebMvcConfigurer() {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(apiSecurityInterceptor()).addPathPatterns(pathPatterns);
}
};
}
}
easy-api:
security:
access-key: ak
secret-key: sk
path-patterns: /**
访问接口ak sk及鉴权接口 通过配置文件灵活定义。(这里演示方便就写死, 生产环境一般每个调用方一对)
Interceptor结合容器更"靠近"业务, filter更倾向基础通用的配置。