代码已上传至github:
1. 网关Zuul的简介
什么是Zuul?
我们已知,开启整个微服务工程项目的时候,都会在发送http请求的时候,通过一个路由的选择来到达指定的module,而网关的作用就是
为什么需要微服务网关:
不同的微服务都有不同的网络地址,而外部的客户端需要调用多个服务的接口才能完成一个业务需求,比如一个sso,我们需要sso server的login给我们/oauth/token,sso client的redirect,token的解析,而且client是很多很多的,这就很难受,我们假设这里有1个sso-server,n个sso-client
会存在以下问题:
- 客户端多次请求不同的微服务,会很复杂
- cors的时候,也很复杂
- Authorization的时候,每一步都需要做配置
所以我们就可以依靠网关来解决这个问题了:
首先先导入依赖:
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
然后配置
server:
port: 9012
spring:
application:
name: tensquare-manager
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:6868/eureka/
instance:
prefer-ip-address: true
zuul:
routes:
tensquare-base:
path: /base/**
serviceId: tensquare-base
tensquare_friend:
path: /article/**
serviceId: tensquare_friend
tensquare_gathering:
path: /gathering/**
serviceId: tensquare_gathering
tensquare_qa:
path: /qa/**
serviceId: tensquare_qa
tensquare_recruit:
path: /recruit/**
serviceId: tensquare_recruit
tensquare_spit:
path: /spit/**
serviceId: tensquare_spit
tensquare_user:
path: /user/**
serviceId: tensquare_user
启动类:
用这个来开启Zuul的网关效果
然后我们按照顺序,我们启动EurekaApplication,BaseApplication,ManagerApplication
这个时候,我们访问base模块(端口9001)的一个接口,用原始的路径去访问的话:
自然是可以正常连通的,这没问题吧
而网关的作用是什么呢,就是让我们访问Manager模块(端口号9012),但是能通过路由表,找到我们任意工程内的一个模块
而这个路径是如何选择的呢,以贴图为例,我们找到配置文件:
意思也就是说,当我们选中了Manager网关模块的9012端口,并且把url自动带上/base之后,就可以自动去映射到你serviceId指定的模块了,然后再跟上你这个匹配模块的控制层,就能自由的去发送http请求了。
而网关在映射的过程中,也是存在过滤的能力的,并且这个过滤器已经被封装好了,我们可以通过集成ZuulFilter来实现过滤,我下面来介绍一下过滤器中的四个方法:
但如果我们需要转发Authorization给该有的模块,做权限验证的话,就需要获得到Http请求的上下文,然后再传递给转发的模块。
于是我们可以写一个在前台专门来获取request上下文并且转发的WebFilter。
@Component
public class WebFilter extends ZuulFilter {
@Override
public String filterType() {
return "pre";
}
@Override
public int filterOrder() {
return 0;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() throws ZuulException {
//得到 request上下文
RequestContext currentContext = RequestContext.getCurrentContext();
//得到request域
HttpServletRequest request = currentContext.getRequest();
//得到头信息
String header = request.getHeader("Authorization");
//判断是否真的有头信息
System.out.println(header);
if (header != null && !"".equals(header)) {
//把头信息继续向下传
currentContext.addZuulRequestHeader("Authorization", header);
}
return null;
}
}
之后,我们获取到了头信息,也就间接的获取到了token给了后端,现在我们要做的是将token去解析出来,获取到用户的角色信息,权限信息等等。
在这里,我们所选的是无状态的jwt整合SpringSecurity去做的token加密。
JWTUtil的工具类,yml等等就不说了,主要说一些核心内容,SpringSecurity整合jwt的无状态认证授权也可以看这篇文章。
https://blog.csdn.net/qq_41936805/article/details/102780732
好,那我们既然已经将Authorization这个Header传下去了,那么接下来我们就需要通过他获取到的token去解析,然后进行一个统一的配置。
我们可以用getRole来获取到角色,然后进行统一的拦截,也就相当于是一个过滤器了,所以我们也就把相关代码继续写在刚才的过滤器里面,只不过不是写在前台过滤器WebFilter,而是写经由WebFilter传递token的后台过滤器ManagerFilter.
在这里,我们可以进行统一的配置
@Component
public class ManagerFilter extends ZuulFilter {
@Autowired
private JwtUtil jwtUtil;
@Override
public String filterType() {
//return "pre"代表操作之前执行过滤,return "post"代表操作之后执行
return "pre";
}
@Override
public int filterOrder() {
//0表示优先执行,当我们有很多过滤器的时候,可以用数字表示执行的级别,数字越小级别越高
return 0;
}
@Override
public boolean shouldFilter() {
//当前过滤器是否开启,true是开启,false是关闭
return true;
}
@Override
public Object run() throws ZuulException {
//过滤器内执行的操作 return 任何object的值都表示放行
//setSendZuulResponse(false)表示不再继续执行
RequestContext requestContext = RequestContext.getCurrentContext();
//request域
HttpServletRequest request = requestContext.getRequest();
if (request.getMethod().equals("OPTIONS")) {
return null;
}
if (request.getRequestURI().indexOf("login") > 0) {
return null;
}
//得到头信息
String header = request.getHeader("Authorization");
if (header != null && !"".equals(header)) {
if (header.startsWith("Bearer ")) {
String token = header.substring(7);
try {
Claims claims = jwtUtil.parseJWT(token);
String roles = (String) claims.get("role");
if (roles.equals("admin")) {
//把头信息转发,放行
requestContext.addZuulRequestHeader("Authorization", header);
return null;
}
} catch (Exception e) {
e.printStackTrace();
requestContext.setSendZuulResponse(false);//终止运行
}
}
}
requestContext.setSendZuulResponse(false);//终止运行
requestContext.setResponseStatusCode(403);//权限不足
requestContext.setResponseBody("权限不足");
requestContext.getResponse().setContentType("text/html;charset=utf-8");
return null;
}
}