网关登录校验

思路分析

微服务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)
相关推荐
invicinble31 分钟前
对linux形成认识
linux·运维·服务器
小Pawn爷37 分钟前
14.VMmare安装ubuntu
linux·运维·ubuntu
技术路上的探险家43 分钟前
8 卡 V100 服务器:基于 vLLM 的 Qwen 大模型高效部署实战
运维·服务器·语言模型
郝学胜-神的一滴1 小时前
深入解析Python字典的继承关系:从abc模块看设计之美
网络·数据结构·python·程序人生
有谁看见我的剑了?1 小时前
介绍一款 测试 DNS解析成功率的网站
运维
半桔1 小时前
【IO多路转接】高并发服务器实战:Reactor 框架与 Epoll 机制的封装与设计逻辑
linux·运维·服务器·c++·io
绵绵细雨中的乡音1 小时前
深入理解 ET 与 LT 模式及其在 Reactor 模型中的应用
服务器·网络·php
HABuo2 小时前
【linux文件系统】磁盘结构&文件系统详谈
linux·运维·服务器·c语言·c++·ubuntu·centos
Howrun7772 小时前
关于Linux服务器的协作问题
linux·运维·服务器
暖馒2 小时前
Modbus应用层协议的深度剖析
网络·网络协议·c#·wpf·智能硬件