spring-cloud-gateway 功能-接口签名
基于webflux
场景
一般防止接口被大量请求,会使用接口签名的技术,如时间戳,随机数,加盐
签名方式
将请求参数key根据ASCCI排序,以 key=val&key=val
形式拼接 为 str, 然后将 str进行 salt 拼接
str.concat(salt)
为 s2, 然后对 s2进行 md5 即为 签名
HttpSignFilter
public class HttpSignFilter implements GlobalFilter, Ordered {
private SignService signService;
private static final String HEAD_SIGN = "sign";
public HttpSignFilter(SignService signService) {
this.signService = signService;
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
if (!exchange.getRequest().getHeaders().containsKey(HEAD_SIGN)) {
log.error("# lack of header[sign]: {}", exchange.getRequest().getPath().value());
throw new GatewaySignException(ErrorCode.GATEWAY_SIGN_ERROR.getMessage());
}
RequestParamContext context = RequestContextUtil.getRequestParamContext(exchange);
RequestContextUtil.logRequest(context, log);
SignInfo signInfo = signService.genSin(exchange);
String requestSign = exchange.getRequest().getHeaders().get(HEAD_SIGN).get(0);
log.info("params and sign: {}, request-sign: {}", JsonUtil.getInstance().toJsonString(signInfo), requestSign);
if (!verifySign(signInfo.getSign(), requestSign)) {
log.info("> HttpSignFilter sign error");
return Mono.error(new GatewaySignException(ErrorCode.GATEWAY_SIGN_ERROR.getMessage()));
}
return chain.filter(exchange);
}
private boolean verifySign(String sign, String requestSign) {
return sign.equals(requestSign);
}
@Override
public int getOrder() {
return HTTPSIGNFILTER_ORDER;
}
}
SignService
public interface SignService {
SignInfo genSin(ServerWebExchange exchange);
String getSecret(ServerWebExchange exchange);
}
public abstract class AbstractSignService implements SignService {
protected static final String AND = "&";
protected static final String EQ = "=";
public Map<String, String> extractParams(RequestParamContext context) {
Map<String, String> sortedMap = new TreeMap<>();
if (CollectionUtils.isEmpty(context.getQueryParams())) {
Map<String, String> collect = context.getQueryParams().entrySet().stream()
.filter(entry -> StrUtil.isNotBlank(entry.getValue()))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
sortedMap.putAll(collect);
}
if (StrUtil.isNotBlank(context.getBody()) && StrUtil.isNotBlank(context.getContentType())) {
if (context.getContentType().equals(MediaType.APPLICATION_JSON_VALUE)) {
//{a:b,c:d}
// TODO: 2023/7/11 处理数组?嵌套型?
if (context.getBody().startsWith("{")) {
Map<String, Object> map = JsonUtil.getInstance().parseObject(context.getBody(), Map.class);
for (Map.Entry<String, Object> entry : map.entrySet()) {
String val = Objects.toString(entry.getValue(), null);
if (Objects.isNull(val)) {
continue;
}
sortedMap.putIfAbsent(entry.getKey(), val);
}
}
} else if (context.getContentType().equals(MediaType.APPLICATION_FORM_URLENCODED_VALUE)) {
// a=b&c=d
String[] split = context.getBody().split(AND);
for (String s : split) {
String[] arr = s.split(EQ);
if (StrUtil.isBlank(arr[1])) {
continue;
}
sortedMap.putIfAbsent(arr[0], arr[1]);
}
}
}
return sortedMap;
}
}
public class DefaultSignServiceImpl extends AbstractSignService {
private String secret = "default_secret";
@Override
public SignInfo genSin(ServerWebExchange exchange) {
RequestParamContext context = RequestContextUtil.getRequestParamContext(exchange);
Map<String, String> sortedMap = extractParams(context);
SignInfo signInfo = new SignInfo();
signInfo.setData(sortedMap);
String sign = generateSign(getSecret(exchange), sortedMap);
signInfo.setSign(sign);
return signInfo;
}
@Override
public String getSecret(ServerWebExchange exchange) {
return secret;
}
public String generateSign(final String signKey, final Map<String, String> params) {
final String sign = params.keySet().stream()
.map(key -> String.join(EQ, key, params.get(key)))
.collect(Collectors.joining(AND)).trim().concat(signKey);
return DigestUtils.md5DigestAsHex(sign.getBytes()).toUpperCase();
}
}
其他
@Data
public class SignInfo {
//签名排序之后的参数
private Map<String,String> data;
//服务器端生成的签名
private String sign;
}
public class GatewaySignException extends RuntimeException{
public GatewaySignException(String message) {
super(message);
}
public GatewaySignException(String message, Throwable cause) {
super(message, cause);
}
}