闽公网安备 35020302035485号
步骤3:服务端比对 sign1 和 sign2 的值,若不一致,则认定为被篡改,判定为非法请求。
.针对查询的接口:黑客一般是重点攻击慢查询接口,例如一个慢查询接口1s,只要黑客发起重放攻击,就必然造成系统被拖垮,数据库查询被阻塞死。
private static String getAppKey() {
long num = IdUtils.nextId();
StringBuilder sb = new StringBuilder();
do {
int remainder = (int) (num % 62);
sb.insert(0, BASE62_CHARACTERS.charAt(remainder));
num /= 62;
} while (num != 0);
return sb.toString();
}
通过这个算法生成的 AppId 和 AppSecret 形如:appKey=6iYWoL2hBk9, appSecret=5de8bc4d8278ed4f14a3490c0bdd5cbe369e8ec9
//认证接口
public interface ApiAuthenticator {
AuthenticatorResult auth(ServerWebExchange request);
}
// 堆代码 duidaima.com
//具体实现
@Slf4j
public class ProtectedApiAuthenticator implements ApiAuthenticator {
...
}
3.2 网关过滤器@Component
@Slf4j
public class ApiAuthenticatorFilter implements GlobalFilter, Ordered {
...
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 获取认证逻辑
ApiAuthenticator apiAuthenticator = getApiAuthenticator(rawPath);
AuthenticatorResult authenticatorResult = apiAuthenticator.auth(exchange);
if (!authenticatorResult.isResult()) {
return Mono.error(new HttpServerErrorException(
HttpStatus.METHOD_NOT_ALLOWED, authenticatorResult.getMessage()));
}
return chain.filter(exchange);
}
/**
* 确定认证策略
* @param rawPath 请求路径
*/
private ApiAuthenticator getApiAuthenticator(String rawPath) {
String[] parts = rawPath.split("/");
if (parts.length >= 4) {
String parameter = parts[3];
return switch (parameter) {
case PROTECT_PATH -> new ProtectedApiAuthenticator();
case PRIVATE_PATH -> new PrivateApiAuthenticator();
case PUBLIC_PATH -> new PublicApiAuthenticator();
case DEFAULT_PATH -> new DefaultApiAuthenticator();
default -> throw new IllegalStateException("Unexpected value: " + parameter);
};
}
return new DefaultApiAuthenticator();
}
}
上面提到过,不同类型的服务其接口认证不一样,为了便于区分,可以规定对于外部请求都增加一个特定的请求前缀 /pt/,如 apigw.xxx.com/order-service/api/pt/creadeOrder。这样在过滤器内部就需要通过 getApiAuthenticator() 方法确定认证逻辑。@Slf4j
public class ProtectedApiAuthenticator implements ApiAuthenticator {
@Override
public AuthenticatorResult auth(ServerWebExchange exchange) {
// 1. 校验参数
boolean checked = preAuthenticationCheck(requestHeader);
if (!checked) {
return new AuthenticatorResult(false, "请携带正确参数访问");
}
// 2 . 重放校验
// 判断timestamp时间戳与当前时间是否操过60s(过期时间根据业务情况设置),如果超过了就提示签名过期。
long now = System.currentTimeMillis() ;
if (now - Long.parseLong(requestHeader.getTimestamp()) > 60000) {
return new AuthenticatorResult(false, "请求超时,请重新访问");
}
// 3. 判断nonce
boolean nonceExists = distributedCache.hasKey(NONCE_KEY + requestHeader.getNonce());
if (nonceExists) {
return new AuthenticatorResult(false, "请勿重复提交请求");
} else {
distributedCache.put(NONCE_KEY + requestHeader.getNonce(), requestHeader.getNonce(), 60000);
}
// 4. 签名校验
SortedMap<String, Object> requestBody = CachedRequestUtil.resolveFromBody(exchange);
String sign = buildSign(requestHeader,requestBody);
if(!sign.equals(requestHeader.getSign())){
return new AuthenticatorResult(false, "签名错误");
}
return new AuthenticatorResult(true, "");
}
这样的写法虽然能够完成校验逻辑,但稍显不够优雅。在这种场景中,使用设计模式中的责任链模式是非常合适的选择。通过责任链模式,将校验逻辑分解为多个责任链节点,每个节点专注于一个方面的校验,使得代码更加清晰和易于维护。@Slf4j
public class ProtectedApiAuthenticator implements ApiAuthenticator {
@Override
public AuthenticatorResult auth(ServerWebExchange exchange) {
...
//构建校验对象
ProtectedRequest protectedRequest = ProtectedRequest.builder()
.requestHeader(requestHeader)
.requestBody(requestBody)
.build();
//责任链上下文
SecurityVerificationChain securityVerificationChain = SpringBeanUtils.getInstance().getBean(SecurityVerificationChain.class);
return securityVerificationChain.handler(protectedRequest);
}
}
3.4 基于责任链的认证实现public interface SecurityVerificationHandler extends Ordered {
/**
* 请求校验
*/
AuthenticatorResult handler(ProtectedRequest protectedRequest);
}
3.4.2 实现参数校验逻辑@Component
public class RequestParamVerificationHandler implements SecurityVerificationHandler {
@Override
public AuthenticatorResult handler(ProtectedRequest protectedRequest) {
boolean checked = checkedHeader(protectedRequest.getRequestHeader());
if(!checked){
return new AuthenticatorResult(false,"请携带正确的请求参数");
}
return new AuthenticatorResult(true,"");
}
private boolean checkedHeader(RequestHeader requestHeader) {
return Objects.nonNull(requestHeader.getAppId()) &&
Objects.nonNull(requestHeader.getSign()) &&
Objects.nonNull(requestHeader.getNonce()) &&
Objects.nonNull(requestHeader.getTimestamp());
}
@Override
public int getOrder() {
return 1;
}
}
3.4.3 实现nonce的校验@Component
public class NonceVerificationHandler implements SecurityVerificationHandler {
private static final String NONCE_KEY = "x-nonce-";
@Value("${dailymart.sign.timeout:60000}")
private long expireTime ;
@Resource
private DistributedCache distributedCache;
@Override
public AuthenticatorResult handler(ProtectedRequest protectedRequest) {
String nonce = protectedRequest.getRequestHeader().getNonce();
boolean nonceExists = distributedCache.hasKey(NONCE_KEY + nonce);
if (nonceExists) {
return new AuthenticatorResult(false, "请勿重复提交请求");
} else {
distributedCache.put(NONCE_KEY + nonce, nonce, expireTime);
return new AuthenticatorResult(true, "");
}
}
@Override
public int getOrder() {
return 3;
}
}
3.4.4 实现签名认证@Component
@Slf4j
public class SignatureVerificationHandler implements SecurityVerificationHandler {
@Override
public AuthenticatorResult handler(ProtectedRequest protectedRequest) {
//1. 服务端按照规则重新签名
String serverSign = sign(protectedRequest);
log.info("服务端签名结果: {}", serverSign);
String clientSign = protectedRequest.getRequestHeader().getSign();
// 2、获取客户端传递的签名
log.info("客户端签名: {}", clientSign);
if (!Objects.equals(serverSign,clientSign)) {
return new AuthenticatorResult(false, "请求签名无效");
}
return new AuthenticatorResult(true, "");
}
/**
* 服务端重建签名
* @param protectedRequest 请求体
* @return 签名结果
*/
private String sign(ProtectedRequest protectedRequest) {
RequestHeader requestHeader = protectedRequest.getRequestHeader();
String appId = requestHeader.getAppId();
String appSecret = getAppSecret(appId);
// 1、 按照规则对数据进行签名
SortedMap<String, Object> requestBody = protectedRequest.getRequestBody();
requestBody.put("app_id",appId);
requestBody.put("nonce_number",requestHeader.getNonce());
requestBody.put("request_time",requestHeader.getTimestamp());
StringBuilder signBuilder = new StringBuilder();
for (Map.Entry<String, Object> entry : requestBody.entrySet()) {
signBuilder.append(entry.getKey()).append("=").append(entry.getValue()).append("&");
}
signBuilder.append("appSecret=").append(appSecret);
return DigestUtils.md5DigestAsHex(signBuilder.toString().getBytes()).toUpperCase();
}
@Override
public int getOrder() {
return 4;
}
}
3.4.5 责任链上下文@Component
@Slf4j
public class SecurityVerificationChain {
@Resource
private List<SecurityVerificationHandler> securityVerificationHandlers;
public AuthenticatorResult handler(ProtectedRequest protectedRequest){
AuthenticatorResult authenticatorResult = new AuthenticatorResult(true,"");
for (SecurityVerificationHandler securityVerificationHandler : securityVerificationHandlers) {
AuthenticatorResult result = securityVerificationHandler.handler(protectedRequest);
// 有一个校验不通过理解返回
if(!result.isResult()){
return result;
}
}
return authenticatorResult;
}
}
组合所有的校验逻辑,任意一个校验逻辑不通过则直接返回。