通过网关统一鉴权openApi,业务层只用关注具体业务,防止重放攻击、验证签名等


import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.google.gson.Gson;
import com.****.MD5Util;
import com.****.SignUtils;
import com.****.Result;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.cloud.gateway.support.ipresolver.XForwardedRemoteAddressResolver;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Date;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.TimeUnit;

import static org.springframework.cloud.gateway.support.GatewayToStringStyler.filterToStringCreator;

/**
 * @author hk
 * @See com.kbd.watermelon.scheduleTask.EnterpriseApiKeyTask
 */
@Slf4j
@Component
public class AuthOpenApiGatewayFilterFactory  extends AbstractGatewayFilterFactory<Object> {
    public static final String PREFIX ="ota:enterprise:api:";

    private static final String CACHE_REQUEST_BODY_OBJECT_KEY = "cachedRequestBodyObject";
    @Autowired
    private RedisTemplate<String,Object> redisTemplate;

    public GatewayFilter apply() {
        return apply(o -> {
        });
    }

    @Override
    public GatewayFilter apply(Object config) {

        return new GatewayFilter() {
            @Override
            public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
                XForwardedRemoteAddressResolver resolver = XForwardedRemoteAddressResolver.maxTrustedIndex(1);
                InetSocketAddress inetSocketAddress = resolver.resolve(exchange);
                String userIp = inetSocketAddress.getAddress().getHostAddress();
                HttpHeaders httpHeaders = exchange.getRequest().getHeaders();
                log.info(new Gson().toJson(httpHeaders));
            
                String body =exchange.getAttribute(CACHE_REQUEST_BODY_OBJECT_KEY);
                log.info("userIp:{},body:{}",userIp,body);
                TreeMap<String,Object> treeMap = JSONObject.parseObject(body, TreeMap.class);
                String timestamp = (String) treeMap.get(SignUtils.TIMESTAMP);
                if(StringUtils.isBlank(timestamp)){
                    log.error("timestamp不能为空");
                    return getErrorMono(exchange, "timestamp不能为空");
                }
                if(DateUtil.between(new Date(),new Date(Long.parseLong(timestamp)), DateUnit.MINUTE)>5){
                    log.error("超时");
                    return getErrorMono(exchange, "超时");
                }
                String accesskey = (String) treeMap.get(SignUtils.ACCESS_KEY);
                String nonce =(String) treeMap.get(SignUtils.NONCE);
                String sign = (String) treeMap.get(SignUtils.SIGN);
                if(StringUtils.isBlank(accesskey)){
                    log.error("accesskey超时");
                    return getErrorMono(exchange, "accesskey超时");
                }
                if(StringUtils.isBlank(nonce)){
                    log.error("nonce超时");
                    return getErrorMono(exchange, "nonce超时");
                }
                if(StringUtils.isBlank(sign)){
                    log.error("sign超时");
                    return getErrorMono(exchange, "sign超时");
                }
                if(!redisTemplate.opsForValue().setIfAbsent(sign,nonce,6, TimeUnit.MINUTES)){
                    log.error("超时");
                    return getErrorMono(exchange, "超时");
                }
                Map map = (Map) redisTemplate.opsForValue().get(PREFIX+accesskey);
                if(map==null||map.get(SignUtils.SECRET_KEY)==null||SignUtils.IP==null){
                    log.error("查询不到密钥{}",accesskey);
                    return getErrorMono(exchange, "查询不到密钥");
                }
                if(!Arrays.asList((map.get(SignUtils.IP) + "").split(",")).contains(userIp)){
                    log.error("ip不在白名单");
                    return getErrorMono(exchange, "ip不在白名单");
                }
                if(!SignUtils.validSign(treeMap,map.get(SignUtils.SECRET_KEY)+"")){
                    log.error("验证签名失败");
                    return getErrorMono(exchange, "验证签名失败");
                }
                return chain.filter(exchange);
            }

            @Override
            public String toString() {
                return filterToStringCreator(AuthOpenApiGatewayFilterFactory.this).toString();
            }
        };
    }

    private Mono<Void> getErrorMono(ServerWebExchange exchange, String msg) {
        exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
        exchange.getResponse().getHeaders().setContentType(MediaType.APPLICATION_JSON);
        String fastResult = JSON.toJSONString(Result.fail(msg));
        DataBuffer dataBuffer = exchange.getResponse().bufferFactory().allocateBuffer().write(fastResult.getBytes(StandardCharsets.UTF_8));
        return exchange.getResponse().writeWith(Mono.just(dataBuffer));
    }

    public static void main(String[] args) {
        TreeMap<String,String> treeMap = new TreeMap<>();
        treeMap.put("timestamp",System.currentTimeMillis()+"");
        treeMap.put("nonce","1234567");
        treeMap.put("accessKey","1234");
        treeMap.put("a","a");
        treeMap.put("b","b");
        StringBuilder stringBuilder = new StringBuilder();
        for (Map.Entry<String, String> stringStringEntry : treeMap.entrySet()) {
            if(!stringStringEntry.getKey().equals("sign")){
                stringBuilder.append(stringStringEntry.getKey());
                stringBuilder.append("=");
                stringBuilder.append(stringStringEntry.getValue());
                stringBuilder.append("&");
            }
        }
        stringBuilder.append("secretKey=");
        stringBuilder.append("123");
        System.out.println(stringBuilder.toString());
        String md5 = MD5Util.md5(stringBuilder.toString());
        treeMap.put("sign",md5);
        System.out.println(JSONObject.toJSON(treeMap));
    }
}

SignUtils

public class SignUtils{
	public static final String SIGN = "sign";
	public static final String NONCE = "nonce";
	public static final String ACCESS_KEY = "accessKey";
	public static final String TIMESTAMP = "timestamp";
	public static final String SECRET_KEY = "secretKey";
	public static final String IP = "ip";
	public static boolean  validSign(TreeMap<String,Object> treeMap,String secret){
       StringBuilder stringBuilder = new StringBuilder();
       for(Map.Entry<String,Object> s :treeMap.entrySet()){
          if(!s.getKey().equals(SIGN)){
    		stringBuilder.append(s.getKey());
    		stringBuilder.append("=");
    		stringBuilder.append(s.getValue());
    		stringBuilder.append("&");
		  }
       }
       stringBuilder.append(SECRET_KEY );
       stringBuilder.append("=");
       stringBuilder.append(secret);
       String md5 =MD5Util.md5(stringBuilder.toString());
       return md5.equalsIgnoreCase(treeMap.get("sign")+"");
    }
}
import java.util.function.Predicate;
@Configuration
public class PredicateConfig{
  @Bean
  public Predicate bodPredicate(){
    return new Predicate(){
		@Override
		public boolean test(Object o ){return true;}
	}
  }
}

最后是yml 网关的配置

id: id
url: http://***
predicates:
 - Path=/openApi/**
 - name: ReadBody
   args:
     inClass: '#{T(String)}'
     predicate: '#{@bodyPredicate}'
filters:
 - AuthOpenApi
Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐