接着第一篇文章
提供三方API接口、调用第三方接口API接口、模拟API接口(一)通过signature签名验证,避免参数恶意修改
我们来继续优化:
/**
* 模拟后端校验签名
* @param request
* @param data
* @return
* @throws UnsupportedEncodingException
*/
@GetMapping("/sign/verifySign")
public boolean verifySign(HttpServletRequest request, @RequestParam Map<String, String> data) throws UnsupportedEncodingException {
String sign = request.getHeader("sign");
String signType = request.getHeader("sign_type");
return ApiUtil.verifySign(data, sign, signType);
}
背景:看这块代码,第一篇中的签名验证是在请求的业务逻辑中手动去调用进行signature验证的。这样代码显得太臃肿,因此采用注解与aop拦截的方式来实现参数校验,复用代码的同时,让程序变得简洁与高效。
接下来先通过AOP实现参数签名校验;
封装请求常量类
package com.atguigu.signcenter.constant;
/**
*
* 请求参数常量类
* @author: jd
* @create: 2024-08-04
*/
public class ReqParameterConstant {
/**
* 请求中的参数类型是head 头部参数的
*/
public static final String HEAD ="head";
/**
* 请求中的参数类型是form,是表单类型的
*/
public static final String FORM ="form";
}
自定义签名校验注解
package com.atguigu.signcenter.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(value = ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface VerifySign {
/**
* 参数类型 head 、 form
*/
String type();
}
aop拦截器
package com.atguigu.signcenter.component;
import com.atguigu.signcenter.annotation.VerifySign;
import com.atguigu.signcenter.constant.ReqParameterConstant;
import com.atguigu.signcenter.util.ApiUtil;
import com.sun.deploy.association.utility.AppUtility;
import io.swagger.annotations.Extension;
import lombok.Synchronized;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.annotation.Annotation;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
/**
*
* 切面类
* 先定义一个切面,这个切面主要的针对于controller中的所有方法,
* 再顶定义一个环绕通知,这个环绕通知主要是为了判断是否有自定义注解,@VerifySign ,如果有自定义注解的话,则会做一些业务处理、逻辑校验
* @author: jd
* @create: 2024-08-04
*/
@Aspect
@Slf4j
@Component
public class VerifySignAop {
/**
* 定义一个切面方法, 使用 AOP 的环绕通知去拦截这里标明的所有访问,拦截的是 controller 层中的所有的public方法,任何参数都可
*/
@Pointcut("execution(public * com.atguigu.signcenter.controller.*.*(..)))")
public void myPointCut(){
}
/**
* 环绕通知,主要用于判断对于切面方法中是否有某个注解,如果有的话,则做逻辑校验或者某些业务逻辑的处理
* @return
*/
@Around("myPointCut()")
@Synchronized
public Object aroundAOP(ProceedingJoinPoint joinPoint) throws Throwable {
//获得getSignature方法对象
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
// 判断方法上是否有注解 @VerifySign
VerifySign verifySign = methodSignature.getMethod().getDeclaredAnnotation(VerifySign.class);
//定义两个变量用于,接收参数签名字符串和参数签名的加密方式字符串
String sign = null;
String signType = null;
if(null!=verifySign){
// 获取上下文的请求
HttpServletRequest request = getRequest();
String type = verifySign.type();
log.info("type:{} " + type);
if(ReqParameterConstant.HEAD.equals(type)){
sign = request.getHeader("sign");
signType = request.getHeader("sign_type");
}
if(ReqParameterConstant.FORM.equals(type)){
sign = request.getParameter("sign");
signType = request.getParameter("sign_type");
}
if(StringUtils.isBlank(sign)){
response("该请求无签名!");
return null;
}
if(StringUtils.isBlank(signType)||!"RSA".equals(signType)){
response("无签名加密方式|签名加密方式不是RSA,所以不支持!");
return null;
}
log.info("sign:{} " , sign);
// 获取请求的所有参数 ,并将他形成一个HashMap,为了参数验证构建
Map<String, String> data = new HashMap();
Enumeration<String> parameterNames = request.getParameterNames();
while (parameterNames.hasMoreElements()){
String element = parameterNames.nextElement();
data.put(element,request.getParameter(element));
}
//参数验签
boolean verifyFlag = ApiUtil.verifySign(data, sign, signType);
if (!verifyFlag) {
response("无效的签名!");
return null;
} else {
log.info("有效的签名。");
}
}
//程序继续向下执行
Object proceed = joinPoint.proceed();
return proceed;
}
/**
*
* 获取容器上下文请求
* @return
*/
public HttpServletRequest getRequest() {
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = requestAttributes.getRequest();
return request;
}
/**
* 响应错误响应信息
* @param msg
*/
public void response(String msg) {
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletResponse response = requestAttributes.getResponse();
response.setHeader("Content-type", "text/html;charset=UTF-8");
try(PrintWriter writer = response.getWriter()) {
writer.println(msg);
} catch (IOException e) {
e.printStackTrace();
}
}
}
控制器的业务方法添加签名校验注解
@VerifySign(type = "head")
@GetMapping("sign/testAnnotation")
@ResponseBody
public String test(@RequestParam Map<String,String> data){
// 模拟业务逻辑
// TODO Something
log.info("做一些业务,业务模拟");
return "成功通过AOP切面中的校验,执行了业务逻辑";
}
测试
需要携带上参数,不然有签名没用,因为签名就是为了验证参数是否发生了变化
参数不带签名时候:
http://localhost:8025/sign/testAnnotation?name=zhangsan&age=100
可以看到,验证提示 该请求无签名! ,而且也没有打印controller中模拟业务的代码(“做一些业务,业务模拟”)。
携带上签名参数
测试结果:
IDEA控制台打印结果:
至此完成了,AOP实现签名验证的代码实现。
码字不易,不足之处还请大家多多指教~