了解springcloud Gateway
网关作为流量的入口,常用功能包括路由转发、权限校验、限流控制等。而springcloud gateway作为SpringCloud官方推出的第二代网关框架,取代了Zuul网关。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
springcloud gateway使用RouteLocator的Bean进行路由转发,对请求进行处理,并转发给下游服务处理。
@Bean
public RouteLocator myRoutes(RouteLocatorBuilder builder) {
return builder.routes()
.route(p -> p
.path("/get")
.filters(f -> f.addRequestHeader("Hello", "World"))
.uri("http://httpbin.org:80"))
.build();
}
gateway过滤器
sprincloud gateway中主要有两种类型的过滤器:GlobalFilter
和 GatewayFilter
- GlobalFilter : 全局过滤器,对所有的路由均起作用
- GatewayFilter : 只对
指定的路由
起作用,单路由过滤器
自定义GatewayFilter
自定义GatewayFilter有两种实现方式,一种是直接实现GatewayFilter接口,另一种是继承AbstractGatewayFilterFactory类 ,任意选一种即可
1、指定路由,应用GatewayFilter接口
/**
* token校验过滤器
*/
public class AuthorizeGatewayFilter implements GatewayFilter, Ordered {
private static final String AUTHORIZE_TOKEN = "token";
private static final String AUTHORIZE_UID = "uid";
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
HttpHeaders headers = request.getHeaders();
String token = headers.getFirst(AUTHORIZE_TOKEN);
String uid = headers.getFirst(AUTHORIZE_UID);
if (token == null) {
token = request.getQueryParams().getFirst(AUTHORIZE_TOKEN);
}
if (uid == null) {
uid = request.getQueryParams().getFirst(AUTHORIZE_UID);
}
ServerHttpResponse response = exchange.getResponse();
if (StringUtils.isEmpty(token) || StringUtils.isEmpty(uid)) {
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}
String authToken = stringRedisTemplate.opsForValue().get(uid);
if (authToken == null || !authToken.equals(token)) {
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 0;
}
}
// 对指定路由的应用
@Bean
public RouteLocator routeLocator(RouteLocatorBuilder builder) {
return builder.routes().route(r ->
r.path("/user/list")
.uri("http://localhost:8080/api/user/list")
.filters(new AuthorizeGatewayFilter())
.id("user-service"))
.build();
}
2、指定路由,继承AbstractGatewayFilterFactory
/**
* 自定义basic认证
*/
@Slf4j
@Component
public class HttpBasicGatewayFilter extends AbstractGatewayFilterFactory {
@Override
public GatewayFilter apply(Object config) {
return (exchange, chain) -> {
if (hasAuth(exchange)) {
return chain.filter(exchange);
} else {
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.UNAUTHORIZED);
response.getHeaders().add(HttpHeaders.WWW_AUTHENTICATE, "Basic Realm="xaax"");
return response.setComplete();
}
};
}
/**
* 简单的basic认证
*
* @param exchange 上下文
* @return 是否有权限
*/
private boolean hasAuth(ServerWebExchange exchange) {
ServerHttpRequest request = exchange.getRequest();
String auth = request.getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
log.info("Basic认证信息为:{}", auth);
return true;
}
}
再举一例
/**
* @date 2018/7/4
* 验证码处理
*/
@Slf4j
@Component
@AllArgsConstructor
public class ValidateCodeGatewayFilter extends AbstractGatewayFilterFactory {
private final ObjectMapper objectMapper;
private final RedisTemplate redisTemplate;
private final FilterIgnorePropertiesConfig filterIgnorePropertiesConfig;
@Override
public GatewayFilter apply(Object config) {
return (exchange, chain) -> {
ServerHttpRequest request = exchange.getRequest();
// 不是登录请求,直接向下执行
if (!StrUtil.containsAnyIgnoreCase(request.getURI().getPath()
, SecurityConstants.OAUTH_TOKEN_URL, SecurityConstants.SMS_TOKEN_URL
, SecurityConstants.SOCIAL_TOKEN_URL)) {
return chain.filter(exchange);
}
// 刷新token,直接向下执行
String grantType = request.getQueryParams().getFirst("grant_type");
if (StrUtil.equals(SecurityConstants.REFRESH_TOKEN, grantType)) {
return chain.filter(exchange);
}
// 终端设置不校验, 直接向下执行
try {
String[] clientInfos = WebUtils.getClientId(request);
if (filterIgnorePropertiesConfig.getClients().contains(clientInfos[0])) {
return chain.filter(exchange);
}
// 如果是社交登录,判断是否包含SMS
if (StrUtil.containsAnyIgnoreCase(request.getURI().getPath(), SecurityConstants.SOCIAL_TOKEN_URL)) {
String mobile = request.getQueryParams().getFirst("mobile");
if (StrUtil.containsAny(mobile, LoginTypeEnum.SMS.getType())) {
throw new ValidateCodeException("验证码不合法");
} else {
return chain.filter(exchange);
}
}
//校验验证码
checkCode(request);
} catch (Exception e) {
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.PRECONDITION_REQUIRED);
try {
return response.writeWith(Mono.just(response.bufferFactory()
.wrap(objectMapper.writeValueAsBytes(
R.builder().msg(e.getMessage())
.code(CommonConstants.FAIL).build()))));
} catch (JsonProcessingException e1) {
log.error("对象输出异常", e1);
}
}
return chain.filter(exchange);
};
}
/**
* 检查code
*
* @param request
*/
@SneakyThrows
private void checkCode(ServerHttpRequest request) {
String code = request.getQueryParams().getFirst("code");
if (StrUtil.isBlank(code)) {
throw new ValidateCodeException("验证码不能为空");
}
String randomStr = request.getQueryParams().getFirst("randomStr");
if (StrUtil.isBlank(randomStr)) {
randomStr = request.getQueryParams().getFirst("mobile");
}
String key = CommonConstants.DEFAULT_CODE_KEY + randomStr;
redisTemplate.setKeySerializer(new StringRedisSerializer());
if (!redisTemplate.hasKey(key)) {
throw new ValidateCodeException("验证码不合法");
}
Object codeObj = redisTemplate.opsForValue().get(key);
if (codeObj == null) {
throw new ValidateCodeException("验证码不合法");
}
String saveCode = codeObj.toString();
if (StrUtil.isBlank(saveCode)) {
redisTemplate.delete(key);
throw new ValidateCodeException("验证码不合法");
}
if (!StrUtil.equals(saveCode, code)) {
redisTemplate.delete(key);
throw new ValidateCodeException("验证码不合法");
}
redisTemplate.delete(key);
}
}
自定义GlobalFilter
只需要添加 @Component 注解,不需要进行任何额外的配置,实现GlobalFilter接口,自动会对所有的路由起作用
/**
*
* token校验全局过滤器
*/
@Component
public class AuthorizeFilter implements GlobalFilter, Ordered {
private static final String AUTHORIZE_TOKEN = "token";
private static final String AUTHORIZE_UID = "uid";
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
HttpHeaders headers = request.getHeaders();
String token = headers.getFirst(AUTHORIZE_TOKEN);
String uid = headers.getFirst(AUTHORIZE_UID);
if (token == null) {
token = request.getQueryParams().getFirst(AUTHORIZE_TOKEN);
}
if (uid == null) {
uid = request.getQueryParams().getFirst(AUTHORIZE_UID);
}
ServerHttpResponse response = exchange.getResponse();
if (StringUtils.isEmpty(token) || StringUtils.isEmpty(uid)) {
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}
String authToken = stringRedisTemplate.opsForValue().get(uid);
if (authToken == null || !authToken.equals(token)) {
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 0;
}
}
springboot依赖
springcloud gateway需要排除spring-boot-starter-web引入spring-boot-starter-webflux
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>