一、构建网关
新建子模块
修改pom文件,使用spring cloud gateway
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
基本代码
server:
port: 8002
spring:
application:
name: GatewayDemo
cloud:
nacos:
discovery:
server-addr: localhost:8848
gateway:
discovery:
locator:
# 开启从注册中心动态创建路由的功能,利用微服务名进行路由
enabled: true
routes:
- id: UserDemo
uri: lb://UserDemo
predicates:
- Path=/api/v1/user/**
- id: DepartDemo
uri: lb://DepartDemo
predicates:
- Path=/DepartDemo/**
@SpringBootApplication
@Slf4j
@EnableDiscoveryClient
public class GatewayDemoApplication {
public static void main (String[]args) throws UnknownHostException {
ConfigurableApplicationContext applicationContext = SpringApplication.run(GatewayDemoApplication.class, args);
log.info("\n.-.-. .-.-. .-.-. .-.-. .-.-. .-.-. .-.-. \n" +
"'. S ).-.-.'. U ).-.-.'. C ).-.-.'. C ).-.-.'. E ).-.-.'. S ).-.-.'. S ) \n" +
" ).' '._.' ).' '._.' ).' '._.' ).' '._.' ).' '._.' ).' '._.' ).' \n" +
"\n----------------------------------------------------------\n\t" +
"Application is running! " +
"----------------------------------------------------------");
}
}
depart-Service服务的端口号是8001,网关的端口号是8002,先启动部门服务,再启动网关服务,
此时可以通过网关端口号访问Depart服务的接口,http://localhost:8002/DepartDemo/api/v1/depart/hello,通过postman请求结果:
二、集成认证中心
集成已有的认证中心,在网关上设置认证中心提供的url,验证token的有效性,不需要在网关内部或者微服务中做验证。
修改pom
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-jose</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-resource-server</artifactId>
</dependency>
<dependency>
<groupId>com.nimbusds</groupId>
<artifactId>nimbus-jose-jwt</artifactId>
<version>7.1</version>
</dependency>
<dependency>
<groupId>com.nimbusds</groupId>
<artifactId>nimbus-jwkset-loader</artifactId>
<version>4.0</version>
</dependency>
新建配置类
package com.gatewaydemo.config;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.SecurityWebFiltersOrder;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.web.server.SecurityWebFilterChain;
@Configuration
@EnableWebFluxSecurity
@RequiredArgsConstructor(onConstructor_ = @Autowired)
public class ResourceServerConfig {
@Value("${spring.security.oauth2.resourceserver.jwt.jwk-set-uri}")
private String jwkSetUri;
@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http.oauth2ResourceServer().jwt()
.jwkSetUri(jwkSetUri) // 远程获取公钥
.and()
.and().authorizeExchange()
.pathMatchers(HttpMethod.OPTIONS).permitAll()
.anyExchange().authenticated() //其他的路径都要进行验证
.and()
.cors().disable().csrf().disable();
return http.build();
}
修改application.yml
spring:
security:
oauth2:
resourceserver:
jwt:
jwk-set-uri: 验证token的地址
基本的框架完成了,先启动微服务,再启动网关服务,这是重新请求上面的接口,结果是401,没有授权,请求接口时请求头需要携带token,这样就可以正常访问。
如果访问的资源不需要身份认证,那么可以修改配置类ResourceServerConfig
private static final String[] excludedAuthPages = {
"/DepartDemo/api/v1/depart/hello"
};
@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http.oauth2ResourceServer().jwt()
.jwkSetUri(jwkSetUri) // 远程获取公钥
.and()
.and().authorizeExchange()
.pathMatchers(excludedAuthPages).permitAll() //无需进行权限过滤的请求路径
.pathMatchers(HttpMethod.OPTIONS).permitAll()
.anyExchange().authenticated()
.and()
.cors().disable().csrf().disable();
return http.build();
}
三、网关swagger
现在有用户和部门两个微服务,分别生成了自己的swagger文档,希望生成网关的swagger,可以直接打开微服务的swagger。
修改网关模块pom
<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>
增加无需进行权限过滤的请求路径
private static final String[] excludedAuthPages = {
"/swagger-ui.html",
"/swagger-resources/**",
"/webjars/**",
"/**/v2/api-docs",
"/csrf",
"/"
};
添加网关模块swagger配置类
import lombok.AllArgsConstructor;
import org.springframework.cloud.gateway.config.GatewayProperties;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.support.NameUtils;
import org.springframework.stereotype.Component;
import springfox.documentation.swagger.web.SwaggerResource;
import springfox.documentation.swagger.web.SwaggerResourcesProvider;
import java.util.ArrayList;
import java.util.List;
@Component
@AllArgsConstructor
public class SwaggerResourcesProviderConfig implements SwaggerResourcesProvider {
/**
* Swagger2默认的url后缀
*/
public static final String SWAGGER2URL = "/v2/api-docs";
/**
* 网关路由
*/
private final RouteLocator routeLocator;
private final GatewayProperties gatewayProperties;
/**
* 聚合其他服务接口
* @return
*/
@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(route -> route.getPredicates().stream()
// 忽略配置文件中断言中配置的Path为空的配置项
.filter(predicateDefinition -> ("Path").equalsIgnoreCase(predicateDefinition.getName()))
// 将Path中的路由地址由**改为v2/api-docs,swagger就是通过这个地址来获取接口文档数据的,可以通过访问:ip:port/v2/api-docs来体会接口数据
.forEach(predicateDefinition -> resources
.add(swaggerResource(route.getId(), predicateDefinition.getArgs()
.get(NameUtils.GENERATED_NAME_PREFIX + "0").replace("/**", SWAGGER2URL)))));
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;
}
}
添加handler
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.*;
import java.util.Optional;
@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)));
}
}
现在可以启动项目了,在swagger上请求接口,返回401,没有授权。这是需要修改微服务的网关配置类。
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.ApiKey;
import springfox.documentation.service.AuthorizationScope;
import springfox.documentation.service.SecurityReference;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;
import java.util.List;
import static com.google.common.collect.Lists.newArrayList;
@Configuration
public class SwaggerConfig {
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.build()//;
.securitySchemes(securitySchemes())
.securityContexts(securityContexts());
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("DepartService系统接口文档")
.description("这是系统接口文档说明")
.version("1.0")
.build();
}
private List<ApiKey> securitySchemes() {
return newArrayList( new ApiKey("Authorization", "Authorization", "header"));
}
private List<SecurityContext> securityContexts() {
return newArrayList( SecurityContext.builder() .securityReferences(defaultAuth())
.forPaths(PathSelectors.any())
.build() );
}
List<SecurityReference> defaultAuth() {
AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
authorizationScopes[0] = authorizationScope;
return newArrayList( new SecurityReference("Authorization", authorizationScopes));
}
}
然后重新