服务网关
服务网关提供请求的统一入口,因此必须保证服务网关的稳定性和高可用,并且在性能、并发性方面好,确保服务安全性,扩展性好。
1、 常用的网关方案有:
- Nginx + Lua
- Tyk
- Kong
- Spring Cloud Zuul
2、Zuul的特点
- 路由 + 过滤器 = Zuul
- 核心是一系列的过滤器
3、Zuul四种过滤器API
- 前置过滤器(Pre)
- 后置过滤器(Post)
- 路由(route)
- 错误(Error)
Zuul编码实例
1、创建一个Api-gateWay工程
2、导入下面依赖:
<dependencies>
<!-- 配置中心依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<!-- Eureka 客户端依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- Zuul依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
3、配置文件
spring:
application:
name: api-gateway
cloud:
config:
discovery:
enabled: true
service-id: CONFIG
profile: dev
eureka:
client:
service-url:
defaultZone: http://localhost:8762/eureka
4、 在启动类中添加注解@EnableZuulProxy
@SpringBootApplication
@EnableZuulProxy
public class ApiGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(ApiGatewayApplication.class, args);
}
5、测试
通过路由访问商品列表接口:http://localhost:9080/product/list
测试接口:http://localhost:8050/product/product/list
- 第一个product代表服务名称
- 后面的 product/list 表示资源路径
此时就可以进行路由转发基本功能。
此时必须通过服务名进行路由,下面通过配置进行自定义路由:
zuul:
routes:
myProduct: #随意自定义
path: /myProduct/**
serviceId: product
如果想禁止某个路由被访问,可以进行如下配置:
zuul:
# 排除某些路由
ignored-patterns:
- /**/product/list
zuul的Cookie传递
zuul默认的会对cookir进行过滤,我们只要在配置中将sensitiveHeaders
设置为null即可
zuul:
routes:
myProduct: #随意自定义
path: /myProduct/**
serviceId: product
# 设置为null
sensitiveHeaders:
此时在进行http请求,后台就可以获取到cookie.
动态路由
将配置部分配置到统一配置中心,在程序中想要获取属性值时,
我们可以通过添加Zuulconfig,创建ZuulProperties类。
/**
* 动态路由 参数获取
*/
@Component
public class ZuulConfig {
@ConfigurationProperties("zuul")
@RefreshScope
public ZuulProperties zuulProperties() {
return new ZuulProperties();
}
}
Pre和Post过滤器
1、pre过滤案例:所有请求都有一个token
@Component
public class TokenFilter extends ZuulFilter{
/**
* 过滤类型设置
* @return
*/
@Override
public String filterType() {
return PRE_TYPE;
}
/**
* 过滤顺序设置
* @return
*/
@Override
public int filterOrder() {
return PRE_DECORATION_FILTER_ORDER - 1;
}
/**
* 返回true
* @return
*/
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() throws ZuulException {
System.out.println("进来没.................");
RequestContext requestContext = RequestContext.getCurrentContext();
HttpServletRequest request = requestContext.getRequest();
// 这里从url参数里获取,也可以从cookie,header获取
String token = request.getParameter("token");
if (StringUtils.isEmpty(token)) {
// 返回沒有权限
requestContext.setSendZuulResponse(false);
requestContext.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
}
return null;
}
}
2、Post过滤案例:对返回的数据做一些处理
@Component
public class AddResponseHeaderFilter extends ZuulFilter{
public AddResponseHeaderFilter() {
super();
}
@Override
public String filterType() {
return POST_TYPE;
}
@Override
public int filterOrder() {
return SEND_RESPONSE_FILTER_ORDER - 1;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() throws ZuulException {
RequestContext requestContext = RequestContext.getCurrentContext();
HttpServletResponse response = requestContext.getResponse();
response.setHeader("X-Foo", UUID.randomUUID().toString());
return null;
}
}
限流
限流采用的令牌桶算法,先将符合规定的令牌放入桶内,再从桶内取出令牌。
案例实现,Google已经实现了令牌桶算法。
/**
* 限流 ,采用的令牌桶算法
*/
public class RateLimiterFilter extends ZuulFilter{
// 每秒中放100个令牌
private static final RateLimiter RATE_LIMITER = RateLimiter.create(100);
@Override
public String filterType() {
return PRE_TYPE;
}
@Override
public int filterOrder() {
return SERVLET_DETECTION_FILTER_ORDER - 1;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() throws ZuulException {
// tryAcquire去取令牌
// 判断没有取到令牌
if (!RATE_LIMITER.tryAcquire()) {
throw new RateLimitException();
}
return null;
}
}
权限校验
根据不同的用户,访问不同的请求
package com.reder.filter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import com.reder.constant.RedisConstant;
import com.reder.utils.CookieUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_DECORATION_FILTER_ORDER;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_TYPE;
/**
* 权限拦截 (区分买家和卖家)
*/
@Component
public class AuthFilter extends ZuulFilter {
@Autowired
private StringRedisTemplate redisTemplate;
@Override
public String filterType() {
return PRE_TYPE;
}
@Override
public int filterOrder() {
return PRE_DECORATION_FILTER_ORDER - 1;
}
@Override
public boolean shouldFilter() {
return true ;
}
@Override
public Object run() throws ZuulException {
RequestContext requestContext = RequestContext.getCurrentContext();
HttpServletRequest request = requestContext.getRequest();
/** 依靠买家和卖家的特征,
* /order/create 只能买家访问(cookie 里有openid)
* /order/finish 只能卖家访问(cookie里有token,并且对应的redis中有值)
* /product/list 都可访问
*/
if ("order/order/create".equals(request.getRequestURI())) {
Cookie cookie = CookieUtil.get(request,"openid");
if (cookie == null || StringUtils.isEmpty(cookie.getValue())) {
requestContext.setSendZuulResponse(false);
requestContext.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
}
}
if ("order/order/finish".equals(request.getRequestURI())) {
Cookie cookie = CookieUtil.get(request, "token");
String redisValue = redisTemplate.opsForValue().get(String.format(RedisConstant.TOKEN_TEMPLATE,cookie.getValue()));
if (cookie == null || StringUtils.isEmpty(cookie.getValue()) || StringUtils.isEmpty(redisValue)) {
requestContext.setSendZuulResponse(false);
requestContext.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
}
}
return null;
}
}
在如上代码中,我们对买家和卖家不同的请求,进行权限控制,虽然实现了我们需要的功能,但是代码比较臃肿,当需求发生变化时,改起来比较麻烦。我们可以做一下改动:
- 将买家和卖家过滤请求拆分为两个类。
- 在是否应该过滤中进行条件判断,run()方法只做逻辑处理。
买家过滤代码如下:
package com.reder.filter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import com.reder.constant.RedisConstant;
import com.reder.utils.CookieUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_DECORATION_FILTER_ORDER;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_TYPE;
/**
* 权限拦截 (区分买家和卖家), 买家
*/
@Component
public class AuthBuyerFilter extends ZuulFilter {
private static final String BUYER_URI = "order/order/create";
@Autowired
private StringRedisTemplate redisTemplate;
@Override
public String filterType() {
return PRE_TYPE;
}
@Override
public int filterOrder() {
return PRE_DECORATION_FILTER_ORDER - 1;
}
/**
* 是否应该拦截
* @return 返回true为拦截
*/
@Override
public boolean shouldFilter() {
RequestContext requestContext = RequestContext.getCurrentContext();
HttpServletRequest request = requestContext.getRequest();
if (BUYER_URI.equals(request.getRequestURI())) {
return true;
}
return false ;
}
@Override
public Object run() throws ZuulException {
RequestContext requestContext = RequestContext.getCurrentContext();
HttpServletRequest request = requestContext.getRequest();
/** 依靠买家和卖家的特征,
* /order/create 只能买家访问(cookie 里有openid)
*
*/
Cookie cookie = CookieUtil.get(request,"openid");
if (cookie == null || StringUtils.isEmpty(cookie.getValue())) {
requestContext.setSendZuulResponse(false);
requestContext.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
}
return null;
}
}
卖家过滤代码如下:
package com.reder.filter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import com.reder.constant.RedisConstant;
import com.reder.utils.CookieUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_DECORATION_FILTER_ORDER;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_TYPE;
/**
* 权限拦截 (区分买家和卖家)
*/
@Component
public class AuthSellerFilter extends ZuulFilter {
private static final String SELLER_URI = "order/order/finish";
@Autowired
private StringRedisTemplate redisTemplate;
@Override
public String filterType() {
return PRE_TYPE;
}
@Override
public int filterOrder() {
return PRE_DECORATION_FILTER_ORDER - 1;
}
@Override
public boolean shouldFilter() {
RequestContext requestContext = RequestContext.getCurrentContext();
HttpServletRequest request = requestContext.getRequest();
if (SELLER_URI.equals(request.getRequestURI())) {
return true;
}
return false ;
}
@Override
public Object run() throws ZuulException {
RequestContext requestContext = RequestContext.getCurrentContext();
HttpServletRequest request = requestContext.getRequest();
/** 依靠买家和卖家的特征,
* /order/finish 只能卖家访问(cookie里有token,并且对应的redis中有值)
*/
Cookie cookie = CookieUtil.get(request, "token");
String redisValue = redisTemplate.opsForValue().get(String.format(RedisConstant.TOKEN_TEMPLATE,cookie.getValue()));
if (cookie == null || StringUtils.isEmpty(cookie.getValue()) || StringUtils.isEmpty(redisValue)) {
requestContext.setSendZuulResponse(false);
requestContext.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
}
return null;
}
}
跨域
ajax请求要符合同源策略,如果不符合同源策略就会发生跨域问题。
在解决跨域问题时,如果只有个别类和个别方法需要进行跨域请求,可以在该类或方法上添加@CrossOrigin
注解,当整个服务都有跨域问题时,可以在zuul进行如下跨域配置:
package com.reder.config;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import java.util.Arrays;
/**
* 跨域配置
*
*/
@Component
public class CorsConfig {
@Bean
public CorsFilter corsFilter() {
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
final CorsConfiguration config = new CorsConfiguration();
//
config.setAllowCredentials(true);
// 配置原始路径 http:www.a.com
config.setAllowedOrigins(Arrays.asList("*"));
config.setAllowedHeaders(Arrays.asList("*"));
config.setAllowedMethods(Arrays.asList("*"));
config.setMaxAge(300L);
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
}