前言
类似于nginx,可以配置一些url前缀,匹配路由到对应的服务。还可以
提供限流。
依赖
<!--api网关-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
配置
spring:
cloud:
gateway:
default-filters:
- name: Retry
args:
retries: 3
statuses: BAD_GATEWAY
- AddResponseHeader=X-Response-Default-Foo, Default-Bar
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 2 #流速 每秒
redis-rate-limiter.burstCapacity: 10 #令牌桶的容积
rate-limiter: "#{@defaultRedisRateLimiter}" #SPEL表达式去的对应的bean
key-resolver: "#{@apiKeyResolver}" #SPEL表达式去的对应的bean
routes:
# This route rule used to forward request to activity server
- id: activity-route
uri: lb://activity
predicates:
- Path=/activity/**
filters:
- StripPrefix=1
discovery:
locator:
enabled: true
routes:对应路由配置,Gateway默认转发是全路径的,设置StripPrefix=1表示从二级url路径转发,即http://localhost:port/activity/test将会转发到http://{activity}/test
@Configuration
public class DefaultRedisRateLimiter extends RedisRateLimiter {
Config getDefaultConfig() {
return super.getConfig().get("defaultFilters");
}
public DefaultRedisRateLimiter(ReactiveRedisTemplate<String, String> redisTemplate,
RedisScript<List<Long>> script,
@Qualifier("defaultValidator") Validator validator) {
super(redisTemplate, script, validator);
}
@Override
public Mono<Response> isAllowed(String routeId, String id) {
if (null == super.getConfig().get(routeId))
getConfig().put(routeId, getDefaultConfig());
return super.isAllowed(routeId, id);
}
}
/**
* 自定义限流标志的key,多个维度可以从这里入手
* exchange对象中获取服务ID、请求信息,用户信息等
*/
@Component
public class RequestRateLimiterConfig {
/**
* ip地址限流
*
* @return 限流key
*/
@Bean
@Primary
public KeyResolver remoteAddressKeyResolver() {
return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getHostName());
}
/**
* 请求路径限流
*
* @return 限流key
*/
@Bean
public KeyResolver apiKeyResolver() {
return exchange -> Mono.just(exchange.getRequest().getPath().value());
}
/**
* username限流
*
* @return 限流key
*/
@Bean
public KeyResolver userKeyResolver() {
return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("username"));
}
}
权限认证
/**
* 请求url权限校验
*/
@Configuration
@ComponentScan(basePackages = "com.springboot.cloud.auth.client")
@Slf4j
public class AccessGatewayFilter implements GlobalFilter {
private static final String X_CLIENT_TOKEN_USER = "x-client-token-user";
private static final String X_CLIENT_TOKEN = "x-client-token";
/**
* 由authentication-client模块提供签权的feign客户端
*/
@Autowired
private IAuthService authService;
@Autowired
private IPermissionService permissionService;
/**
* 1.首先网关检查token是否有效,无效直接返回401,不调用签权服务
* 2.调用签权服务器看是否对该请求有权限,有权限进入下一个filter,没有权限返回401
*
* @param exchange
* @param chain
* @return
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String authentication = request.getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
String method = request.getMethodValue();
String url = request.getPath().value();
log.debug("url:{},method:{},headers:{}", url, method, request.getHeaders());
//不需要网关签权的url
if (authService.ignoreAuthentication(url)) {
return chain.filter(exchange);
}
//调用签权服务看用户是否有权限,若有权限进入下一个filter
if (permissionService.permission(authentication, url, method)) {
ServerHttpRequest.Builder builder = request.mutate();
//TODO 转发的请求都加上服务间认证token
builder.header(X_CLIENT_TOKEN, "TODO zhoutaoo添加服务间简单认证");
//将jwt token中的用户信息传给服务
builder.header(X_CLIENT_TOKEN_USER, getUserToken(authentication));
return chain.filter(exchange.mutate().request(builder.build()).build());
}
return unauthorized(exchange);
}
/**
* 提取jwt token中的数据,转为json
*
* @param authentication
* @return
*/
private String getUserToken(String authentication) {
String token = "{}";
try {
token = new ObjectMapper().writeValueAsString(authService.getJwt(authentication).getBody());
return token;
} catch (JsonProcessingException e) {
log.error("token json error:{}", e.getMessage());
}
return token;
}
/**
* 网关拒绝,返回401
*
* @param
*/
private Mono<Void> unauthorized(ServerWebExchange serverWebExchange) {
serverWebExchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
DataBuffer buffer = serverWebExchange.getResponse()
.bufferFactory().wrap(HttpStatus.UNAUTHORIZED.getReasonPhrase().getBytes());
return serverWebExchange.getResponse().writeWith(Flux.just(buffer));
}
}