在controller中直接对方法进行验证,最初想着简单省事;因为springboot貌似对数据请求二次解析,需要请求转发处理,后续补上;
1.自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SignValidate {
String name() default "";
}
2.解析注解,使用Before(),抛出异常 全局异常处理的方式;
若使用aroud(),依然会走方法体;
/***
* 验证sign
* appid appsecret Header
* timestamp 13位毫秒 Header
* sign MD5(appid+appkey+timestamp) 32位小写 Header
* 1.appkey最好存在服务端
* 2.RSA更安全
*/
@Slf4j
@Aspect
@Component
public class SignValidateAspect {
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:";
@Autowired
StringRedisTemplate redisTemplate;
@Autowired
SysApplicationService sysApplicationService;
@Pointcut("@annotation(com.xx.xx.annotation.SignValidate)")
public void pointCut() {
}
// 验证时间是否有效;验证sign是否有效;验证sign是否被使用过,防止被人截取一直请求
@Before(value = "pointCut()")
public void before(JoinPoint point) {
ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = sra.getRequest();
String timestamp = request.getHeader("timestamp");
String appid = request.getHeader("appid");
String sign = request.getHeader("sign");
if (StringUtils.isBlank(appid) || StringUtils.isBlank(sign) || StringUtils.isBlank(timestamp)) {
throw new SignValidateException(ReturnCodeEnum.INVALID_HEADER);
}
// 验证时间是否有效
long now = System.currentTimeMillis();
if (Math.abs(now - Long.valueOf(timestamp)) >= REDIS_SIGN_EXPIRE_TIME) {
// 过期
throw new SignValidateException(ReturnCodeEnum.INVALID_TIMESTAMP);
}
// 验证sign MD5(appid+appkey+timestamp) 也可以将appkey放在配置文件中
// String appSecret = redisTemplate.opsForValue().get(REDIS_KEY_PREFIX + appid);
String appSecret = sysApplicationService.getAppSecret(appid);
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)) {
throw new SignValidateException(ReturnCodeEnum.INVALID_SIGN);
}
// 保存sign
redisTemplate.opsForValue()
.set(REDIS_APPSIGN_PREFIX + sign, "1", REDIS_SIGN_EXPIRE_TIME, TimeUnit.MILLISECONDS);
}
}
3.自定义异常
@Data
public class SignValidateException extends RuntimeException {
private ReturnCodeEnum returnCodeEnum;
private SignValidateException() {}
public SignValidateException(ReturnCodeEnum returnCodeEnum) {
super(returnCodeEnum.getMsg());
this.returnCodeEnum = returnCodeEnum;
}
}
4.全局异常处理@RestcontrollerAdvice
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
/**
* @param e
* @return
* @Validated 验证签名
*/
@ResponseStatus(HttpStatus.OK)
@ExceptionHandler(SignValidateException.class)
public ResponseWrapper handler(SignValidateException e) throws IOException {
return ResponseWrapper.error(e.getReturnCode());
}
/**
* 系统异常
*
* @param e
* @return
*/
@ResponseStatus(HttpStatus.OK)
@ExceptionHandler(value = Exception.class)
public ResponseWrapper exceptionHandler(Exception e) {
log.info(e.getMessage(),e);
return ResponseWrapper.error(e.getMessage());
}
}
5.在需要验证的地方加@SignValidate