微服务网关
网关
网络的关口,负责请求的路由,转发,身份校验
当我们把一个单体项目分成多个微服务并部署在多台服务器中,这时由于地址的不同,可能出现身份校验,地址过多等问题
我们通过网关,将微服务集群统一在一个网关之下,并在通过注册中心将服务拉取到网关之中,自此我们直接访问网关,由网关进行对应微服务的路由转发和身份校验
在SpringCloud中网关的实现包括两种:
Spring Cloud Gateway
:基于WebFlux响应式编程,无需调优即可获取优异性能
Netflix
:基于Servlet的阻塞式编程,需要调优才能获取与SpringCloudGateway类似的性能
网关路由
快速入门
网关判断由哪个微服务进行处理的过程就是网关路由
- 引入依赖
网关
,Nacos
,Loadbalancer
java
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
- 创建一个新的网关微服务模块,并创建启动类
java
@SpringBootApplication
public class GatewayApplication{
public static void main(String[] args){
SpringApplication.run(GatewayApplication.Class,args);
}
}
- 配置网关配置
yml
server:
port: 8080
spring:
application:
name: gateway
cloud:
nacos:
server-addr: 服务器地址:8848
gateway:
routes:
-id: 对应的服务名称
uri: lb://服务名称 # lb意思是负载均衡
predicates:
- Path=/路径名称/**
# 配置多个路由
-id:
路由属性
网关路由对应的java类型是RouteDefinition,其中常见的属性:
id
:路由的唯一标识
uri
:路由的目标地址
predicates
:路由断言,判断请求是否符合当前路由
filters
:路由过滤器,对请求或响应做特殊处理
路由断言
Spring提供了12种基本的RoutePredicateFactory实现:
网关提供了很多的路由过滤器,每种过滤器都有独特的作用:
对应的过滤器可以在配置文件中进行配置
网关登录校验
此处为网关内部的处理逻辑,我们要进行登录校验的处理需要在过滤器的最前面添加一个自定义的过滤器,在pre阶段进行登录校验
校验成功之后,网关如何将信息传递给微服务:
将用户信息保存到请求头当中进行传递
微服务之间该如何传递校验信息:
也是保存信息到请求头当中,但是请求头的发送是由Openfeign来实现的
自定义过滤器
网关过滤器有两种:
GatewayFilter
:路由过滤器,作用于任意指定的路由,默认不生效,要配置到路由后生效(在配置文件中进行配置)
GlobalFilter
:全局过滤器,作用范围是所有路由,声明后自动生效(自定义之后在配置文件中使用)
我们这里通过GlobalFilter
进行登录校验的实现
java
public interface GlobalFilter{
Mono<Void> filter(ServerWebExchange exchange,GatewayFilterChain chain);
}
通过这个方法实现过滤器中的操作,第一个参数是请求上下文,包含整个过滤器链中的共享数据,比如request,response等等,第二个参数是过滤器链,当前过滤器执行完成之后,要调用过滤器中的下一个过滤器
网关采用非阻塞式的编程,Mono中编写一个回调函数,当过滤器将来执行到POST的时候调用回调函数,过滤器将不再进行长时间的等待
我们实现该接口实现过滤器:
java
@Component
public class MyGlobalFilter implements GlobalFilter,Ordered{
@Override
public Mono<void> filter(ServerWebExchange exchange,GatewayFilterChain chain){
//利用exchange进行登录校验的逻辑编写
//方向,将共享数据传递给下一个过滤器中
return chain.filter(exchange);
}
@Override
public int getOrder(){
return 0;
}
}
注意:查看过滤器链,将鼠标放在GlobalFilter
上按ctrl+H
实现Ordered接口,重写getOrder方法,返回的值越小,则过滤器在过滤器链中的优先级越高
自定义之后在配置文件中进行配置使用:
yml
spring:
cloud:
gateway:
default-filters:
-AddRequestHeader=a,b
通常我们使用上述的自定义过滤器,但是还有一种过滤器GatewayFilter自定义过滤器,使用过滤器工厂AbstractGateWayFilterFactory
:
java
@Component
public class GLQFilterFactory extends AbstractGatewayFilterFactory<Object>{
@Override
public GatewayFilter apply(Object config){
return new GatewayFilter(new GatewayFilter(){
@Override
public Mono<void> filter(ServerWebExchange exchange,GatewayFilterChain chain){
//编写过滤器逻辑
//放行
return chain.filter(exchange);
}
},1);
}
}
自定义过滤器类的类名的前缀就是将来的过滤器的名字,而后半部分应该是类名
实现登录校验
java
@Component
public class AuthGlobalFilter implements GlobalFilter,Ordered{
//不用登录校验的地址实体类
private final AuthProperties authproperties;
private final JWtTool jwttool;
//spring提供的匹配工具类
private final AntPathMatcher antpathMatcher=new AntPathMatcher();
@Override
public Mono<Void> filter(ServerWebExchange exchange,GatewayFilterChain chain){
//获取request
ServerHttpRequest request= exchange.getRequest();
//判断是否需要做登录校验(向注册,登录界面就不需要做登录校验)
if(isExclude(request.getPath().toString())){
//放行
return chain.filter(exchange);
}
//获取token
String token=null;
List<String> headers=request.getHeaders().get("token");
if(headers !=null && !headers.isEmply()){
token=headers.get(0);
}
//利用jwt工具类校验token
try{
Long userid=jwttool.parseToken(token);
}catch (UnauthorizedException e){
//拦截设置响应状态码为401(未登录)
ServerHttpResponse response=exchange.getResponse();
response.setStatusCode(HttpStatus.UNAUTHORIZED);
}
//放行
return chain.filter(exchange);
}
private boolean isExclude(String path){
for(String pathPattern : authProperties.getExcludePaths()){
if(antPathMatcher.match(pathPattern,path)){
return true;
}
}
return false;
}
@Override
public int getOrder(){
return 0;
}
}
网关传递用户
前面所将的需要将信息传递给的微服务,需要将信息保存到请求头中,而在微服务中需要设置拦截器将所有用户信息保存到ThreadLocal中
如何向微服务中进行传递,需要略微修改上述所讲的过滤器:
java
@Override
public Mono<Void> filter(ServerWebExchange exchange,GatewayFilterChain chain){
//获取request
ServerHttpRequest request= exchange.getRequest();
//判断是否需要做登录校验(向注册,登录界面就不需要做登录校验)
if(isExclude(request.getPath().toString())){
//放行
return chain.filter(exchange);
}
//获取token
String token=null;
List<String> headers=request.getHeaders().get("token");
if(headers !=null && !headers.isEmply()){
token=headers.get(0);
}
//利用jwt工具类校验token
try{
Long userid=jwttool.parseToken(token);
}catch (UnauthorizedException e){
//拦截设置响应状态码为401(未登录)
ServerHttpResponse response=exchange.getResponse();
response.setStatusCode(HttpStatus.UNAUTHORIZED);
}
//保存到请求头中,传递给微服务
String userinfo=userid.toString();
ServerWebExchange ww=exchange.mutate()
.request(builder->builder.header("user-info",userinfo))
.build();
//放行
return chain.filter(ww);
}
将拦截器单独写在一个微服务当中,这样避免代码冗余:
java
public class UserInfoInterceptor implements HandlerInterceptor{
@Override
public boolean preHandle(HttpServletRequest request,HttpServletResponse response,Object handler) throws Exception{
//获取登录信息
String userInfo=request.getHeader("user-info");
//判断是否获取了用户,如果有,存入ThreadLocal
if(StrUtil.isNotBlank(userInfo)){
//存入到ThreadLocal中
}
// 放行
return true;
}
@Override
public void afterCompletion(HttpServletRequest request,HttpServletResponse response,Object handler,Exception ex) throws Exception{
}
}
创建好之后在继承了WebMvcConfigurer的配置类中添加拦截器
这时别的微服务还是用不了拦截器,因为它配置类的包没有被扫到,这时我们需要将添加拦截器的配置类放到resources
目录下的META-INF
文件中添加配置类
java
@Configuration
@ConditionalOnClass(DispatcherServlet.class)
public class MvcConfig implements WebMvcConfigurer{
@Override
public void addInterceptors(InterceptorRegistry registry){
registry.addInterceptor(new UserInfoInterceptor());
}
}
注意:如果运行的时候网关模块报错
是因为网关的底层不是SpringMVC,而是响应式编程,而WebMvcConfigurer是MVC中提供的,所以我们在配置类上方添加注解,当有MVC的核心api就是DispatcherServlet,拥有这个的微服务该配置类生效
OpenFeign传递用户
微服务项目中的很多业务要被多个微服务共同合作完成,而这个过程中也需要传递登录用户信息
openFeign中提供了一个拦截器接口,所有有Openfeign发起的请求都会先调用拦截器处理请求
将此拦截器写在当时定义的Openfeign的微服务项目中,以便日后统一调用
java
public class Myconfig{
@Bean
public RequestInterceptor userinterceptor(){
return new RequestInterceptor(){
@Override
public void apply(RequestTemplate template){
//将添加请求头信息
Long userid=Usercontext.getUser();
if(userid!=null){
template.header("user-info",userid.toString()) ;
}
}
}
}
}