当前位置: 首页 > article >正文

sa-token全局过滤器之写法优化(包含设置Order属性)

背景:在手写登录的过程中需要继承sa-token框架,并使用sa-token的过滤器功能,那全局过滤器怎么写呢 ?我们一起来看看吧。

根据sa-token的官方文档:
https://sa-token.cc/doc.html#/up/global-filter

如下图所示:下面是最简单的一种sa-token过滤器的实现方式,这种写法简单,但是会有一些问题。
第一种写法:
其中有关于

相较于第一种写法,配置更为全面的过滤器写法:
在这里插入图片描述

登录中的具体写法:第一种,不设置Order的写法:

@Configuration
@Slf4j
public class SaTokenConfigure implements WebMvcConfigurer {

    /**
     * 白名单
     **/
    private static String[] EXCLUDE_PATH = {"/login/**",
            "/doc.html",
            "/service-worker.js",
            "/swagger-resources",
            "/webjars/**",
            "/v3/api-docs/**",
            "/favicon.ico",
            "/ocr/**",
            "/location/**"};

    /**
     * 注册拦截器
     *
     **/
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 注册 Sa-Token 拦截器,校验规则为 StpUtil.checkLogin() 登录校验。
        registry.addInterceptor(new SaInterceptor(handle -> StpUtil.checkLogin()))
                .addPathPatterns("/**")
                //设置白名单
                .excludePathPatterns(EXCLUDE_PATH);
    }

    /**
     * 全局过滤器
     *
     * @return cn.dev33.satoken.filter.SaServletFilter
     **/
    @Bean
    public SaServletFilter getSaServletFilter() {
        return new SaServletFilter()
                // 拦截与排除 path
                .addInclude("/**").addExclude(EXCLUDE_PATH)
                // 全局认证函数
                .setAuth(obj -> {
                    // ...
                    log.info("这里是全局认证函数");
                    //这里可以写判断是否登录的逻辑,比如sa-token是有工具类StpUtil.isLogin()来判断当前请求的用户是否已经登录了,如果登录过滤器则放行,否则的话拦截,比如用户未登录的情况下,访问了一些在白名单之外的url地址,那此时就会经过这个位置,这里可以写校验逻辑,还有一些登录上下文的初始化逻辑。
                    if(StpUtil.isLogin()){
                    //Thread_local实现的上下文工具类
                    //每次先清除一下上下文内容,
                        TMSSsoContextHelper.clearSession();
                        //重新初始化这个容器,其实也就是从sa-session中取得内容放到上下文信息中,以方便后续使用。
                        initTMSSsoContextIfLogin();
                    }else {
                    //这里抛出了未登录的异常,会被下面的setError函数捕获并处理相关逻辑。
                        throw new NotLoginException("未登录","web","");
                    }

                })
                // 异常处理函数
                .setError(e -> {
                    log.info("------------->NotLogin-------------");
                    // 1. 设置响应头为 JSON 格式
                    SaHolder.getResponse().setHeader("Content-Type", "application/json;charset=UTF-8");

                    // 2. 捕获未登录异常,这样设置上,会响应到前台具体的 如“请先登录”的提示。其实也可以在全局拦截器那捕获这个未登录的异常,提示未登录请重新登陆,也可以在这里进行包装后返回提示,如果这里不写和全局捕获那都不写的话,就会返回500 内部服务错误这种不好的信息到前台,所以这些需要进行响应信息的设置包装。
                    if (e instanceof NotLoginException) {
                        SaHolder.getResponse().setStatus(401); // 401 Unauthorized
                        return SaResult.error("请先登录").setCode(401);
                    }

                    // 3. 其他异常返回 500
                    SaHolder.getResponse().setStatus(500);
                    return SaResult.error("服务器繁忙,请稍后重试").setCode(500);
                })
                // 前置函数:在每次认证函数之前执行
                .setBeforeAuth(obj -> {
                    //设置跨域响应头
                    SaHolder.getResponse()
                            .setHeader("Access-Control-Allow-Origin", "*")
                            .setHeader("Access-Control-Allow-Methods", "*")
                            .setHeader("Access-Control-Max-Age", "3600")
                            .setHeader("Access-Control-Allow-Headers", "*");
                    // 如果是预检请求,则立即返回到前端
                    SaRouter.match(SaHttpMethod.OPTIONS)
                            .free(r -> System.out.println("--------OPTIONS预检请求,不做处理"))
                            .back();
                });
    }

    /**
     * 跨域请求设置
     *
     * @param registry
     **/
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOriginPatterns("*")
                .allowedMethods("*")
                .allowCredentials(false)
                .maxAge(3600);
    }

    /**
     * 设置上下文信息
     * 其实就是通过登录之后,登录的具体逻辑里面会初始化sa-session对象,然后登录后调用其他请求的时候,这个sasession容器中肯定是有相关信息了,直接初始化放到Thread_local对象中即可让别的程序中现用现取得了。
     * @Date 
     **/
    private void initTMSSsoContextIfLogin() {
        SaSession saSession = StpUtil.getSession();
        String tokenValue = StpUtil.getTokenValue();
        Long userId = (Long) saSession.get(BaseConstant.CURRENT_USER_ID);
        String tenantIds = (String)saSession.get(BaseConstant.CURRENT_TENANT_ID);
        String phone = (String)saSession.get(BaseConstant.CURRENT_USER_PHONE);
        //供service使用  当前登录用户关联到审核的企业后:将租户放入RPC容器
        if (ObjectUtil.isNotEmpty(tenantIds)) {
            RpcContext.getContext().setAttachment(BaseConstant.RPC_CONTEXT_TENANT_ID, tenantIds);
        }

        TMSSsoContextInfo tmsSsoContextInfo = TMSSsoContextInfo.builder()
                .token(tokenValue)
                .userId(userId+"")
                .tenantId(tenantIds)
                .phone(phone)
                .build();
        TMSSsoContextHelper.setTMSSsoContextInfo(tmsSsoContextInfo);

    }
}

第二种,设置Order的写法:

这种是先创建了FilterRegistrationBean对象,同时给这个对象set值的时候,new 了一个SaServletFilter对象,并同时设置上了相关属性逻辑。

@Bean
public FilterRegistrationBean<SaServletFilter> saFilterRegistration() {
    FilterRegistrationBean<SaServletFilter> frBean = new FilterRegistrationBean<>();
    frBean.setFilter(
        new SaServletFilter()
            .addInclude("/**")
            .addExclude("/favicon.ico")
            .setAuth(obj -> {
                SaRouter.match("/**", "/user/doLogin", () -> StpUtil.checkLogin());
            })
            .setError(e -> SaResult.error(e.getMessage()))
    );
    frBean.setOrder(-101); // 确保在 Spring Security 过滤器之前执行
    frBean.addUrlPatterns("/**"); // 显式指定 URL 模式(避免覆盖问题)
    return frBean;
}

或者
可以先创建一个SaServletFilter对象,并设置上相关的属性逻辑后,然后再去创建FilterRegistrationBean对象,然后再给这个对象设置Filter属性及Order等属性。具体如下:

@Bean
public FilterRegistrationBean<SaServletFilter> saFilterRegistration() {
    // 1. 创建并配置 SaServletFilter
    SaServletFilter saServletFilter = new SaServletFilter()
        .addInclude("/**")
        .addExclude(EXCLUDE_PATH)
        .setAuth(obj -> { /* 认证逻辑 */ })
        .setError(e -> { /* 异常处理 */ })
        .setBeforeAuth(r -> { /* 跨域配置 */ });

    // 2. 包装为 FilterRegistrationBean
    FilterRegistrationBean<SaServletFilter> frBean = new FilterRegistrationBean<>();
    frBean.setFilter(saServletFilter);
    frBean.setOrder(-99); 
    frBean.addUrlPatterns("/**"); // 使用 /** 匹配所有路径

    return frBean;
}

将通过第一种写法的全局拦截器代码进行优化(设置上相关执行顺序了)后如下:
改写思路:

首先,我需要回忆Spring
Boot中如何注册过滤器。通常,通过FilterRegistrationBean可以注册Servlet过滤器,并设置其顺序、URL模式等属性。而直接返回SaServletFilter作为Bean,Spring
Boot会自动将其注册为过滤器,但可能无法灵活设置优先级。

接下来,我需要将现有的getSaServletFilter()方法转换为创建FilterRegistrationBean实例。原方法中,用户配置了拦截路径、排除路径、认证函数、异常处理函数以及前置函数。这些配置需要保留,但需要包装到FilterRegistrationBean中。

步骤可能如下:

  1. 创建一个FilterRegistrationBean实例。

  2. 将原有的SaServletFilter配置移动到该实例中。

  3. 设置过滤器的顺序为-99。

  4. 确保URL模式的正确设置,可能需要显式指定addUrlPatterns,尽管原代码中已经通过addInclude设置了路径,但为了保险起见,可能在FilterRegistrationBean中再次指定。

需要注意的地方包括:

  • 确保SaServletFilter的所有配置(如addInclude, addExclude, setAuth, setError, setBeforeAuth)都被正确转移到新的FilterRegistrationBean中。

  • 检查是否有任何依赖于Spring Boot自动配置的部分,可能需要显式设置。

  • 确认优先级设置是否正确,-99的值是否合适,这取决于系统中其他过滤器的优先级设置,比如Spring Security通常使用-100,所以-99会让Sa-Token过滤器在其之后执行,但用户可能需要根据实际需求调整。

此外,用户提到要设置优先级为-99,我需要确保在FilterRegistrationBean中通过setOrder方法设置。例如:frBean.setOrder(-99);
在代码转换过程中,还需要测试以确保修改后的过滤器仍然按预期工作,包括拦截正确的路径、处理异常、设置响应头等。特别是跨域设置和认证逻辑是否在正确的位置执行。

最后,需要检查是否有其他配置可能影响过滤器的行为,比如WebMvcConfigurer中的其他设置,确保没有冲突。

以下是修改后的代码,将 SaServletFilter 的注册方式改为通过 FilterRegistrationBean,并设置优先级为 -99:

@Configuration
@Slf4j
public class SaTokenConfigure implements WebMvcConfigurer {

    // ... 其他代码保持不变 ...

    /**
     * 注册全局过滤器,并设置优先级为 -99
     */
    @Bean
    public FilterRegistrationBean<SaServletFilter> saFilterRegistration() {
        // 1. 创建 SaServletFilter 实例并配置
        SaServletFilter saServletFilter = new SaServletFilter()
                .addInclude("/**")
                .addExclude(EXCLUDE_PATH)
                .setAuth(obj -> {
                    log.info("这里是全局认证函数");
                    if(SsoManager.isLogin()){
                        TMSSsoContextHelper.clearSession();
                        initTMSSsoContextIfLogin();
                    } else {
                        throw new NotLoginException("未登录", "web", "");
                    }
                })
                .setError(e -> {
                    log.info("------------->NotLogin-------------");
                    SaHolder.getResponse().setHeader("Content-Type", "application/json;charset=UTF-8");
                    if (e instanceof NotLoginException) {
                        SaHolder.getResponse().setStatus(401);
                        return SaResult.error("请先登录").setCode(401);
                    }
                    SaHolder.getResponse().setStatus(500);
                    return SaResult.error("服务器繁忙,请稍后重试").setCode(500);
                })
                .setBeforeAuth(obj -> {
                    SaHolder.getResponse()
                            .setHeader("Access-Control-Allow-Origin", "*")
                            .setHeader("Access-Control-Allow-Methods", "*")
                            .setHeader("Access-Control-Max-Age", "3600")
                            .setHeader("Access-Control-Allow-Headers", "*");
                    SaRouter.match(SaHttpMethod.OPTIONS)
                            .free(r -> System.out.println("--------OPTIONS预检请求,不做处理"))
                            .back();
                });

        // 2. 包装成 FilterRegistrationBean 并设置优先级
        FilterRegistrationBean<SaServletFilter> registrationBean = new FilterRegistrationBean<>();
        registrationBean.setFilter(saServletFilter);
        registrationBean.setOrder(-99); // 设置优先级为-99
        registrationBean.addUrlPatterns("/*"); // 显式指定拦截路径(覆盖 addInclude 的配置)

        return registrationBean;
    }

    // ... 其他方法保持不变 ...
}

关键改动说明

  1. 返回类型修改

    // 原返回类型
    @Bean
    public SaServletFilter getSaServletFilter() { ... }
    
    // 新返回类型
    @Bean
    public FilterRegistrationBean<SaServletFilter> saFilterRegistration() { ... }
    
  2. 显式设置优先级

    registrationBean.setOrder(-99);  // 确保优先级高于 Spring Security(默认-100)
    
  3. URL 模式显式声明

    registrationBean.addUrlPatterns("/*"); // 明确拦截所有路径
    
    • 虽然 SaServletFilter 内部已通过 .addInclude("/**") 声明,但 FilterRegistrationBean 会覆盖该配置,建议保持一致
  4. 保持原有配置

    • 认证逻辑(.setAuth
    • 异常处理(.setError
    • 跨域配置(.setBeforeAuth
    • 排除路径(.addExclude(EXCLUDE_PATH)

注意事项

  1. 优先级与 Spring Security 的关系

    • Spring Security 的过滤器链默认优先级为 -100
    • 设置 -99 表示您的过滤器会在 Spring Security 之后执行
    • 如果需要先于 Spring Security 执行鉴权,需设置为比 -100 更小的值(如 -101
  2. URL 匹配模式

    • SaServletFilter.addInclude("/**") 匹配所有路径(包括子路径)
    • FilterRegistrationBean.addUrlPatterns("/*") 匹配所有路径(但不会匹配子路径)
    • **建议统一使用 /*** 以保证拦截范围一致
  3. 排除路径处理

    • addExclude(EXCLUDE_PATH) 已通过 SaServletFilter 配置
    • 需确保 EXCLUDE_PATH 中的路径不会被 Spring Security 或其他过滤器拦截
  4. 跨域配置冲突

    • 如果项目中同时存在其他跨域配置(如 @CrossOriginCorsFilter),需确保配置不冲突
    • 建议统一在 一处 配置跨域逻辑(推荐在 setBeforeAuth 中配置)

完整配置建议

@Bean
public FilterRegistrationBean<SaServletFilter> saFilterRegistration() {
    // 1. 创建并配置 SaServletFilter
    SaServletFilter saServletFilter = new SaServletFilter()
        .addInclude("/**")
        .addExclude(EXCLUDE_PATH)
        .setAuth(obj -> { /* 认证逻辑 */ })
        .setError(e -> { /* 异常处理 */ })
        .setBeforeAuth(r -> { /* 跨域配置 */ });

    // 2. 包装为 FilterRegistrationBean
    FilterRegistrationBean<SaServletFilter> frBean = new FilterRegistrationBean<>();
    frBean.setFilter(saServletFilter);
    frBean.setOrder(-99); 
    frBean.addUrlPatterns("/**"); // 使用 /** 匹配所有路径

    return frBean;
}

梳理完毕,两种实现方式都练习🆗啦,大家选一个使用就行。
但是具体两个写法都i有什么优略呢?我们可以看下篇文章: 两种写法的区别 敬请期待~


http://www.kler.cn/a/571770.html

相关文章:

  • HiRT:利用分层机器人Transformer 增强机器人控制
  • 企业级Python后端数据库使用指南(简略版)
  • 计算机视觉算法实战——医学影像分割(主页有源码)
  • 深入解析Tiktokenizer:大语言模型中核心分词技术的原理与架构
  • Spring线程池学习笔记
  • [LeetCode]day33 150.逆波兰式求表达值 + 239.滑动窗口最大值
  • STM32MP1xx的启动流程
  • 【数据结构与算法】常见数据结构与算法在JDK和Spring中的实现:源码解析与实战代码!
  • Arm64架构的Linux服务器安装jdk8
  • 珈和科技亮相CCTV-13《新闻直播间》,AI多模态农业大模型引领智慧农业新变革
  • 【蓝桥杯集训·每日一题2025】 AcWing 5526. 平衡细菌 python
  • 最新Flutter导航拦截PopScope使用
  • 国家网络安全通报中心:大模型工具Ollama存在安全风险
  • Ubuntu的tmux配置
  • Delphi连接MySql数据库房
  • 高效玩转 PDF:实用的分割、合并操作详解
  • 迷你世界脚本道具接口:Item
  • 记一次(0xC0000005)内存访问冲突( Tkinter 嵌入 PyQt5 的 QWebEngineView)
  • AI多维度创新探索:ChatGPT大模型提示词使用技巧
  • 【中国首个AI原生IDE:字节跳动发布AI编程工具Trae,开启智能编程新时代】