项目整体架构图如下:
8.1 Zuul:Pre和Post过滤器
- 1.从以上架构图知道:所有的请求都会到Zuul,然后到ServicreA、然后到ServiceB、再到ServiceC、现在我们对整体服务做一个权限校验。假如没有zuul服务、那么ServicreA、ServicreB、ServicreC都得校验一次做的太多受不了。所以权限校验我们放在Zuul里面统一处理。
- 2.接下来我们演示如何对请求进行统一的校验,我们现在做一个所有经过Zuul的请求都要有一个token,并且内容不能为空,如果不带token参数的话,权限校验不通过。我们以:http://localhost:8084/myProduct/product/list?token=12121为例子.。我们只有在连接后面添加:token=?才允许校验通过。
8.1.1 Zuul统一校验
我们在api-gateway项目下面创建filter包,并新建一个TokenFilter类 - 1.新建TokenFilter做PRE过滤
a.这里面我们做的是参数校验:所以filterType是:PRE_TYPE。
b.我们在run()方法里面实现具体的逻辑。
|
|
测试:http://localhost:8084/myProduct/product/list
携带token测试:http://localhost:8084/myProduct/product/list?token=112
正常返回
当然我们的判断逻辑可能不止token为空,也可以拿到token值之后从数据库读。
- 2.新建Post过滤器
我们也可以定义postfilter,在请求到结果之后,对请求的结果进行处理加工。 这里我们往请求返回的header里面写一些东西,新建AddResponseHeaderFilter类。
a.filterType->POST_TYPE
b.自定义post filter时候,我们选择使用:SEND_RESPONSE_FILTER_ORDER之前的filter
|
|
测试:
8.2 Zuul:限流
8.2.1 限流基本理论
- 1.Zuul充当的是api网关的角色、每个请求都会经过它、在他上面做api限流保护、防止攻击。比如我们的api是发短信的、我们需要限制客户端请求速度、从而在一定程序上抵制短信大攻击、降低损失。
2.Zuul的限流是放在前置过滤器去做的、更具体来说、时机是在请求被转发之前调用。如果前置过滤器有多个操作、限流放到最靠前那个。比如:Zuul前置过滤器里面有限流、鉴权。那么限流应该早于鉴权。
3.限流方案很多,这里以一种来说:令牌桶流。
a.中间桶(token bucket):是存放令牌的、所以叫做令牌桶。
b.最上面会以固定的速率往令牌桶中添加令牌、如果已经放满了就丢掉。
c.外部请求过来、会从令牌桶中获取令牌、拿到令牌之后才可以继续往前走。如果拿不到令牌直接被拒绝。其实是和买房一样。
8.2.2 限流代码实现
限流也是一个Filter,我们以RateFilter
a.filterType–>PRE_TYPE
b.filterOrder–>优先级最高:优先级为最高:组件自带的优先级最高位-3 我们这边比他还高(SERVLET_DETECTION_FILTER_ORDER-1)
1.新建RateFilter类
8.3 Zuul:鉴权和添加用户服务
8.3.1 Zuul权限校验
我们使用Zuul的权限校验实现下面3个功能。
- 1./order/create 只能买家访问(创建订单)
- 2./order/finish 只能卖家访问(完结订单)
3./product/list 都可以访问(商品列表)
1.新建鉴权Filter(AuthFilter)
新建鉴权Filter、用来区分买家和卖家2.实现逻辑
怎样区分买家和卖家呢?有些人想到cookie、既然想到了cookie那么必须买家和卖家登录了之后才能获取到信息。那么登录功能写到哪里了,就需要有一个用户服务。
8.3.2 添加用户服务
- 1.API接口登录区分
- 2.数据库
|
|
@Entity
public class UserInfo {
@Id
private String id;
private String username;
private String password;
private String openid;
private Integer role;
public String getId() {
return id;
}
}
public interface UserService {
/**
- 通过openid来查询用户信息
- @param openid
@return
*/
UserInfo findByOpenid(String openid);
}
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserInfoRepository repository;
@Override
public UserInfo findByOpenid(String openid) {
return repository.findByOpenid(openid);
}
}1234567891011121314151617181920212223#### 8.3 Zuul:鉴权和添加用户服务1. 1.拷贝ResultVOUtil、ResultEnum到user![](https://raw.githubusercontent.com/startshineye/img/master/2019/12/8.png)2. 2.书写买家登录:![](https://raw.githubusercontent.com/startshineye/img/master/2019/12/9.png)3. 3.测试:a.正确输入:![](https://raw.githubusercontent.com/startshineye/img/master/2019/12/10.png)b.错误输入:![](https://raw.githubusercontent.com/startshineye/img/master/2019/12/11.png)c.数据库角色修成成2:![](https://raw.githubusercontent.com/startshineye/img/master/2019/12/12.png)4. 4.往redis里面书写数据买家登录时候先去redis里面找,如果有说明已经登录了,没有的话才设置cookie![](https://raw.githubusercontent.com/startshineye/img/master/2019/12/13.png)上面代码会出现一个问题,就是每次页面刷新,都会在redis里面写数据,导致很多脏数据产生,所以需要在卖家最前面添加判断。![](https://raw.githubusercontent.com/startshineye/img/master/2019/12/14.png)![](https://raw.githubusercontent.com/startshineye/img/master/2019/12/15.png)### 8.4 订单完结接口开发我们在api-gateway的AuthFilter里面进行过滤校验时候:/**
- 1./order/create 只能买家访问
- 2./order/finish 只能卖家访问
- 3./product/list 都可以访问
*/12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273分为以上3步,我们还差在Order服务中订单完结接口没有开发:"/order/finish";我们现在实现此接。### 8.5 权限校验完成#### 8.5.1 权限基本操作1. 1.先访问user(卖家登录:http://localhost:8085/login/seller?openid=xyz)结果如下:![](https://raw.githubusercontent.com/startshineye/img/master/2019/12/16.png)我们请求正常,并且也获取到cookie了,此时我们把cookie删掉.再次请求api-gateway(此时请求api-gateway的时候注释掉:TokenFilter中run认证)![](https://raw.githubusercontent.com/startshineye/img/master/2019/12/17.png)![](https://raw.githubusercontent.com/startshineye/img/master/2019/12/18.png)以上返回的是成功,但是cookies中没有值,之前提过Zuul敏感头设置:![](https://raw.githubusercontent.com/startshineye/img/master/2019/12/19.png)假如我们对所有服务都提供敏感头,都需要传递cookie,则:![](https://raw.githubusercontent.com/startshineye/img/master/2019/12/20.png)![](https://raw.githubusercontent.com/startshineye/img/master/2019/12/21.png)此时我们能够确保通过api-gateway是可以正常登录的。2. 2.接下来我们书写AuthFilter中业务逻辑。![](https://raw.githubusercontent.com/startshineye/img/master/2019/12/22.png)下面我们通过接口去调用:3. 3.我们通过postman创建订单(切记一定要加上order服务名)![](https://raw.githubusercontent.com/startshineye/img/master/2019/12/23.png)我们改掉:![](https://raw.githubusercontent.com/startshineye/img/master/2019/12/24.png)我们再次请求时候回返回401:权限不足->所以我们需要先登录注意:在postman中,每一个窗口是互相隔离的,我们在一个窗口中登录,在另一个窗口中也是不能请求过的。![](https://raw.githubusercontent.com/startshineye/img/master/2019/12/26.png)所以我们自己需要写入一个cookie到postman中去![](https://raw.githubusercontent.com/startshineye/img/master/2019/12/25.png)4. 4.上面我们使用if语句来判断不同授权用户,其实这种写法以后相当不好维护,如果用户很多,耦合进来判断,有一天假如对卖家放开不限制,那么只能删掉,如何改造呢?我们一般对卖家和买家各自做一个filter:a.拦截统一在shouldFilter方法中b.拦截后处理统一在run方法中AuthBuyerFilter:![](https://raw.githubusercontent.com/startshineye/img/master/2019/12/27.png)AuthSellerFilter也是类似。#### 8.5.2 权限补充1. 1.大多数情况下,我们会把权限校验的uri存放到数据库中去,那是不是api-gateway直接去连接数据库进行判断呢?这里牵涉到一个边界的问题,api-gateway要做的是网关,如果去连接user服务的数据库,显然是不合适的,那能不能去调用user服务呢?调用user服务是合理的,应该去调用,但是每次鉴权的时候去调用user服务,user服务那边又去调用数据库的话,这样对数据库压力很大,我们应该使用redis,api-gateway直接去redis里面获取用户权限,那么redis里面的数据怎样过来呢?可以像之前"异步扣库存的方式->用户信息一变动,就发一个信息过来,网关这边监听消息,并记录到redis"2. 2.微服务架构下对所有服务都得鉴权,每个服务都需要明确当前用户的权限,在Zuul的前置过滤器里面实现相关逻辑是一个值得考虑的方案,同时在微服务框架中,多个服务的无状态化一般会考虑两种技术方案:a.分布式session(将用户认证信息存储在共享储存中,且通常用用户会话作为key来实现简单的分布式hash映射,当用户访问微服务时,用户数据可以从共享存储中获取,用户登录状态是不透明的,同时也是一个高可用且可扩展的解决方案)b.OAuth2与Spring Security结合3. 3.我们这有一个细节:用户模块的utils代码一直在copy,相同的我们就拷贝过来,我们少了一个基础服务(如果公司是一个大型服务改造的话,基础服务比较容易,一目连然拆出来,但是是一个从头开发的项目,没有多大把握,类似我们现在,建议是将每个微服务的公用组件放到公用模块里面去,比如我们现在的user,order,product服务都有common模块,有了一定积累后,很自然的将这块代码剥离出来,作为公共组件,下沉,成为公共的一个服务)4. 4.另外一点,在SpringCloud架构体系中的所有微服务都是通过Zuul对外提供统一访问入口,这个时候如果公司有两套系统,一套传统项目,一套微服务架构项目,让这两套项目在线上同时运行,Zuul会非常关键。### 8.6 跨域#### 8.6.1 跨域问题1. 1.我们一般的大型项目都是前后端分离的,前端通过ajax发送请求,浏览器的ajax是有同源策略的,如果违反了同源策略就会有跨域问题,Zuul作为微服务的api网关,在它上面处理跨域也是一种选择。2. 2.跨域其实可以看成是Spring的跨域,Spring跨域常用的一种方法是在:被调用的类或方法上增加@CrossOrigin注解来声明自己支持跨域访问,这种方式的缺点其实很明显,作用域在类或者方法上,微服务里面的应用就有很多个,一个应用里面的类和方法更加多3. 3.解决跨域的另一种方法是:在Zuul里增加CorsFilter过滤器,此方案是添加到网关上,对内部应用代码没有任何改造。#### 8.6.2 跨域改造##### 1.单个接口跨域1. 1.项目中跨域:我们在product服务里面的ProductController的list方法里面添加跨域注解:allowCredentials = "true"->true允许cookie跨域
@CrossOrigin(allowCredentials = “true”)
```
2.统一跨域设置
- 1.在api-gateway上添加配置文件: