gateway(二)信息传递的三种情况
1、通过过滤器在网关内获取用户信息(网关内传递)
通过下面的代码,就可以实现网关转发请求到微服务的时候携带用户信息
package com.hmall.gateway.filters;
import com.hmall.common.exception.UnauthorizedException;
import com.hmall.gateway.config.AuthProperties;
import com.hmall.gateway.utils.JwtTool;
import lombok.RequiredArgsConstructor;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.List;
@Component
@RequiredArgsConstructor
public class AuthGlobalFilter implements GlobalFilter, Ordered {
private final AuthProperties authProperties;
private final JwtTool jwtTool;
private final AntPathMatcher antPathMatcher = new AntPathMatcher();
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 1.获取用户信息
ServerHttpRequest request = exchange.getRequest();
// 2.判断是否需要做登录拦截
if(isExclude(request.getPath().toString())) return chain.filter(exchange); // 放行
// 3.获取token
String token = null;
List<String> headers = request.getHeaders().get("authorization");
if(headers!=null && !headers.isEmpty()) {
token = headers.get(0);
}
// 4.解析并校验token
Long userId = null;
try {
userId = jwtTool.parseToken(token);
} catch (UnauthorizedException e) {
// 拦截,设置响应状态码401
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}
// 5.传递用户信息
String userInfo = userId.toString();
ServerWebExchange serverWebExchange = exchange.mutate()
.request(builder -> builder.header("user-info", userInfo))
.build();
// 6.放行
return chain.filter(serverWebExchange);
}
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;
}
}
2、微服务解析网关传过来的用户信息(网关和微服务之间传递)
解析有很多种方法,一种是最原始的,也就是在controller形参处加上@RequestHeader注解来获取请求头内的用户信息,但是这样就太麻烦了,因为每个controller都要这么做。
第二种方法就是基于SpringMVC的拦截器,如果每个拦截器都写也挺麻烦的,所以最终我们把拦截器写到了common模块里,在这里面获取请求头里面的信息,并保存到ThreadLocal,这样后续的所有业务都能从ThreadLocal里获取到用户信息。(简而言之,就是定义一个通过用的拦截器,用于获取网关传递过来的用户信息)。
在把拦截器定义在common模块下我们也碰到了很多问题,就是我们把拦截器的配置放在了common模块下,这样就导致了我们其它微服务是扫描不到的,没有办法生效,在这里,我们用到了SpringBoot自动装配的原理(Spring通过扫描指定的包路径,自动发现标注了特定注解 如@Component,@Service,@Repository的类,并将其注册为Spring容器中的bean):我们将定义的配置类放到了resource/META-INF下的spring.factories文件下,这样的话就能实现自动装配。
但是这又带来另外一个问题,就是我们希望这个配置类在微服务里生效,在网关里不要生效,因此我们又用了一个条件注解@ConditionalOnClass(DispatcherServlet.class)
来去判断当前项目下有没有DispatcherServlet,网关里没有,这样就不会生效了
用来获取网关传过来的用户:
package com.hmall.common.interceptors;
import cn.hutool.core.util.StrUtil;
import com.hmall.common.utils.UserContext;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class UserInfoInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // controller之前执行
// 1.获取用户信息
String userInfo = request.getHeader("user-info");// 和网关保持一致
// 2.判断是否获取了用户,如果有,存入ThreadLocal
if(StrUtil.isNotBlank(userInfo)) {
UserContext.setUser(Long.valueOf(userInfo)); // 保存到ThreadLocal
}
// 3.放行
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { // controller完成之后
// 清理用户
UserContext.removeUser();
}
}
拦截器生效的配置类
package com.hmall.common.config;
import com.hmall.common.interceptors.UserInfoInterceptor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
@ConditionalOnClass(DispatcherServlet.class) // 判断一个类是否存在,当DispatcherServlet.class存在时,配置生效
public class MvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) { // 拦截器的注册器
registry.addInterceptor(new UserInfoInterceptor());
}
}
3、微服务之间传递
package com.hmall.api.config;
import com.hmall.common.utils.UserContext;
import feign.Logger;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.springframework.context.annotation.Bean;
public class DefaultFeignConfig { // 千万不要在这个类上面加@Configuration注解,因为这个配置类会在其它启动类上的注解引用
@Bean
public Logger.Level feignLogLevel() {
return Logger.Level.BASIC;
}
@Bean
public RequestInterceptor userInfoInterceptor() {
return new RequestInterceptor() {
@Override
public void apply(RequestTemplate requestTemplate) {
Long userId = UserContext.getUser();
// 为什么上面这行代码可以从UserContext里取用户信息,因为之前的服务中的信息是网关给的,这个时候请求头里是带了用户信息的
if(userId != null) {
requestTemplate.header("user-info", userId.toString());
}
}
};
}
}