BackGround-背景
Recently, I am coding a Spring Cloud project, that I need to test something with Swagger.The problem is that I need to route and filter the request urls of Swagger in case of being block by JWT Auth & Gateway Custom Filter.
最近,我在做一个微服务项目,需要用到Swagger。问题是我需要路由过滤请求,防止他被JWT和网关自定义过滤器给阻挡。
Project Structure 项目结构
There are 2 consumers modules and 1 gateway module in this project.
这有2个消费者 和1个生产者。
Process-过程
Add depencies of swagger2 & ui to commons module.Here I use version-2.9.2
- Add Maven dependencies of swagger2 & ui to commons module.Here I use version-2.9.2.
在commons模块添加swagger2和ui的Maven依赖,这里我使用的版本是-2.9.2.
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
- Create SwaggerConfig.java to admin & app-server modules.At the same time,don’t forget to add urls pattern of Swagger including its urls static urls to the interceptors to make it free to be visited without token as we make token interceptor for authorization.
在admin和app-server模块下添加SwaggerConfig.java配置类。与此同时,由于我们做了token认证的拦截器,别忘记把swagger的访问的路径,包括他的静态文件路径到拦截器里排除掉,使他们能够被正常访问。
package com.tanhua.admin.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.ParameterBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.schema.ModelRef;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.service.Parameter;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
import java.util.ArrayList;
import java.util.List;
@Slf4j
@Configuration
@EnableSwagger2
public class SwaggerConfig {
private boolean SWAGGER_IS_ENABLE = true;
public ApiInfo createApiInfo() {
return new ApiInfoBuilder()
.title("鸡你太美-交友Admin后端文档")
.description("陌生人交友")
.contact(new Contact("ikun探花交友论坛", "tanhua.com", "tanhua@tanhua.com"))
.version("1.0.0").build();
}
@Bean
public Docket createApi() {
ParameterBuilder tokenPar = new ParameterBuilder();
List<Parameter> pars = new ArrayList<>();
tokenPar.name("token").description("token")
.modelRef(new ModelRef("string")).parameterType("header").required(false).build();
pars.add(tokenPar.build());
return new Docket(DocumentationType.SWAGGER_2)
.enable(SWAGGER_IS_ENABLE)
.apiInfo(createApiInfo()).select()
.apis(RequestHandlerSelectors.basePackage("com.tanhua.admin.controller"))
.paths(PathSelectors.any())
.build().globalOperationParameters(pars);
}
}
package com.tanhua.server.interceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new TokenInterceptor()).addPathPatterns("/**").excludePathPatterns(new String[]{"/user/login", "/user/loginVerification", "/v2/**", "/webjars/**", "/swagger-ui.html", "/swagger-resources/**"});
}
}
- In gate-way module, create an api for the visit.Also,we create a SwaggerConfig to configura some properties of Swagger.
在gate-way模块里,增加一个API文件方便访问。我们也创建一个SwaggerConfig 来配置Swagger的属性
package com.tanhua.gateway.api;
import com.tanhua.gateway.config.SwaggerConfig;
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.SecurityConfiguration;
import springfox.documentation.swagger.web.SecurityConfigurationBuilder;
import springfox.documentation.swagger.web.UiConfiguration;
import springfox.documentation.swagger.web.UiConfigurationBuilder;
import java.util.Optional;
@RestController
public class SwaggerController {
@Autowired(required = false)
private SecurityConfiguration securityConfiguration;
@Autowired(required = false)
private UiConfiguration uiConfiguration;
@Autowired
private SwaggerConfig swaggerResources;
@Autowired
public SwaggerController(SwaggerConfig 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)));
}
}
package com.tanhua.gateway.config;
import org.springframework.cloud.gateway.config.GatewayProperties;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.support.NameUtils;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import springfox.documentation.swagger.web.SwaggerResource;
import springfox.documentation.swagger.web.SwaggerResourcesProvider;
import java.util.ArrayList;
import java.util.List;
@Configuration
@Primary
public class SwaggerConfig implements SwaggerResourcesProvider {
public static final String API_URI = "/v2/api-docs";
private final RouteLocator routeLocator;
private final GatewayProperties gatewayProperties;
public SwaggerConfig(RouteLocator routeLocator, GatewayProperties gatewayProperties) {
this.routeLocator = routeLocator;
this.gatewayProperties = gatewayProperties;
}
@Override
public List<SwaggerResource> get() {
List<SwaggerResource> resources = new ArrayList<>();
List<String> routes = new ArrayList<>();
routeLocator.getRoutes().subscribe(route -> routes.add(route.getId()));
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(),
predicateDefinition.getArgs().get(NameUtils.GENERATED_NAME_PREFIX + "0")
.replace("/**", 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;
}
}
- Modify AuthFilter to filter the Swagger Urls & Add the urls in Config list of Nacos
在AuthFilter认证过滤中,过滤掉Swagger的链接,与此同时在Nacos配置中心加入这些urls.
package com.tanhua.gateway.filters;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.tanhua.commons.utils.JwtUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Component
@Slf4j
@RefreshScope
public class AuthFilter implements GlobalFilter, Ordered {
@Value("${gateway.excludedUrls}")
private List<String> excludedUrls;
@Value("${gateway.swaggerUrls}")
private List<String> swaggerUrls;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String path = exchange.getRequest().getURI().getPath();
log.info("请求路径path:{}",path);
log.info("gateway.excludedUrls:{},-----gateway.swaggerUrls:{}",excludedUrls,swaggerUrls);
if (isAllowPath(path)){
return chain.filter(exchange);
}
if (excludedUrls.contains(path)) {
return chain.filter(exchange);
}
String token = exchange.getRequest().getHeaders().getFirst("Authorization");
log.info("gateway校验:token:{}",token);
if (!StringUtils.isEmpty(token)) {
token=token.replaceFirst("Bearer ", "");
}
log.info("处理后的token:{}",token);
boolean verifyToken = JwtUtils.verifyToken(token);
log.info("认证后的boolean值:{}",verifyToken);
if (!verifyToken) {
Map<String, Object> responseData = new HashMap<>();
responseData.put("errCode", 401);
responseData.put("errMessage", "用户未登录");
return responseError(exchange.getResponse(), responseData);
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE;
}
private Mono<Void> responseError(ServerHttpResponse response, Map<String, Object> responseData) {
ObjectMapper objectMapper = new ObjectMapper();
byte[] data = new byte[0];
try {
data = objectMapper.writeValueAsBytes(responseData);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
DataBuffer buffer = response.bufferFactory().wrap(data);
response.setStatusCode(HttpStatus.UNAUTHORIZED);
response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
return response.writeWith(Mono.just(buffer));
}
private boolean isAllowPath(String path) {
for (String allowPath : excludedUrls) {
if(path.startsWith(allowPath)){
return true;
}
}
return false;
}
}
- Finally,we can open the visit url to browse APIs after we start the service of Spring Cloud.
最后,我们可以带链接访问这些APIs
|