封面图片

微服务

SpringCloud微服务开发(四):搭建网关服务器

微服务体系一般由服务注册发现中心、网关、资源服务和认证服务这几个部分组成。这是微服务系列的第四篇,网关搭建。


网关的主要作用就是请求的路由,包括外部请求和内部微服务之间的请求都通过网关来进行。网关也承担一部分的登录认证和权限校验的作用。

Spring Cloud Gateway 是基于 WebFlux 框架实现的,而 WebFlux 框架底层则使用了高性能的 Reactor 模式通信框架 Netty。

核心概念

路由Route

网关最基本的模块。它由一个 ID、一个目标 URI、一组断言(Predicate)和一组过滤器(Filter)组成。

断言Predicate

路由转发的判断条件,我们可以通过 Predicate 对 HTTP 请求进行匹配,例如请求方式、请求路径、请求头、参数等,如果请求与断言匹配成功,则将请求转发到相应的服务。

Predicate 是路由转发的判断条件,请求只有满足了 Predicate 的条件,才会被转发到指定的服务上进行处理。

使用 Predicate 断言需要注意以下 3 点:

  • Route 路由与 Predicate 断言的对应关系为“一对多”,一个路由可以包含多个不同断言。
  • 一个请求想要转发到指定的路由上,就必须同时匹配路由上的所有断言。
  • 当一个请求同时满足多个路由的断言条件时,请求只会被首个成功匹配的路由转发。

过滤器Filter

过滤器,我们可以使用它对请求进行拦截和修改,还可以使用它对上文的响应进行再处理。

在微服务架构中,系统由多个微服务组成,所有这些服务都有一些相同的校验逻辑,此时我们就可以将这些校验逻辑写到 Spring Cloud Gateway的Filter过滤器中。

过滤器类型

  • Pre 类型:这种过滤器在请求被转发到微服务之前可以对请求进行拦截和修改,例如参数校验、权限校验、流量监控、日志输出以及协议转换等操作。
  • Post 类型:这种过滤器在微服务对请求做出响应后可以对响应进行拦截和再处理,例如修改响应内容或响应头、日志输出、流量监控等。

按照作用范围划分

  • GatewayFilter:应用在单个路由或者一组路由上的过滤器。
  • GlobalFilter:应用在所有的路由上的过滤器。

示例

集成了基于redis的token校验和swagger3,其中swagger3可以在一个页面中查看不同微服务的接口。

  1. 添加依赖
1<?xml version="1.0" encoding="UTF-8"?> 2<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 3 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> 4 <modelVersion>4.0.0</modelVersion> 5 <parent> 6 <groupId>org.springframework.boot</groupId> 7 <artifactId>spring-boot-starter-parent</artifactId> 8 <version>2.3.4.RELEASE</version> 9 <relativePath/> <!-- lookup parent from repository --> 10 </parent> 11 <groupId>com.example</groupId> 12 <artifactId>gateway-service</artifactId> 13 <version>0.0.1-SNAPSHOT</version> 14 <name>gateway-service</name> 15 <description>gateway-service</description> 16 <properties> 17 <java.version>1.8</java.version> 18 <org.projectlombok.version>1.18.16</org.projectlombok.version> 19 </properties> 20 <dependencyManagement> 21 <dependencies> 22 <!-- spring cloud 依赖 --> 23 <dependency> 24 <groupId>org.springframework.cloud</groupId> 25 <artifactId>spring-cloud-dependencies</artifactId> 26 <version>Hoxton.SR12</version> 27 <type>pom</type> 28 <scope>import</scope> 29 </dependency> 30 <!-- spring cloud alibaba 依赖--> 31 <dependency> 32 <groupId>com.alibaba.cloud</groupId> 33 <artifactId>spring-cloud-alibaba-dependencies</artifactId> 34 <version>2.2.1.RELEASE</version> 35 <type>pom</type> 36 <scope>import</scope> 37 </dependency> 38 </dependencies> 39 </dependencyManagement> 40 <dependencies> 41 <dependency> 42 <groupId>org.springframework.boot</groupId> 43 <artifactId>spring-boot-starter-webflux</artifactId> 44 </dependency> 45 <dependency> 46 <groupId>org.springframework.cloud</groupId> 47 <artifactId>spring-cloud-starter-gateway</artifactId> 48 </dependency> 49 50 <dependency> 51 <groupId>com.alibaba.cloud</groupId> 52 <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> 53 </dependency> 54 55 <dependency> 56 <groupId>org.springframework.boot</groupId> 57 <artifactId>spring-boot-starter</artifactId> 58 </dependency> 59 <dependency> 60 <groupId>org.springframework.security</groupId> 61 <artifactId>spring-security-config</artifactId> 62 </dependency> 63 <dependency> 64 <groupId>org.springframework.security</groupId> 65 <artifactId>spring-security-oauth2-resource-server</artifactId> 66 </dependency> 67 <dependency> 68 <groupId>org.springframework.security</groupId> 69 <artifactId>spring-security-oauth2-jose</artifactId> 70 </dependency> 71 <dependency> 72 <groupId>org.springframework.cloud</groupId> 73 <artifactId>spring-cloud-starter-oauth2</artifactId> 74 </dependency> 75 <dependency> 76 <groupId>org.springframework.boot</groupId> 77 <artifactId>spring-boot-starter-data-redis</artifactId> 78 </dependency> 79 <dependency> 80 <groupId>org.springframework.boot</groupId> 81 <artifactId>spring-boot-devtools</artifactId> 82 <scope>runtime</scope> 83 <optional>true</optional> 84 </dependency> 85 <dependency> 86 <groupId>org.springframework.boot</groupId> 87 <artifactId>spring-boot-configuration-processor</artifactId> 88 <optional>true</optional> 89 </dependency> 90 <dependency> 91 <groupId>com.fasterxml.jackson.core</groupId> 92 <artifactId>jackson-databind</artifactId> 93 <version>2.13.3</version> 94 </dependency> 95 <dependency> 96 <groupId>com.fasterxml.jackson.core</groupId> 97 <artifactId>jackson-core</artifactId> 98 <version>2.13.3</version> 99 </dependency> 100 <dependency> 101 <groupId>com.fasterxml.jackson.core</groupId> 102 <artifactId>jackson-annotations</artifactId> 103 <version>2.13.3</version> 104 </dependency> 105 <dependency> 106 <groupId>org.projectlombok</groupId> 107 <artifactId>lombok</artifactId> 108 <optional>true</optional> 109 </dependency> 110 <dependency> 111 <groupId>cn.hutool</groupId> 112 <artifactId>hutool-all</artifactId> 113 <version>5.3.5</version> 114 </dependency> 115 <!-- 引入Swagger3依赖 --> 116 <dependency> 117 <groupId>io.springfox</groupId> 118 <artifactId>springfox-boot-starter</artifactId> 119 <version>3.0.0</version> 120 </dependency> 121 <dependency> 122 <groupId>org.springframework.boot</groupId> 123 <artifactId>spring-boot-starter-test</artifactId> 124 <scope>test</scope> 125 </dependency> 126 </dependencies> 127 128 <build> 129 <plugins> 130 <plugin> 131 <groupId>org.springframework.boot</groupId> 132 <artifactId>spring-boot-maven-plugin</artifactId> 133 <configuration> 134 <excludes> 135 <exclude> 136 <groupId>org.projectlombok</groupId> 137 <artifactId>lombok</artifactId> 138 </exclude> 139 </excludes> 140 </configuration> 141 </plugin> 142 </plugins> 143 </build> 144 145</project> 146
  1. 配置application.yml
1server: 2 port: 9090 3 4spring: 5 application: 6 name: gateway-service 7 security: 8 oauth2: 9 resourceserver: 10 jwt: 11 jwk-set-uri: 'http://localhost:9082/auth/rsa/publicKey' 12 redis: 13 host: localhost 14 port: 6381 15 password: 123456 16 timeout: 3000 17 cloud: 18 nacos: 19 discovery: 20 server-addr: localhost:8848 21 username: nacos 22 password: nacos 23 gateway: 24 discovery: 25 locator: 26 enabled: true # gateway 可以从 nacos 发现微服务 27 lower-case-service-id: true #是否将服务名称转换小写 28 routes: 29 # 认证中心 30 - id: oauth2-auth 31 uri: lb://oauth2-auth 32 predicates: 33 - Path=/oauth2/** 34 filters: 35 - StripPrefix=1 36 # 用户服务 37 - id: user-service 38 uri: lb://user-service 39 predicates: 40 - Path=/us/** 41 filters: 42 - StripPrefix=1 43secure: 44 ignore: 45 urls: #配置白名单路径,该路径不会被token和权限校验,但会被过滤器拦截 46 - "/actuator/**" 47 - "/oauth2/oauth/token" 48 - "/oauth2/oauth/check_token" 49 - "/oauth2/logout" 50 - "/oauth2/oauth/authorize" 51 - "/oauth2/auth/rsa/publicKey" 52 - "/swagger-ui/**" 53 - "/swagger-resources/**" 54 - "/v3/api-docs/**" 55 - "/v2/api-docs/**" 56 - "/*/v3/api-docs" 57 - "/*/v2/api-docs" 58logging: 59 file: 60 path: logs
  1. 过滤器
1@Slf4j 2@Component 3public class AccessLogFilter implements GlobalFilter, Ordered { 4 private final List<HttpMessageReader<?>> messageReaders = HandlerStrategies.withDefaults().messageReaders(); 5 6 @Override 7 public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { 8 ServerHttpRequest request = exchange.getRequest(); 9 10 // 请求路径 11 String requestPath = request.getPath().pathWithinApplication().value(); 12 String checkTokenPath = "/oauth2/oauth/check_token"; 13 String swaggerUrl = "/v3/api-docs"; 14 if (requestPath.equals(checkTokenPath) || requestPath.contains(swaggerUrl)) { 15 return chain.filter(exchange); 16 } 17 Route route = getGatewayRoute(exchange); 18 19 String ipAddress = IpUtils.getRealIpAddress(request); 20 String requestId = RandomStringUtils.random(9, true, true); 21 ServerHttpResponse response = exchange.getResponse(); 22 response.getHeaders().set("requestId", requestId); 23 24 String clientType = exchange.getResponse().getHeaders().getFirst("client_type"); 25 String clientId = exchange.getResponse().getHeaders().getFirst("client_id"); 26 27 GatewayLog gatewayLog = new GatewayLog(); 28 gatewayLog.setSchema(request.getURI().getScheme()); 29 gatewayLog.setRequestMethod(request.getMethodValue()); 30 gatewayLog.setRequestPath(requestPath); 31 gatewayLog.setTargetServer(route.getId()); 32 gatewayLog.setRequestTime(new Date()); 33 gatewayLog.setIp(ipAddress); 34 gatewayLog.setClientType(clientType); 35 gatewayLog.setClientId(clientId); 36 gatewayLog.setRequestId(requestId); 37 38 String token = exchange.getRequest().getHeaders().getFirst("Authorization"); 39 if (!StrUtil.isEmpty(token)) { 40 try { 41 //从token中解析用户信息并设置到Header中去 42 String realToken = token.replace("Bearer ", ""); 43 JWSObject jwsObject = JWSObject.parse(realToken); 44 String userStr = jwsObject.getPayload().toString(); 45 JSONObject jsonObj = JSONObject.parseObject(userStr); 46 Long userId = jsonObj.getLong("id"); 47 String username = jsonObj.getString("user_name"); 48 gatewayLog.setUsername(username == null ? "" : username); 49 gatewayLog.setUserId(userId == null ? 0 : userId); 50 log.info("AuthGlobalFilter.filter() user:{}", userStr); 51 request = request.mutate().header("user", URLEncoder.encode(userStr, "UTF-8")).header("client_id", clientId).build(); 52 exchange = exchange.mutate().request(request).build(); 53 } catch (ParseException e) { 54 e.printStackTrace(); 55 } catch (UnsupportedEncodingException e) { 56 throw new RuntimeException(e); 57 } 58 } 59 60 MediaType mediaType = request.getHeaders().getContentType(); 61 62 if(MediaType.APPLICATION_FORM_URLENCODED.isCompatibleWith(mediaType) || MediaType.APPLICATION_JSON.isCompatibleWith(mediaType)){ 63 return writeBodyLog(exchange, chain, gatewayLog); 64 }else{ 65 return writeBasicLog(exchange, chain, gatewayLog); 66 } 67 } 68 69 @Override 70 public int getOrder() { 71 return -100; 72 } 73 74 private Mono<Void> writeBasicLog(ServerWebExchange exchange, GatewayFilterChain chain, GatewayLog accessLog) { 75 StringBuilder builder = new StringBuilder(); 76 MultiValueMap<String, String> queryParams = exchange.getRequest().getQueryParams(); 77 for (Map.Entry<String, List<String>> entry : queryParams.entrySet()) { 78 builder.append(entry.getKey()).append("=").append(StringUtils.join(entry.getValue(), ",")); 79 } 80 accessLog.setRequestBody(builder.toString()); 81 82 //获取响应体 83 ServerHttpResponseDecorator decoratedResponse = recordResponseLog(exchange, accessLog); 84 85 return chain.filter(exchange.mutate().response(decoratedResponse).build()) 86 .then(Mono.fromRunnable(() -> { 87 // 打印日志 88 writeAccessLog(accessLog); 89 })); 90 } 91 92 private Mono writeBodyLog(ServerWebExchange exchange, GatewayFilterChain chain, GatewayLog gatewayLog) { 93 ServerRequest serverRequest = ServerRequest.create(exchange,messageReaders); 94 95 Mono<String> modifiedBody = serverRequest.bodyToMono(String.class) 96 .flatMap(body ->{ 97 gatewayLog.setRequestBody(body); 98 return Mono.just(body); 99 }); 100 101 // 通过 BodyInserter 插入 body(支持修改body), 避免 request body 只能获取一次 102 BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody, String.class); 103 HttpHeaders headers = new HttpHeaders(); 104 headers.putAll(exchange.getRequest().getHeaders()); 105 106 headers.remove(HttpHeaders.CONTENT_LENGTH); 107 108 CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, headers); 109 110 return bodyInserter.insert(outputMessage,new BodyInserterContext()) 111 .then(Mono.defer(() -> { 112 // 重新封装请求 113 ServerHttpRequest decoratedRequest = requestDecorate(exchange, headers, outputMessage); 114 115 // 记录响应日志 116 ServerHttpResponseDecorator decoratedResponse = recordResponseLog(exchange, gatewayLog); 117 118 // 记录普通的 119 return chain.filter(exchange.mutate().request(decoratedRequest).response(decoratedResponse).build()) 120 .then(Mono.fromRunnable(() -> { 121 // 打印日志 122 writeAccessLog(gatewayLog); 123 })); 124 })); 125 } 126 private void writeAccessLog(GatewayLog gatewayLog) { 127 String logInfo = JSON.toJSONString(gatewayLog, SerializerFeature.WriteMapNullValue, SerializerFeature.DisableCircularReferenceDetect, SerializerFeature.WriteDateUseDateFormat); 128 log.warn("==access== {}",logInfo); 129 } 130 131 private Route getGatewayRoute(ServerWebExchange exchange) { 132 return exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR); 133 } 134 135 private ServerHttpRequestDecorator requestDecorate(ServerWebExchange exchange, HttpHeaders headers, 136 CachedBodyOutputMessage outputMessage) { 137 return new ServerHttpRequestDecorator(exchange.getRequest()) { 138 @Override 139 public HttpHeaders getHeaders() { 140 long contentLength = headers.getContentLength(); 141 HttpHeaders httpHeaders = new HttpHeaders(); 142 httpHeaders.putAll(super.getHeaders()); 143 if (contentLength > 0) { 144 httpHeaders.setContentLength(contentLength); 145 } else { 146 // TODO: this causes a 'HTTP/1.1 411 Length Required' // on 147 // httpbin.org 148 httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked"); 149 } 150 return httpHeaders; 151 } 152 153 @Override 154 public Flux<DataBuffer> getBody() { 155 return outputMessage.getBody(); 156 } 157 }; 158 } 159 160 private ServerHttpResponseDecorator recordResponseLog(ServerWebExchange exchange, GatewayLog gatewayLog) { 161 ServerHttpResponse response = exchange.getResponse(); 162 DataBufferFactory bufferFactory = response.bufferFactory(); 163 164 return new ServerHttpResponseDecorator(response) { 165 @Override 166 public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) { 167 if (body instanceof Flux) { 168 Date responseTime = new Date(); 169 gatewayLog.setResponseTime(responseTime); 170 // 计算执行时间 171 long executeTime = (responseTime.getTime() - gatewayLog.getRequestTime().getTime()); 172 173 gatewayLog.setExecuteTime(executeTime); 174 175 // 获取响应类型,如果是 json 就打印 176 String originalResponseContentType = exchange.getAttribute(ServerWebExchangeUtils.ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR); 177 gatewayLog.setStatus(Objects.requireNonNull(this.getStatusCode()).value()); 178 179 if (ObjectUtil.equal(this.getStatusCode(), HttpStatus.OK) 180 && !StringUtil.isNullOrEmpty(originalResponseContentType) 181 && originalResponseContentType.contains("application/json")) { 182 183 Flux<? extends DataBuffer> fluxBody = Flux.from(body); 184 return super.writeWith(fluxBody.buffer().map(dataBuffers -> { 185 186 // 合并多个流集合,解决返回体分段传输 187 DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory(); 188 DataBuffer join = dataBufferFactory.join(dataBuffers); 189 byte[] content = new byte[join.readableByteCount()]; 190 join.read(content); 191 192 // 释放掉内存 193 DataBufferUtils.release(join); 194 String responseResult = new String(content, StandardCharsets.UTF_8); 195 gatewayLog.setResponseData(responseResult); 196 197 return bufferFactory.wrap(content); 198 })); 199 } 200 } 201 return super.writeWith(body); 202 } 203 }; 204 } 205 206}
1@Component 2public class IgnoreUrlsRemoveJwtFilter implements WebFilter { 3 private final IgnoreUrlsConfig ignoreUrlsConfig; 4 5 public IgnoreUrlsRemoveJwtFilter(IgnoreUrlsConfig ignoreUrlsConfig) { 6 this.ignoreUrlsConfig = ignoreUrlsConfig; 7 } 8 9 @Override 10 public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) { 11 ServerHttpRequest request = exchange.getRequest(); 12 URI uri = request.getURI(); 13 PathMatcher pathMatcher = new AntPathMatcher(); 14 //白名单路径移除JWT请求头 15 List<String> ignoreUrls = ignoreUrlsConfig.getUrls(); 16 for (String ignoreUrl : ignoreUrls) { 17 if (pathMatcher.match(ignoreUrl, uri.getPath())) { 18 request = exchange.getRequest().mutate().header("Authorization", "").build(); 19 exchange = exchange.mutate().request(request).build(); 20 return chain.filter(exchange); 21 } 22 } 23 return chain.filter(exchange); 24 } 25}
  1. 认证相关类
1@Component 2@Slf4j 3@AllArgsConstructor 4public class AuthorizationManager implements ReactiveAuthorizationManager<AuthorizationContext> { 5 6 private final RedisTemplate<String,Object> redisTemplate; 7 private final TokenStore redisTokenStore; 8 9 @Override 10 public Mono<AuthorizationDecision> check(Mono<Authentication> mono, AuthorizationContext authorizationContext) { 11 // 对应跨域的预检请求直接放行 12 if (authorizationContext.getExchange().getRequest().getMethod() == HttpMethod.OPTIONS) { 13 return Mono.just(new AuthorizationDecision(true)); 14 } 15 16 String authorizationToken = authorizationContext.getExchange().getRequest().getHeaders().getFirst(HttpHeaders.AUTHORIZATION); 17 log.debug("当前请求头Authorization中的值:{}",authorizationToken); 18 if (StringUtils.isBlank(authorizationToken)) { 19 log.warn("当前请求头Authorization中的值不存在"); 20 return Mono.just(new AuthorizationDecision(false)); 21 } 22 23 String token = authorizationToken.replace(OAuth2AccessToken.BEARER_TYPE + " ", ""); 24 OAuth2Authentication oAuth2Authentication = redisTokenStore.readAuthentication(token); 25 String clientId = oAuth2Authentication.getOAuth2Request().getClientId(); 26 ServerHttpResponse response = authorizationContext.getExchange().getResponse(); 27 response.getHeaders().set("client_id", clientId); 28 //通过客户端方式访问,则直接放行,不进行权限校验,由服务自身去校验 29 if (oAuth2Authentication.isClientOnly()) { 30 response.getHeaders().set("client_type", "client"); 31 return Mono.just(new AuthorizationDecision(true)); 32 } else { 33 response.getHeaders().set("client_type", "password"); 34 } 35 36 //从Redis中获取当前路径可访问角色列表 37 URI uri = authorizationContext.getExchange().getRequest().getURI(); 38 Object obj = redisTemplate.opsForHash().get(RedisConstant.RESOURCE_ROLES_MAP, uri.getPath()); 39 List<String> authorities = Convert.toList(String.class,obj); 40 authorities = authorities.stream().map(i -> i = AuthConstant.AUTHORITY_PREFIX + i).collect(Collectors.toList()); 41 //认证通过且角色匹配的用户可访问当前路径 42 return mono 43 .filter(Authentication::isAuthenticated) 44 .flatMapIterable(Authentication::getAuthorities) 45 .map(GrantedAuthority::getAuthority) 46 .any(authorities::contains) 47 .map(AuthorizationDecision::new) 48 .defaultIfEmpty(new AuthorizationDecision(false)); 49 50 } 51}

因为集成了oauth2,所以对不同的认证方式进行不同的处理。这里对客户端的访问方式直接做了放行处理,让微服务自身去验证。这里可以根据实际情况进行调整。

1@Component 2@Slf4j 3public class GlobalGatewayExceptionHandler implements ErrorWebExceptionHandler { 4 @Override 5 public Mono<Void> handle(ServerWebExchange exchange, Throwable throwable) { 6 //自定义无效的token异常处理返回结果 7 if (throwable.getCause() instanceof InvalidTokenException) { 8 ServerHttpResponse response = exchange.getResponse(); 9 response.setStatusCode(HttpStatus.OK); 10 response.getHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE); 11 String body= JSONUtil.toJsonStr(ResultJson.failure(ResultCode.UNAUTHORIZED, throwable.getMessage())); 12 DataBuffer buffer = response.bufferFactory().wrap(body.getBytes(StandardCharsets.UTF_8)); 13 return response.writeWith(Mono.just(buffer)); 14 } 15 //其他异常返回500 16 ServerHttpResponse response = exchange.getResponse(); 17 response.setStatusCode(HttpStatus.OK); 18 response.getHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE); 19 String body= JSONUtil.toJsonStr(ResultJson.failure(ResultCode.SERVER_ERROR, throwable.getMessage())); 20 DataBuffer buffer = response.bufferFactory().wrap(body.getBytes(StandardCharsets.UTF_8)); 21 return response.writeWith(Mono.just(buffer)); 22 } 23}

还有未登录和没有权限的处理类。

  1. 配置类

配置类主要包括资源服务器类、swagger配置类、redis配置类等。

1@Component 2@Primary 3public class GatewaySwaggerProvider implements SwaggerResourcesProvider { 4 public static final String API_URI = "/v3/api-docs"; 5 private final RouteLocator routeLocator; 6 private final GatewayProperties gatewayProperties; 7 8 public GatewaySwaggerProvider(RouteLocator routeLocator, GatewayProperties gatewayProperties) { 9 this.routeLocator = routeLocator; 10 this.gatewayProperties = gatewayProperties; 11 } 12 13 /** 14 * 这个类是核心,这个类封装的是SwaggerResource,即在swagger-ui.html页面中顶部的选择框,选择服务的swagger页面内容。 15 * RouteLocator:获取spring cloud gateway中注册的路由 16 * RouteDefinitionLocator:获取spring cloud gateway路由的详细信息 17 * RestTemplate:获取各个配置有swagger的服务的swagger-resources 18 */ 19 @Override 20 public List<SwaggerResource> get() { 21 List<SwaggerResource> resources = new ArrayList<>(); 22 List<String> routes = new ArrayList<>(); 23 //取出gateway的route 24 routeLocator.getRoutes().subscribe(route -> routes.add(route.getId())); 25 //结合配置的route-路径(Path),和route过滤,只获取有效的route节点 26 gatewayProperties.getRoutes().stream().filter(routeDefinition -> routes.contains(routeDefinition.getId())) 27 .forEach(routeDefinition -> routeDefinition.getPredicates().stream() 28 .filter(predicateDefinition -> ("Path").equalsIgnoreCase(predicateDefinition.getName())) 29 .forEach(predicateDefinition -> resources.add(swaggerResource(routeDefinition.getId(), 30 predicateDefinition.getArgs().get(NameUtils.GENERATED_NAME_PREFIX + "0") 31 .replace("/**", API_URI))))); 32 return resources; 33 } 34 35 private SwaggerResource swaggerResource(String name, String url) { 36 SwaggerResource swaggerResource = new SwaggerResource(); 37 swaggerResource.setName(name); 38 swaggerResource.setUrl(url); 39 swaggerResource.setSwaggerVersion("3.0.0"); 40 return swaggerResource; 41 } 42}
1@Configuration 2@EnableRedisRepositories 3public class RedisRepositoryConfig { 4 private final RedisConnectionFactory redisConnectionFactory; 5 6 public RedisRepositoryConfig(RedisConnectionFactory redisConnectionFactory) { 7 this.redisConnectionFactory = redisConnectionFactory; 8 } 9 @Bean 10 public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) { 11 RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>(); 12 redisTemplate.setConnectionFactory(connectionFactory); 13 StringRedisSerializer stringRedisSerializer = new StringRedisSerializer(); 14 redisTemplate.setKeySerializer(stringRedisSerializer); 15 redisTemplate.setHashKeySerializer(stringRedisSerializer); 16 Jackson2JsonRedisSerializer<?> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class); 17 redisTemplate.setValueSerializer(jackson2JsonRedisSerializer); 18 redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer); 19 redisTemplate.afterPropertiesSet(); 20 return redisTemplate; 21 } 22 23 @Bean 24 public TokenStore tokenStore(){ 25 return new RedisTokenStore(redisConnectionFactory); 26 } 27}
1@AllArgsConstructor 2@Configuration 3@EnableWebFluxSecurity 4public class ResourceServerConfig { 5 private final AuthorizationManager authorizationManager; 6 private final IgnoreUrlsConfig ignoreUrlsConfig; 7 private final RestfulAccessDeniedHandler restfulAccessDeniedHandler; 8 private final RestAuthenticationEntryPoint restAuthenticationEntryPoint; 9 private final IgnoreUrlsRemoveJwtFilter ignoreUrlsRemoveJwtFilter; 10 11 @Bean 12 public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) { 13 http.oauth2ResourceServer().jwt() 14 .jwtAuthenticationConverter(jwtAuthenticationConverter()); 15 http.oauth2ResourceServer().authenticationEntryPoint(restAuthenticationEntryPoint); 16 17 //对白名单路径,直接移除JWT请求头 18 http.addFilterBefore(ignoreUrlsRemoveJwtFilter, SecurityWebFiltersOrder.AUTHENTICATION); 19 http.authorizeExchange() 20 .pathMatchers(ArrayUtil.toArray(ignoreUrlsConfig.getUrls(),String.class)).permitAll()//白名单配置 21 .anyExchange().access(authorizationManager)//鉴权管理器配置 22 .and().exceptionHandling() 23 .authenticationEntryPoint(restAuthenticationEntryPoint)//处理未认证 24 .accessDeniedHandler(restfulAccessDeniedHandler)//处理未授权 25 .and().csrf().disable(); 26 return http.build(); 27 } 28 29 @Bean 30 public Converter<Jwt, ? extends Mono<? extends AbstractAuthenticationToken>> jwtAuthenticationConverter() { 31 JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter(); 32 jwtGrantedAuthoritiesConverter.setAuthorityPrefix(AuthConstant.AUTHORITY_PREFIX); 33 jwtGrantedAuthoritiesConverter.setAuthoritiesClaimName(AuthConstant.AUTHORITY_CLAIM_NAME); 34 JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter(); 35 jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter); 36 return new ReactiveJwtAuthenticationConverterAdapter(jwtAuthenticationConverter); 37 } 38}

以上贴出的是主要代码,全部代码请参考:

GitHub - pipijoe/gateway-service: spring cloud gateway 网关服务demo

之前2期关于资源服务器和认证服务器的博客地址:

SpringCloud微服务开发(三):搭建认证服务器

SpringCloud微服务开发(一):搭建资源服务器

2023年05月04日
在初学者眼中,世界充满了可能;专家眼中,世界大都已经既定。--铃木俊隆