2年前有幸使用过一次Spring Cloud (1.5.9),那次用的是ZUUL做网关,没有使用Gateway做网关,一直是个小遗憾。终于在2年后的19年底再次使用Spring Cloud,这次有机会可以自己选择架构,果断使用Spring Cloud 全家桶。网关就是原生的Spring Cloud Gateway。项目架构图如下:
1.使用Route结合Hystrix实现默认降级策略
2.使用GatewayFilter实现登录态(token)校验
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
|
如上图,我们开启了2个微服务route路由。
1 2 3 4 5 6 7 8 9 10 11 |
|
3.2.1 自定义过滤器
自定义过滤器,实现GatewayFilter, Ordered 2个接口。
1 import com.*.auth.UserTokenTools; 2 import lombok.extern.slf4j.Slf4j; 3 import org.apache.commons.lang3.StringUtils; 4 import org.springframework.cloud.gateway.filter.GatewayFilter; 5 import org.springframework.cloud.gateway.filter.GatewayFilterChain; 6 import org.springframework.core.Ordered; 7 import org.springframework.http.HttpHeaders; 8 import org.springframework.http.HttpStatus; 9 import org.springframework.http.server.reactive.ServerHttpRequest; 10 import org.springframework.http.server.reactive.ServerHttpResponse; 11 import org.springframework.stereotype.Component; 12 import org.springframework.web.server.ServerWebExchange; 13 import reactor.core.publisher.Mono; 14 15 /** 16 * @author denny.zhang 17 * @Description token过滤器 18 * @date 2019/12/12 13:55 19 */ 20 @Slf4j 21 @Component 22 public class LoginTokenFilter implements GatewayFilter, Ordered { 23 24 private static final String AUTHORIZE_TOKEN = "Authorization"; 25 private static final String BEARER = "Bearer "; 26 27 /** 28 * token过滤 29 * 30 * @param exchange 31 * @param chain 32 * @return 33 */ 34 @Override 35 public Monofilter(ServerWebExchange exchange, GatewayFilterChain chain) { 36 log.info("当前环境已开启token校验"); 37 ServerHttpRequest request = exchange.getRequest(); 38 HttpHeaders headers = request.getHeaders(); 39 ServerHttpResponse response = exchange.getResponse(); 40 // 取Authorization 41 String tokenHeader = headers.getFirst(AUTHORIZE_TOKEN); 42 log.info("tokenHeader=" + tokenHeader); 43 // token不存在 44 if (StringUtils.isEmpty(tokenHeader)) { 45 response.setStatusCode(HttpStatus.UNAUTHORIZED); 46 return response.setComplete(); 47 } 48 // 取token 49 String token = this.getToken(tokenHeader); 50 log.info("token=" + token); 51 52 // token不存在 53 if (StringUtils.isEmpty(token)) { 54 log.info("token不存在"); 55 response.setStatusCode(HttpStatus.UNAUTHORIZED); 56 return response.setComplete(); 57 } 58 // 校验 token是否失效 59 if (UserTokenTools.isTokenExpired(token, null)) { 60 log.info("token失效"); 61 response.setStatusCode(HttpStatus.UNAUTHORIZED); 62 return response.setComplete(); 63 } 64 // 校验 token是否正确 65 if (!UserTokenTools.checkToken(token, null)) { 66 response.setStatusCode(HttpStatus.UNAUTHORIZED); 67 return response.setComplete(); 68 } 69 70 // //有token 这里可根据具体情况,看是否需要在gateway直接把解析出来的用户信息塞进请求中,我们最终没有使用 71 // UserTokenInfo userTokenInfo = UserTokenTools.getUserTokenInfo(token); 72 // log.info("token={},userTokenInfo={}",token,userTokenInfo); 73 // request.getQueryParams().add("token",token); 74 //request.getHeaders().set("token", token); 75 return chain.filter(exchange); 76 } 77 78 79 @Override 80 public int getOrder() { 81 return -10; 82 } 83 84 /** 85 * 解析Token 86 */ 87 public String getToken(String requestHeader) { 88 //2.Cookie中没有从header中获取 89 if (requestHeader != null && requestHeader.startsWith(BEARER)) { 90 return requestHeader.substring(7); 91 } 92 return ""; 93 } 94 }
上图中,UserTokenTools是我们自定义的一个JWT工具类,用来生成token,校验token过期、正确等。
3.2.2 配置路由
大家可根据具体情况,如果只有一套登录态,那就用一个filter即可。
1 import com.*.gateway.filter.AuthorizeGatewayFilter; 2 import com.*.gateway.filter.LoginTokenFilter; 3 import org.springframework.cloud.gateway.route.RouteLocator; 4 import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; 5 import org.springframework.context.annotation.Bean; 6 import org.springframework.context.annotation.Configuration; 7 8 9 @Configuration 10 public class GatewayConfig { 11 12 @Bean 13 public RouteLocator getRouteLocator(RouteLocatorBuilder builder) { 14 return builder.routes() 15 // token校验1 16 .route(predicateSpec -> predicateSpec 17 .path("/gateway/pay/card/**", "/gateway/app/**") 18 .filters(gatewayFilterSpec -> gatewayFilterSpec.stripPrefix(1).filter(new AuthorizeGatewayFilter())) 19 .uri("lb://OLOAN-PAY-SERVICE") 20 .id("OLOAN-PAY-SERVICE-token")) 21 22 // token校验2 23 .route(predicateSpec -> predicateSpec 24 .path("/gateway/order-audit/**", "/gateway/order/**", "/gateway/order-payment/**") 25 .filters(gatewayFilterSpec -> gatewayFilterSpec.stripPrefix(1).filter(new LoginTokenFilter())) 26 .uri("lb://OLOAN-ORDER-SERVICE") 27 .id("OLOAN-ORDER-ORDER-token")) 28 .build(); 29 } 30 }
1.Spring Cloud Gateway使用WebFlux,和spring boot web包冲突,使用时一定记得pom中排除相关jar,否则会报错。
1 2 3 4 5 6 7 8 9 10 |
|
2.Gateway Filter 自带的源码支撑错误码response.setStatusCode(HttpStatus.UNAUTHORIZED);并不是那么的友好。错误码枚举使用的是spring自带框架的枚举类:
org.springframework.http.HttpStatus:
BAD_REQUEST(400, "Bad Request"),
。这样请求返回的结构体和一般定义的(code message data)不同。当然官方也是提供了解决方案。后续再去优化吧。