之前有使用aop自定义注解的方式;
本篇签名验证都一样;时效;防抓包重复请求;redis验签
拦截器方法感觉解耦更强吧,结合全局异常(之前文章里有-aop自定义注解)
1.签名验证拦截器
@Component
@Slf4j
public class SignAuthInterceptor implements HandlerInterceptor {
private final static Long REDIS_SIGN_EXPIRE_TIME = 5 * 60 * 1000L;
private final static String REDIS_KEY_PREFIX = "APPID:";
private final static String REDIS_APPSIGN_PREFIX = "APPSIGN:FILESERVER:";
@Autowired
StringRedisTemplate redisTemplate;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String timestamp = request.getHeader("timestamp");
String appid = request.getHeader("appid");
String sign = request.getHeader("sign");
String method = request.getMethod();
if(StringUtils.equals("POST", method)) {
// 使用getInputStream后,controller方法参数获取不到
// 简单的思路就是将需要签名验证的参数放到header,暂时只用到header
// 若签名对参数加密,可以添加过滤器处理requestBody, 即二次转发数据
// InputStream inputStream = request.getInputStream();
// String param = FileUtil.readJsonFile(inputStream);
// log.info(param);
}
if (StringUtils.isBlank(appid) || StringUtils.isBlank(sign) || StringUtils.isBlank(timestamp)) {
returnJson(response, ReturnCode.INVALID_HEADER);
return false;
}
// 验证时间是否有效
long now = System.currentTimeMillis();
if (Math.abs(now - Long.valueOf(timestamp)) >= REDIS_SIGN_EXPIRE_TIME) {
returnJson(response, ReturnCode.INVALID_TIMESTAMP);
return false;
}
// 验证sign MD5(appid+appkey+timestamp) 此处,省事 默认secret为Md5(appId).substring(10,14)
String appSecret = "";
if (StringUtils.isBlank(appSecret)) {
appSecret = DigestUtils.md5DigestAsHex(appid.getBytes(StandardCharsets.UTF_8)).substring(10, 14);
}
String md5Str = appid + appSecret + timestamp;
log.info(md5Str);
String encode = DigestUtils.md5DigestAsHex(md5Str.getBytes(StandardCharsets.UTF_8));
log.info(encode);
if (!StringUtils.equals(sign, encode) || redisTemplate.hasKey(REDIS_APPSIGN_PREFIX + sign)) {
returnJson(response, ReturnCode.INVALID_SIGN);
return false;
}
redisTemplate.opsForValue()
.set(REDIS_APPSIGN_PREFIX + sign, "1", REDIS_SIGN_EXPIRE_TIME, TimeUnit.MILLISECONDS);
return HandlerInterceptor.super.preHandle(request, response, handler);
}
// 异常返回值;可以用全局异常替代,之前的文章里有
// ReturnCode 为枚举返回值
private void returnJson(HttpServletResponse response, ReturnCode returnCode){
PrintWriter writer = null;
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
try {
writer = response.getWriter();
writer.print(JSON.toJSONString(ResponseWrapper.error(returnCode)));
} catch (IOException e){
log.error(e.getMessage());
} finally {
if(writer != null){
writer.close();
}
}
}
}
2.添加全局配置
@Configuration
public class SignAuthWebConfig implements WebMvcConfigurer {
@Autowired
private SignAuthInterceptor signAuthInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 添加拦截规则
registry.addInterceptor(signAuthInterceptor).addPathPatterns("/test/**");
}
// 若对参数验签,此处添加过滤器配置;防止接口层不能获取RequestBody值
// @Bean
// public FilterRegistrationBean registrationBean() {
// FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(new MyFilter());
// filterRegistrationBean.addUrlPatterns("/*");
//
// return filterRegistrationBean;
// }
}