简介
本文将介绍如何在Spring Cloud
微服务中使用Swagger
网关来统一管理所有微服务的接口文档,并通过Spring Security
实现登录后才能访问Swagger
文档,以确保接口数据的安全访问。
在开始之前,需要假设你已经完成了Spring Cloud Gateway
的相关配置,并且已经了解了基本的网关配置知识。本文将不再赘述Gateway
的配置,只介绍在此基础上如何配置Swagger
来管理所有微服务,并通过账号密码来管理Swagger的访问。
一.网关中需要引入swagger依赖
本教程spring boot版本为2.0.9.RELEASE,spring cloud版本为Finchley.SR4
引入swagger pom依赖:
com.spring4all
swagger-spring-boot-starter
2.0.2
二.通过Swagger配置类,获取所有微服务文档
此类会在访问swagger ui时调用,来获取所有swagger resources资源,包括了所有微服务的接口文档地址。
package cn.demo.conf;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
import springfox.documentation.swagger.web.SwaggerResource;
import springfox.documentation.swagger.web.SwaggerResourcesProvider;
import javax.annotation.Resource;
import java.util.*;
@Slf4j
@Component
@Primary
@AllArgsConstructor
public class SwaggerResourceConfig implements SwaggerResourcesProvider {
/**
* swagger2默认的url后缀(v2或v3)
*/
private static final String SWAGGER2URL = "/v2/api-docs";
/**
* 网关路由
*/
@Resource
private final RouteLocator routeLocator;
/**
* 汇总所有微服务的swagger文档路径(访问swagger页面时,会调用此方法获取docs)
*
* @return
*/
@Override
public List get() {
List resources = new ArrayList();
// 从网关路由中获取所有服务的host名
List routeHosts = new LinkedList();
routeLocator.getRoutes().filter(route -> route.getUri().getHost() != null)
.subscribe(route -> routeHosts.add(route.getUri().getHost()));
// 将文档列表排序,服务多的时候以免显得混乱
Collections.sort(routeHosts);
// 不需要展示Swagger的服务列表,在swagger ui页面右上角下拉框中,将不再展示(改成你自己不需要展示的服务)
List ignoreServers = Arrays
.asList( "sever1", "server2");
// 记录已经添加过的微服务(有些服务部署多个节点,过滤掉重复的)
List docsUrls = new LinkedList();
for (String host : routeHosts) {
if (ignoreServers.contains(host) ) {
//排除忽略服务名
continue;
}
// 拼接swagger docs的url,示例:/server1/v2/api-docs
String url = "/" + host + SWAGGER2URL;
// 排除掉重复
if (!docsUrls.contains(url)) {
docsUrls.add(url);
SwaggerResource swaggerResource = new SwaggerResource();
swaggerResource.setUrl(url);
swaggerResource.setName(host);
resources.add(swaggerResource);
}
}
return resources;
}
}
上述代码中,ignoreServers
为忽略服务的列表,我们通过网关路由,可以将所有经过网关管理甚至注册到同一个注册中心的服务都能获取到,但有些服务是不需要提供接口的,或者不需要展示swagger文档,我们可以在此列表中将其排除,排除后的服务不再显示在swagger UI 页面中,如下图:
三.通过swagger controller增加获取swagger资源和配置的接口
此类逻辑不需要修改,直接复制即可:
package cn.demo.controller;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;
import springfox.documentation.swagger.web.*;
@RestController
public class SwaggerHandlerController {
@Autowired(required = false)
private SecurityConfiguration securityConfiguration;
@Autowired(required = false)
private UiConfiguration uiConfiguration;
private final SwaggerResourcesProvider swaggerResources;
@Autowired
public SwaggerHandlerController(SwaggerResourcesProvider swaggerResources) {
this.swaggerResources = swaggerResources;
}
@GetMapping("/swagger-resources/configuration/security")
public Mono> securityConfiguration() {
return Mono.just(new ResponseEntity(
Optional.ofNullable(securityConfiguration).orElse(SecurityConfigurationBuilder.builder().build()), HttpStatus.OK));
}
@GetMapping("/swagger-resources/configuration/ui")
public Mono> uiConfiguration() {
return Mono.just(new ResponseEntity(
Optional.ofNullable(uiConfiguration).orElse(UiConfigurationBuilder.builder().build()), HttpStatus.OK));
}
@GetMapping("/swagger-resources")
public Mono swaggerResources() {
return Mono.just((new ResponseEntity(swaggerResources.get(), HttpStatus.OK)));
}
// @GetMapping("/")
public Mono swaggerResourcesN() {
return Mono.just((new ResponseEntity(swaggerResources.get(), HttpStatus.OK)));
}
//@GetMapping("/csrf")
public Mono swaggerResourcesCsrf() {
return Mono.just((new ResponseEntity(swaggerResources.get(), HttpStatus.OK)));
}
}
四.网关的路由配置中透传所有swagger资源地址
这个地方大家需要根据自己网关内容进行配置了,每个人的网关中,都有些URL需要透传,即不需要网关做拦截,那么把swagger 服务名进行透传即可。
举例:
swagger需要通过http://10.1.1.1:88/server1/v2/api-docs
来获取server1
微服务的接口文档,那么如果你网关拦截了此路径,那么swagger就无法显示此文档了。所有我们需要让网关放行此路径。默认网关是放行的,但有些网关会把所有接口返回值进行封装,所以就需要透传。
格式示例:
String ignoreUrl = "/server1/**";
配置完了这些,如果你各个微服务中接口,已经加上了swagger注解的话,那么就可以启动网关,来测试下是否能访问swagger了,网关地址示例:http://10.1.1.1:88/swagger-ui/index.html
,将ip和端口换成你自己的即可。
五.给Swagger增加用户名密码登陆,保证文档安全
这一步是可选的,生产环境我们一般是不允许使用swagger 的,但如果你们公司对接口文档安全要求严格的话,甚至开发环境都要加密可以继续下面的配置,来实现当你访问swagger之前,需要先登录。
1.添加Spring Security依赖和webFlux依赖
org.springframework.boot
spring-boot-starter-security
2.5.15
org.springframework.boot
spring-boot-starter-webflux
org.springframework.boot
spring-boot-starter-logging
ch.qos.logback
logback-classic
2.增加Spring security配置类
由于spring cloud gateway是一个spring-boot-starter-webflux
项目,不是spring-boot-starter-web
项目,所以需要用webFlux实现路径拦截,写法与web项目类似。
package cn.demo.conf;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.core.userdetails.MapReactiveUserDetailsService;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.server.SecurityWebFilterChain;
/**
* @description: spring security路径拦截器
* @author: 大脑补丁
* @create: 2023-08-18 16:34
*/
@Configuration
@EnableWebFluxSecurity
public class SecurityConfig {
@Value("${swagger.username}")
private String username;
@Value("${swagger.password}")
private String password;
@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
return http
.authorizeExchange()
.pathMatchers("/swagger-ui/index.html").authenticated()
.pathMatchers("/swagger-resources/**").authenticated()
.pathMatchers("/v2/api-docs").authenticated()
.pathMatchers("/v3/api-docs").authenticated()
.pathMatchers("/webjars/**").authenticated()
.pathMatchers("/actuator/**").authenticated()
.anyExchange().permitAll()
.and()
.formLogin()
.and()
.logout()
.and()
.csrf().disable()
.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
@Bean
public MapReactiveUserDetailsService userDetailsService() {
UserDetails user = User.withUsername(username)
.password(password)
.roles("USER")
.build();
return new MapReactiveUserDetailsService(user);
}
}
在配置文件中,配置下需要登陆的用户名密码:
#swagger登录用户名密码
swagger.username=admin
swagger.password=123456
配置完成后,当你再次访问swagger页面时,会先跳转到login页面,登陆成功后,会自动跳转到登陆页面。
六.总结
本文讲解了Spring Cloud Gateway统一管理所有微服务Swagger文档,并支持登陆后才能访问。其中主要用到的步骤都已交代,大家可以根据自己的网关配置,将其兼容进去即可。喜欢本文请点赞收藏。