spring cloud gateway开发openApi统一鉴权

该博客介绍了如何通过网关进行API的统一鉴权,实现业务层专注于具体业务。文中提到的关键步骤包括:检查请求参数(timestamp、nonce、accessKey、sign),验证签名的有效性,防止重放攻击,并使用Redis缓存进行限流控制。同时,还涉及了错误处理机制,返回自定义的错误信息。
摘要由CSDN通过智能技术生成

通过网关统一鉴权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
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Cloud Gateway 2.3.12 是 Spring Cloud 的一个组件,它是一个高度可配置的 HTTP 代理服务器,用于微服务架构中的API网关。要将 Swagger(通常指Swagger UI)集成到 Spring Cloud Gateway 中,你可以按照以下步骤操作: 1. 添加依赖:在你的项目中添加 Swagger 和 Swagger UI 的依赖。如果你使用 Maven,可以在 `pom.xml` 文件中添加: ```xml <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-boot-starter-web</artifactId> <version>3.x.x</version> <!-- 使用最新稳定版本 --> </dependency> <dependency> <groupId>io.github.swagger2markup</groupId> <artifactId>swagger2markup</artifactId> <version>1.0.0-M7</version> </dependency> ``` 注意:替换 `<version>` 部分为当前最稳定的 Swagger 版本。 2. 创建配置:在 `application.yml` 或 `application.properties` 中配置 Swagger 的信息,例如: ```yaml # application.yml springdoc: webFlux: openapi: info: title: 'Your API Title' version: 'v1' description: 'Description of your API' # 如果想要自定义路径和端点,可以添加这些配置 springdoc.webFlux.path=/docs, /api-docs springdoc.webFlux.endpoint-path=/v2/api-docs # Swagger UI 配置 springdoc.swagger-ui.enabled=true ``` 3. 注解控制器:在你的 API 控制器上添加 Swagger 注解,如 `@ApiOperation` 和 `@ApiResponses`。这会告诉 Swagger 生成文档的内容。 4. 自动扫描:确保你在 Spring Boot 应用中启用了自动扫描功能,以便 Swagger 能够发现并处理包含注解的类。 5. 启动应用:运行你的 Spring Boot 应用,访问 `/docs` 或者根据之前配置的路径来查看 Swagger UI 页面。 相关问题-- 1. Spring Cloud Gateway 的 Swagger 配置文件在哪里? 2. 如何在 Spring Cloud Gateway 中禁用 Swagger UI? 3. Spring Cloud Gateway 中如何设置 Swagger 的基本授权信息?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值