思路分析
微服务02-04.网关登录校验-思路分析_哔哩哔哩_bilibili
自定义过滤器
网关过滤器有两种,分别是:
- GatewayFilter:路由过滤器,作用于任意指定的路由;默认不生效,要配置到路由后生效
- GlobalFilter:全局过滤器,作用范围是所有路由;声明后自动生效
两种过滤器的过滤方法签名完全一致
从思路分析中得知,NettyRoutingFilter是最后一个过滤器,用来做路由转发的,因此我们自定义的过滤器需要在NettyRoutingFilter之前进行过滤
GlobalFilter
java
@Component
public class MyGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 模拟登录校验逻辑
// 获取请求
ServerHttpRequest request = exchange.getRequest();
// 过滤器业务处理
HttpHeaders headers = request.getHeaders();
System.out.println("headers:"+ headers);
// 放行
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 0;
}
}
Ordered是Spring提供的一个用来排序的接口,NettyRoutingFilter也实现了这个接口,并且它的值是int类型的最大值(值越大,优先级越低),所以我们只用比这个值小就行,这里使用的值是0
GatewayFilter(一般不用)
微服务02-06.网关登录校验-自定义GatewayFilter_哔哩哔哩_bilibili
实现登录校验
java
@Component
@RequiredArgsConstructor
public class AuthGlobalFilter implements GlobalFilter, Ordered {
// 自定义配置类,里面有不用登录拦截的路径列表(路径带有通配符)
private final AuthProperties authProperties;
// 自定义工具类,jwt令牌的相关工具
private final JwtTool jwtTool;
// 匹配具有通配符的路径
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("authorization");
if (headers != null && !headers.isEmpty()){
token = headers.get(0);
}
// 校验并解析token
Long userId=null;
try {
userId = jwtTool.parseToken(token);
} catch (UnauthorizedException e) { // 自定义的未登录异常
// 拦截,设置响应状态码为401
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.UNAUTHORIZED); // 401
return response.setComplete();
}
// TODO 传递用户信息
System.out.println("用户id:"+userId);
// 放行
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里
将用户信息保存到请求头
要修改转发到微服务的请求,需要用到ServerWebExchange类提供的APl
在上述的登录校验的代码中修改后两步
java
// 传递用户信息
String userInfo = userId.toString();
ServerWebExchange swe = exchange.mutate()
.request(builder -> builder.header("user-info", userInfo)) // 请求头的名字要和别的微服务开发者约定好
.build();
// 放行
return chain.filter(swe);
将用户信息保存到ThreadLocal
①先定义一个UserContext类作为线程上下文持有者
java
public class UserContext {
private static final ThreadLocal<Long> tl = new ThreadLocal<>();
/**
* 保存当前登录用户信息到ThreadLocal
* @param userId 用户id
*/
public static void setUser(Long userId) {
tl.set(userId);
}
/**
* 获取当前登录用户信息
* @return 用户id
*/
public static Long getUser() {
return tl.get();
}
/**
* 移除当前登录用户信息
*/
public static void removeUser(){
tl.remove();
}
}
②然后编写拦截器
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)){
UserContext.setUser(Long.valueOf(userInfo));
}
// 放行
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 清理用户
UserContext.removeUser();
}
}
因为登录校验是在网关完成了的,所以这里直接放行
③配置拦截器
java
@Configuration
public class MvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new UserInfoInterceptor()); // 默认拦截所有的路径
}
}
这里我们是把这些文件都放在了common模块里,所以其他的微服务无法扫描到这个配置类,因此还有一个操作
④在common模块的resources里定义一个文件,并加上这个配置类
- 在resources/META-INF目录下定义一个名为spring.factories的文件(旧版)
- 在resources/META-INF/spring目录下定义一个名为org.springframework.boot.autoconfigure.AutoConfiguration.imports的文件(新版)
二选一,然后加上拦截器配置类(以旧版为例)
java
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.hmall.common.config.MyBatisConfig,\
com.hmall.common.config.MvcConfig,\
com.hmall.common.config.JsonConfig
其他两个用来演示换行,不用管
但是,这里网关引入了common的包,而common模块有Springmvc的依赖,但scope是provided,所以当网关项目运行时就会提示找不到WebConfigurer,所以还有一步(如果没有这种问题就跳过这一步)
⑤加注解
在配置类上加上注解@ConditionalOnClass(DispatcherServlet.class),表示必须要有springmvc的核心类才扫描这个配置类,这样网关就不会报错了
java
@Configuration
@ConditionalOnClass(DispatcherServlet.class)
public class MvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new UserInfoInterceptor()); // 默认拦截所有的路径
}
}
OpenFeign传递用户
实现微服务之间相互传递用户信息,而不是网关传递
OpenFeign中提供了一个拦截器接口,所有由OpenFeign发起的请求都会先调用拦截器处理请求
其中的RequestTemplate类中提供了一些方法可以让我们修改请求头
这个拦截器定义在使用了openfeign的通用模块中
配置类
java
public class DefaultFeignConfig {
// 日志
@Bean
public Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
// 添加请求头,采用的是匿名内部类
@Bean
public RequestInterceptor userInfoInterceptor() {
return new RequestInterceptor() {
@Override
public void apply(RequestTemplate requestTemplate) {
Long userId = UserContext.getUser();
if (userId != null) {
requestTemplate.header("user-info", userId.toString());
}
}
};
}
}
要想生效,需要在服务消费者的启动类上添加注释和对应属性
java
@EnableFeignClients(basePackages = "com.hmall.api.client", defaultConfiguration = DefaultFeignConfig.class)