SpringCloud Gateway基于JWT整合Swagger2聚合微服务系统API文档
- 需求
- 解决
- 配置swagger2
- pom
- swagger配置类
- order-service测试使用
- 配置gateway网关
- pom
- yml
- GatewayApplication启动类
- SwaggerProvider
- SwaggerHandler
- SwaggerHeaderFilter
- AuthorizeFilter全局过滤器
- 测试
需求
1、有两个微服务user-service和order-service都需要使用swagger2生成接口文档,后期还有其他微服务。如果每一个都去访问http://ip:port/swagger-ui.html
会比较麻烦。
2、后端接口访问都是需要携带token令牌才能进行请求访问的,如果每一个微服务都需要做请求拦截,代码冗余繁琐,后期不便于维护。
想达到的效果: 统一访问gateway网关http://localhost:10010/swagger-ui.html
地址,在网关中统一管理所有微服务的swagger文档,访问用户管理的单点登录获取token,然后携带token去访问微服务的其他接口。
端口如下:
页面效果:
user-service用户微服务的接口文档
切换order-service订单微服务的接口文档
解决
配置swagger2
pom
<dependency>
<groupId>com.spring4all</groupId>
<artifactId>swagger-spring-boot-starter</artifactId>
<version>1.9.1.RELEASE</version>
</dependency>
swagger配置类
这里和springboot使用swagger一样
/**
* Swagger2配置信息
*/
@Configuration
@EnableSwagger2
public class Swagger2Config {
// @Value("${swagger.enable}")
// private boolean swaggerEnable;
//是否允许显示swagger。此值可在application.yml中设定。
//作为开关,可在生产环境和开发环境打开或关闭,简便易行。
@Bean
public Docket webApiConfig(){
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(webApiInfo())
//.enable(swaggerEnable)
.select()
// 针对方法注解ApiOperation生成接口文档api
.apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
.paths(PathSelectors.any())
.build();
}
private ApiInfo webApiInfo(){
return new ApiInfoBuilder()
.title("网站-API文档")
.description("本文档描述了网站微服务接口定义")
.version("1.0")
.contact(new Contact("封于修", "http://zysheep.cn", "zysheep@126.com"))
.build();
}
}
这里我把swagger2抽成了一个公共的组件,微服务需要就引入坐标依赖,并在启动类上使用@ComponentScan
注解扫描组件到ioc容器中即可使用swagger2
order-service测试使用
这里使用@ComponentScan(basePackages = {"cn.zysheep"})
扫描swagger注册为容器中的Bean
配置gateway网关
我的环境
springboot | springcloud | spring-cloud-alibaba |
2.3.9.RELEASE | Hoxton.SR10 | 2.2.6.RELEASE |
pom
<!--网关-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--nacos服务发现依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--swagger依赖 Spring Cloud Gateway整合Swagger聚合微服务系统API文档-->
<dependency>
<groupId>com.spring4all</groupId>
<artifactId>swagger-spring-boot-starter</artifactId>
<version>1.9.1.RELEASE</version>
</dependency>
<dependency>
<groupId>cn.zysheep</groupId>
<artifactId>commons-util</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
yml
server:
port: 10010 # 网关端口
spring:
application:
name: gateway
cloud:
nacos:
server-addr: localhost:8848
discovery:
namespace: 4f64927e-a2a7-4e2f-b657-3da7c57eb008
gateway:
routes: # 网关路由配置
- id: user-service # 路由id,自定义,只要唯一即可
# uri: http://127.0.0.1:8081 # 路由的目标地址 http就是固定地址
uri: lb://user-service # 路由的目标地址 lb就是负载均衡,后面跟服务名称
predicates: # 路由断言,也就是判断请求是否符合路由规则的条件
- Path=/user/** # 这个是按照路径匹配,只要以/user/开头就符合要求
filters:
- StripPrefix=1
- id: order-service
uri: lb://order-service
predicates:
- Path=/order/**
filters:
- StripPrefix=1
logging:
level:
cn.zysheep.gateway: debug
GatewayApplication启动类
@SpringBootApplication
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class,args);
}
}
SwaggerProvider
配置SwaggerProvider,获取Api-doc
,即SwaggerResources。
@Component
@Primary
@AllArgsConstructor
public class SwaggerProvider implements SwaggerResourcesProvider {
public static final String API_URI = CommonConstants.SWAGGER_URL;
private final RouteLocator routeLocator;
private final GatewayProperties gatewayProperties;
@Override
public List<SwaggerResource> get() {
List<SwaggerResource> resources = new ArrayList<>();
List<String> routes = new ArrayList<>();
//取出gateway的route
routeLocator.getRoutes().subscribe(route -> routes.add(route.getId()));
//结合配置的route-路径(Path),和route过滤,只获取有效的route节点
gatewayProperties.getRoutes().stream().filter(routeDefinition -> routes.contains(routeDefinition.getId()))
.forEach(routeDefinition -> routeDefinition.getPredicates().stream()
.filter(predicateDefinition -> ("Path").equalsIgnoreCase(predicateDefinition.getName()))
.forEach(predicateDefinition -> resources.add(swaggerResource(routeDefinition.getId(),
// 1、公共路径使用服务名的http://localhost:10010/user/v2/api-docs
// predicateDefinition.getArgs().get(NameUtils.GENERATED_NAME_PREFIX + "0")
// .replace("/**", API_URI)))));
// 2、公共路径使用服务名的http://localhost:10010/user-service/v2/api-docs
"/"+routeDefinition.getId()+API_URI ))));
return resources;
}
private SwaggerResource swaggerResource(String name, String location) {
SwaggerResource swaggerResource = new SwaggerResource();
swaggerResource.setName(name);
swaggerResource.setLocation(location);
swaggerResource.setSwaggerVersion("2.0");
return swaggerResource;
}
}
SwaggerHandler
因为Gateway里没有配置SwaggerConfig,而运行Swagger-ui又需要依赖一些接口,所以我的想法是自己建立相应的swagger-resource端点。
@RestController
@RequestMapping("/swagger-resources")
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("/configuration/security")
public Mono<ResponseEntity<SecurityConfiguration>> securityConfiguration() {
return Mono.just(new ResponseEntity<>(
Optional.ofNullable(securityConfiguration).orElse(SecurityConfigurationBuilder.builder().build()), HttpStatus.OK));
}
@GetMapping("/configuration/ui")
public Mono<ResponseEntity<UiConfiguration>> uiConfiguration() {
return Mono.just(new ResponseEntity<>(
Optional.ofNullable(uiConfiguration).orElse(UiConfigurationBuilder.builder().build()), HttpStatus.OK));
}
@GetMapping("")
public Mono<ResponseEntity> swaggerResources() {
return Mono.just((new ResponseEntity<>(swaggerResources.get(), HttpStatus.OK)));
}
}
SwaggerHeaderFilter
@Component
public class SwaggerHeaderFilter extends AbstractGatewayFilterFactory {
private static final String HEADER_NAME = "X-Forwarded-Prefix";
@Override
public GatewayFilter apply(Object config) {
return (exchange, chain) -> {
ServerHttpRequest request = exchange.getRequest();
String path = request.getURI().getPath();
if (!StringUtils.endsWithIgnoreCase(path, SwaggerProvider.API_URI)) {
return chain.filter(exchange);
}
String basePath = path.substring(0, path.lastIndexOf(SwaggerProvider.API_URI));
ServerHttpRequest newRequest = request.mutate().header(HEADER_NAME, basePath).build();
ServerWebExchange newExchange = exchange.mutate().request(newRequest).build();
return chain.filter(newExchange);
};
}
}
AuthorizeFilter全局过滤器
Token工具类JWTUtil
@Order(-1)
@Component
@Slf4j
public class AuthorizeFilter implements GlobalFilter {
/**
* 处理当前请求,有必要的话通过{@link GatewayFilterChain}将请求交给下一个过滤器处理
*
* @param exchange 请求上下文,里面可以获取Request、Response等信息
* @param chain 用来把请求委托给下一个过滤器
* @return {@code Mono<Void>} 返回标示当前过滤器业务结束
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.debug("请求: {}", exchange.getRequest().getURI().getPath());
log.info("---------enter gateway interceptor--------");
String url = exchange.getRequest().getURI().getPath();
log.info("url---------------{}", url);
//登录直接放行
if (StringUtils.contains(url, CommonConstants.LOGIN_URL)) {
log.info(" login .............. ");
return chain.filter(exchange);
}else if(StringUtils.contains(url, CommonConstants.SWAGGER_URL)){//swagger直接放行
log.info(" swagger2 .............. ");
return chain.filter(exchange);
} else {
//获取token
ServerHttpRequest request = exchange.getRequest();
String token = request.getHeaders().getFirst("token");
//验证token
Map<String, Object> tokenMap = JWTUtil.parseToken(token);
log.info("-----{}", tokenMap);
if (ObjectUtils.isEmpty(tokenMap.get("ERR_CODE"))) {
request = exchange.getRequest().mutate().headers(httpHeaders -> {
httpHeaders.add("tokenMap", JSON.toJSONString(tokenMap));
}).build();
ServerWebExchange build = exchange.mutate().request(request).build();
return chain.filter(build);
} else {
return errorInfo(exchange, tokenMap.get("ERR_MSG").toString(), 500);
}
}
}
/**
* 返回response
*
* @param exchange
* @param message 异常信息
* @param status data中的status
* @return
*/
public static Mono<Void> errorInfo(ServerWebExchange exchange, String message, Integer status) {
// 自定义返回格式
Map<String, Object> resultMap = new HashMap<>(8);
resultMap.put("code", status);
resultMap.put("msg", StringUtils.isBlank(message) ? "服务异常!" : message);
resultMap.put("data", null);
return Mono.defer(() -> {
byte[] bytes;
try {
bytes = new ObjectMapper().writeValueAsBytes(resultMap);
} catch (JsonProcessingException e) {
log.error("网关响应异常:", e);
throw new RuntimeException("信息序列化异常");
} catch (Exception e) {
log.error("网关响应异常:", e);
throw new RuntimeException("写入响应异常");
}
ServerHttpResponse response = exchange.getResponse();
response.getHeaders().add("Content-Type", MediaType.APPLICATION_JSON_UTF8.toString());
DataBuffer buffer = response.bufferFactory().wrap(bytes);
return response.writeWith(Flux.just(buffer));
});
}
}
测试
测试流程:
- 访问gateway网关
http://localhost:10010/swagger-ui.html
- 访问用户微服务的单点登录获取token值
- 复制token,访问订单微服务的订单管理查询订单
1、访问gateway网关http://localhost:10010/swagger-ui.html
2、访问用户微服务的单点登录获取token值
3、复制token,访问订单微服务的订单管理查询订单
成功响应结果