网关登录校验

思路分析

微服务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)
相关推荐
biubiubiu070631 分钟前
Linux / Ubuntu systemd 服务使用说明
linux·运维·ubuntu
MaximusCoder1 小时前
等保测评命令——Anolis Linux
linux·运维·服务器·网络·经验分享·安全·php
田里的水稻1 小时前
ubuntu22.04_构建openclaw开发框架
运维·人工智能·python
线束线缆组件品替网1 小时前
Adam Tech NPC-6-007-BU网线组件详解
服务器·网络·数码相机·智能路由器·电脑·51单片机·电视盒子
Striver-Diligent2 小时前
您的解决方案准确吗?一种用于增强通信网络可靠性的、面向故障的性能预测方法
网络·深度学习·机器学习·网络性能估计·数字孪生网络·网络预测
相思难忘成疾2 小时前
《RHEL9虚拟机部署及SSH远程登录实践手册》
linux·运维·ssh·虚拟机
cg_ssh2 小时前
Vue3中样式变量的使用
linux·运维·服务器
cheems95272 小时前
[网络原理]http协议理论基础以及wireshark抓包分析(二)
网络·http·wireshark
双星系统2 小时前
ABB机器人DSQC 679示教器电缆选型与故障排查(附原装型号对照表)
网络·数据库·机器人·工业4.0·工业机器人
炸炸鱼.2 小时前
Nginx 代理与缓存实战:正向、反向及网络层级详解
网络·nginx·缓存