前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >SpringCloud-gateway-nacos-swagger踩坑记录

SpringCloud-gateway-nacos-swagger踩坑记录

作者头像
doper
发布2022-09-26 17:50:50
9100
发布2022-09-26 17:50:50
举报
文章被收录于专栏:后台技术杂项笔记

SpringCloud-gateway-nacos-swagger

gateway聚合各服务模块,以及遇到的坑。

1. 背景

image-20220412144842381
image-20220412144842381

​ 在gateway中集成各个模块,然后接入swagger方便测试各模块接口,其中sunshine-common是放入一些通用组件和配置的,swagger的配置就在这里面。sunshine-gateway即网关模块,在网关通过nacos服务注册发现,将请求路由到各个模块中。

2. 具体代码

2.1. swagger的配置

swagger的配置放在common模块中了,这里只是参考了一般的配置,并且加了Token的认证Header,之后各个模块只要引入了common模块并扫描就默认配置了swagger,具体如下:

代码语言:javascript
复制
/**
 * Swagger2API文档的配置
 */
@Configuration
@EnableSwagger2
public class Swagger2Config {
    @Bean
    public Docket createRestApi() {

        new ParameterBuilder().name("real_source_ip").description("本机局域网IP").
                modelRef(new ModelRef("string")).parameterType("header").required(false).build();

        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                //为当前包下controller生成API文档
                .apis(RequestHandlerSelectors.basePackage("com.huashi.sunshine"))
                //为有@Api注解的Controller生成API文档
                .apis(RequestHandlerSelectors.withClassAnnotation(Api.class))
                //为有@ApiOperation注解的方法生成API文档
                .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
                .paths(PathSelectors.any())
                .build()
                .securitySchemes(securitySchemes())
                .securityContexts(securityContexts());
    }

    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("sunshine")
                .description("阳光影院接口文档")
                .contact("xxy")
                .version("1.0.0")
                .build();
    }

    private List<ApiKey> securitySchemes() {
        // 设置请求头信息
        // 这里是为了在测试请求中添加认证token用的,这里可不管
        List<ApiKey> result = new ArrayList<>();
        ApiKey apiKey = new ApiKey("Authorization", "Authorization", "header");
        result.add(apiKey);
        return result;
    }

    private List<SecurityContext> securityContexts() {
        //设置需要登录认证的路径
        List<SecurityContext> result = new ArrayList<>();
        result.add(getContextByPath("/api/user/.*"));
        result.add(getContextByPath("/auth/.*"));
        result.add(getContextByPath("/sunshine-movie-comment/.*"));
        result.add(getContextByPath("/sunshine-cinema-comment/.*"));
        return result;
    }

    private SecurityContext getContextByPath(String pathRegex){
        return SecurityContext.builder()
                .securityReferences(defaultAuth())
                .forPaths(PathSelectors.regex(pathRegex))
                .build();
    }

    private List<SecurityReference> defaultAuth() {
        List<SecurityReference> result = new ArrayList<>();
        AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
        AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
        authorizationScopes[0] = authorizationScope;
        result.add(new SecurityReference("Authorization", authorizationScopes));
        return result;
    }

}

2.2. gateway聚合各模块接口

​ 由于系统是采用nacos做服务中心的,因此gateway要去服务中心拉取各个服务的api信息,生成文档。springfox-swagger提供的分组接口是swagger-resource,重写该接口,具体代码如下:

代码语言:javascript
复制
@Component
@Primary
@AllArgsConstructor
public class SwaggerProvider implements SwaggerResourcesProvider {
    public static final String API_URI = "/v2/api-docs";
    private final RouteLocator routeLocator;

    @Value("${spring.application.name}")
    private String self;

    public SwaggerProvider(RouteLocator routeLocator) {
        this.routeLocator = routeLocator;
    }

    @Override
    public List<SwaggerResource> get() {
        List<SwaggerResource> resources = new ArrayList<>();
        List<String> routeHosts = new ArrayList<>();
        // 由于我的网关采用的是负载均衡的方式,因此我需要拿到所有应用的serviceId
        // 获取所有可用的host:serviceId
        routeLocator.getRoutes().filter(route -> route.getUri().getHost() != null)
                .filter(route -> !self.equals(route.getUri().getHost()))	//排除自己
                .subscribe(route -> routeHosts.add(route.getUri().getHost()));

        // 记录已经添加过的server,存在同一个应用注册了多个服务在nacos上
        Set<String> dealed = new HashSet<>();
        routeHosts.forEach(instance -> {
            // 注意,本次坑出现在这
            // 拼接url,样式为/serviceId/v2/api-info,当网关调用这个接口时,会自动通过负载均衡寻找对应的主机
            String url = "/" + instance + API_URI;
            if (!dealed.contains(url)) {
                dealed.add(url);
                SwaggerResource swaggerResource = new SwaggerResource();
                swaggerResource.setUrl(url);
                swaggerResource.setName(instance);
                resources.add(swaggerResource);
            }
        });
        return resources;
    }

}

swagger接口:

代码语言:javascript
复制
@RestController
public class SwaggerHandler {

    @Autowired(required = false)
    private SecurityConfiguration securityConfiguration;

    @Autowired(required = false)
    private UiConfiguration uiConfiguration;

    private final SwaggerResourcesProvider swaggerResources;

    @Autowired
    public SwaggerHandler(SwaggerResourcesProvider swaggerResources) {
        this.swaggerResources = swaggerResources;
    }


    @GetMapping("/swagger-resources/configuration/security")
    public Mono<ResponseEntity<SecurityConfiguration>> securityConfiguration() {
        return Mono.just(new ResponseEntity<>(
                Optional.ofNullable(securityConfiguration).orElse(SecurityConfigurationBuilder.builder().build()), HttpStatus.OK));
    }

    @GetMapping("/swagger-resources/configuration/ui")
    public Mono<ResponseEntity<UiConfiguration>> uiConfiguration() {
        return Mono.just(new ResponseEntity<>(
                Optional.ofNullable(uiConfiguration).orElse(UiConfigurationBuilder.builder().build()), HttpStatus.OK));
    }

    @GetMapping("/swagger-resources")
    public Mono<ResponseEntity> swaggerResources() {
        return Mono.just((new ResponseEntity<>(swaggerResources.get(), HttpStatus.OK)));
    }

    @GetMapping("/")
    public Mono<ResponseEntity> swaggerResourcesN() {
        return Mono.just((new ResponseEntity<>(swaggerResources.get(), HttpStatus.OK)));
    }

    @GetMapping("/csrf")
    public Mono<ResponseEntity> swaggerResourcesCsrf() {
        return Mono.just((new ResponseEntity<>(swaggerResources.get(), HttpStatus.OK)));
    }
}

2.3. gateway路由配置

​ 系统中会对接口进行token认证,但有些接口是不需要Header Token的,比如用户登陆注册,还有就是本文中的swagger接口。在2.2中看到每个服务模块的暴露swagger接口都为{服务名}/v2/api-docs的格式,这些路径都必须走自定义的不认证Token过滤器。具体的相关配置代码如下

代码语言:javascript
复制
spring:
  cloud:
    gateway:
      routes:
        #放行swagger配置,匹配具体路径,因为swagger发请求会把/{服务名}带上,这样子会走了swagger的过滤配置从而跳过了token鉴权
        - id: sunshine-user-swagger-ui
          uri: lb://sunshine-user
          predicates:
            - Path=/sunshine-user/v2/api-docs		# user模块的接口信息
          filters:
# 访问时跳过第一部分,即真实路径是user模块里面的'/v2/api-docs',因为注册时加了服务名作为标识,防止各个服务的api信息都为'/v2/api-docs'冲突
            - StripPrefix=1							
            - IgnoreGlobalFilter
       
        # 用户不需要权限api
        - id: sunshine-user-public
          uri: lb://sunshine-user
          predicates: # 路由断言
            - Path=/user/**			# 不需要token认证的路径
          filters:
            - IgnoreGlobalFilter
        - id: sunshine-user-public
          uri: lb://sunshine-user
          predicates: # 路由断言
            - Path=/sunshine-user/user/**	# 同样是不需要token认证的路径,为了兼容swagger的接口测试添加的
          filters:
            - StripPrefix=1
            - IgnoreGlobalFilter
        # 用户需要权限api
        - id: sunshine-user-auth
          uri: lb://sunshine-user
          predicates: # 路由断言
            - Path=/api/user/**		# 需要token认证的路径
        - id: sunshine-user-auth
          uri: lb://sunshine-user
          predicates: # 路由断言
            - Path=/sunshine-user/api/user/**	# 同样是需要token认证的路径,为了兼容swagger的接口测试添加的
          filters:
            - StripPrefix=1

​ 上面只列出了user模块的配置,实际配置文件还有其他模块的配置,但整体大同小异,具体效果如下:

image-20220414104040492
image-20220414104040492

3. 一些小坑

​ 以user模块为例,在实际的业务代码中,需要认证的请求路径为/api/user/**,但实际在swagger测试这些请求时都会默认在路径前面带上服务名,这里即变为/sunshine-user/api/user/**

image-20220414104459837
image-20220414104459837

​ 但在一开始为了能够各个模块的swagger-api信息不被全局过滤器过滤,这里以user模块为例,配置了如下信息:

代码语言:javascript
复制
spring:
  cloud:
    gateway:
      routes:
        #放行swagger配置,匹配具体路径,因为swagger发请求会把/{服务名}带上,这样子会走了swagger的过滤配置从而跳过了token鉴权
        - id: sunshine-user-swagger-ui
          uri: lb://sunshine-user
          predicates:
            - Path=/sunshine-user/**		# user模块的接口信息
          filters:
            - StripPrefix=1							
            - IgnoreGlobalFilter

​ 这就导致了在swagger中测试接口时所有的请求都走了上面这个路由配置,实际能运行,因此这里还配置了- StripPrefix=1,只是每次请求都跳过了全局过滤器,即跳过token认证。因此后面为了解决这个问题,修改配置文件的修改如下:

代码语言:javascript
复制
spring:
  cloud:
    gateway:
      routes:
        - id: sunshine-user-swagger-ui
          uri: lb://sunshine-user
          predicates:
            - Path=/sunshine-user/v2/api-docs		# 这里改成了api接口的具体路径
          filters:
            - StripPrefix=1							
            - IgnoreGlobalFilter
       
        # 用户不需要权限api
        - id: sunshine-user-public
          uri: lb://sunshine-user
          predicates: # 路由断言
            - Path=/user/**			# 实际业务的请求路由
          filters:
            - IgnoreGlobalFilter
        - id: sunshine-user-public
          uri: lb://sunshine-user
          predicates: # 路由断言
            - Path=/sunshine-user/user/**	# 这里添加了swagger发送请求时的路由项
          filters:
            - StripPrefix=1
            - IgnoreGlobalFilter

​ 开发环境中为了能够测试接口就可以这么处理,但应该也有更好的方法。但生产环境中各模块的接口信息是不可以暴露出来的,因此不配置这些也无所谓了。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-04-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • SpringCloud-gateway-nacos-swagger
  • 1. 背景
  • 2. 具体代码
    • 2.1. swagger的配置
      • 2.2. gateway聚合各模块接口
        • 2.3. gateway路由配置
        • 3. 一些小坑
        相关产品与服务
        对象存储
        对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档