VYPR
Critical severityNVD Advisory· Published Oct 25, 2023· Updated Sep 12, 2024

CVE-2023-44794

CVE-2023-44794

Description

Sa-Token <=1.36.0, when used with Spring Boot >=2.3.1 or Spring >=5.3.0, allows privilege escalation via crafted URLs due to differing URI pattern matching.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Sa-Token <=1.36.0, when used with Spring Boot >=2.3.1 or Spring >=5.3.0, allows privilege escalation via crafted URLs due to differing URI pattern matching.

Root

Cause Sa-Token versions 1.36.0 and earlier, when integrated with Spring Boot 2.3.1+ or Spring 5.3.0+, are vulnerable to authentication bypass due to differences in URI pattern matching. Sa-Token uses AntPathMatcher for route interception, while Spring Boot's default matching strategy in those versions is PathPatternParser. This mismatch allows an attacker to craft a URL that Sa-Token interprets as a non-protected path and Spring interprets as a protected path, bypassing access controls [2].

Exploitation

An attacker can send a specially crafted HTTP request containing path traversal or encoding tricks (e.g., using URL-encoded characters or double slashes) to access protected endpoints like /admin/** without the required permissions. No authentication or special network position is required; the attack is remote and simple to execute [2].

Impact

Successful exploitation enables an unprivileged user to escalate privileges and access admin-level functionality, potentially leading to full system compromise depending on the application's configuration. The vulnerability does not require any prior authentication [2].

Mitigation

The issue is fixed in Sa-Token version 1.37.0. Users unable to upgrade can set the Spring Boot configuration spring.mvc.pathmatch.matching-strategy=ant_path_matcher to align URI matching behavior and close the bypass vector [2][4].

AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
cn.dev33:sa-token-coreMaven
< 1.37.01.37.0

Affected products

2

Patches

1
954efeb73277

修复路由拦截鉴权可被绕过的问题 fix #515

https://github.com/dromara/Sa-Tokenclick33Oct 16, 2023via ghsa
33 files changed · +688 79
  • sa-token-core/src/main/java/cn/dev33/satoken/application/ApplicationInfo.java+45 0 added
    @@ -0,0 +1,45 @@
    +/*
    + * Copyright 2020-2099 sa-token.cc
    + *
    + * Licensed under the Apache License, Version 2.0 (the "License");
    + * you may not use this file except in compliance with the License.
    + * You may obtain a copy of the License at
    + *
    + *      http://www.apache.org/licenses/LICENSE-2.0
    + *
    + * Unless required by applicable law or agreed to in writing, software
    + * distributed under the License is distributed on an "AS IS" BASIS,
    + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    + * See the License for the specific language governing permissions and
    + * limitations under the License.
    + */
    +package cn.dev33.satoken.application;
    +
    +import cn.dev33.satoken.util.SaFoxUtil;
    +
    +/**
    + * 应用全局信息
    + *
    + * @author click33
    + * @since 1.31.0
    + */
    +public class ApplicationInfo {
    +
    +    /**
    +     * 应用前缀
    +     */
    +    public static String routePrefix;
    +
    +    /**
    +     * 为指定 path 裁剪掉 routePrefix 前缀
    +     * @param path 指定 path
    +     * @return /
    +     */
    +    public static String cutPathPrefix(String path) {
    +        if(! SaFoxUtil.isEmpty(routePrefix) && ! routePrefix.equals("/") && path.startsWith(routePrefix)){
    +            path = path.substring(routePrefix.length());
    +        }
    +        return path;
    +    }
    +
    +}
    \ No newline at end of file
    
  • sa-token-core/src/main/java/cn/dev33/satoken/exception/RequestPathInvalidException.java+46 0 added
    @@ -0,0 +1,46 @@
    +/*
    + * Copyright 2020-2099 sa-token.cc
    + *
    + * Licensed under the Apache License, Version 2.0 (the "License");
    + * you may not use this file except in compliance with the License.
    + * You may obtain a copy of the License at
    + *
    + *      http://www.apache.org/licenses/LICENSE-2.0
    + *
    + * Unless required by applicable law or agreed to in writing, software
    + * distributed under the License is distributed on an "AS IS" BASIS,
    + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    + * See the License for the specific language governing permissions and
    + * limitations under the License.
    + */
    +package cn.dev33.satoken.exception;
    +
    +/**
    + * 一个异常:代表请求 path 无效或非法
    + * 
    + * @author click33
    + * @since 1.37.0
    + */
    +public class RequestPathInvalidException extends SaTokenException {
    +
    +	/**
    +	 * 序列化版本号
    +	 */
    +	private static final long serialVersionUID = 8243974276159004739L;
    +
    +	/** 具体无效的 path */
    +	private final String path;
    +
    +	/**
    +	 * @return 具体无效的 path
    +	 */
    +	public String getPath() {
    +		return path;
    +	}
    +
    +	public RequestPathInvalidException(String message, String path) {
    +		super(message);
    +		this.path = path;
    +	}
    +
    +}
    
  • sa-token-core/src/main/java/cn/dev33/satoken/fun/strategy/SaCheckRequestPathFunction.java+39 0 added
    @@ -0,0 +1,39 @@
    +/*
    + * Copyright 2020-2099 sa-token.cc
    + *
    + * Licensed under the Apache License, Version 2.0 (the "License");
    + * you may not use this file except in compliance with the License.
    + * You may obtain a copy of the License at
    + *
    + *      http://www.apache.org/licenses/LICENSE-2.0
    + *
    + * Unless required by applicable law or agreed to in writing, software
    + * distributed under the License is distributed on an "AS IS" BASIS,
    + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    + * See the License for the specific language governing permissions and
    + * limitations under the License.
    + */
    +package cn.dev33.satoken.fun.strategy;
    +
    +import cn.dev33.satoken.exception.RequestPathInvalidException;
    +
    +/**
    + * 函数式接口:校验请求 path 的算法
    + *
    + * <p>  如果属于无效请求 path,则抛出异常 RequestPathInvalidException  </p>
    + *
    + * @author click33
    + * @since 1.37.0
    + */
    +@FunctionalInterface
    +public interface SaCheckRequestPathFunction {
    +
    +    /**
    +     * 执行函数
    +     * @param path 请求 path
    +     * @param extArg1 扩展参数1
    +     * @param extArg2 扩展参数2
    +     */
    +    void run(String path, Object extArg1, Object extArg2);
    +
    +}
    \ No newline at end of file
    
  • sa-token-core/src/main/java/cn/dev33/satoken/fun/strategy/SaRequestPathInvalidHandleFunction.java+37 0 added
    @@ -0,0 +1,37 @@
    +/*
    + * Copyright 2020-2099 sa-token.cc
    + *
    + * Licensed under the Apache License, Version 2.0 (the "License");
    + * you may not use this file except in compliance with the License.
    + * You may obtain a copy of the License at
    + *
    + *      http://www.apache.org/licenses/LICENSE-2.0
    + *
    + * Unless required by applicable law or agreed to in writing, software
    + * distributed under the License is distributed on an "AS IS" BASIS,
    + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    + * See the License for the specific language governing permissions and
    + * limitations under the License.
    + */
    +package cn.dev33.satoken.fun.strategy;
    +
    +import cn.dev33.satoken.exception.RequestPathInvalidException;
    +
    +/**
    + * 函数式接口:当请求 path 校验不通过时处理方案的算法
    + *
    + * @author click33
    + * @since 1.37.0
    + */
    +@FunctionalInterface
    +public interface SaRequestPathInvalidHandleFunction {
    +
    +    /**
    +     * 执行函数
    +     * @param e 请求 path 无效的异常对象
    +     * @param extArg1 扩展参数1
    +     * @param extArg2 扩展参数2
    +     */
    +    void run(RequestPathInvalidException e, Object extArg1, Object extArg2);
    +
    +}
    \ No newline at end of file
    
  • sa-token-core/src/main/java/cn/dev33/satoken/strategy/SaStrategy.java+44 0 modified
    @@ -18,6 +18,7 @@
     import cn.dev33.satoken.SaManager;
     import cn.dev33.satoken.annotation.*;
     import cn.dev33.satoken.basic.SaBasicUtil;
    +import cn.dev33.satoken.exception.RequestPathInvalidException;
     import cn.dev33.satoken.exception.SaTokenException;
     import cn.dev33.satoken.fun.strategy.*;
     import cn.dev33.satoken.session.SaSession;
    @@ -329,6 +330,49 @@ private SaStrategy() {
     		return new StpLogic(loginType);
     	};
     
    +	/**
    +	 * 请求 path 不允许出现的字符
    +	 */
    +	public static String[] INVALID_CHARACTER = {
    +			"//", "\\",
    +			"%2e", "%2E",	// .
    +			"%2f", "%2F",	// /
    +			"%5c", "%5C",	// \
    +			"%25"	// 空格
    +	};
    +
    +	/**
    +	 * 校验请求 path 的算法
    +	 */
    +	public SaCheckRequestPathFunction checkRequestPath = (requestPath, extArg1, extArg2) -> {
    +
    +		// 不允许为null
    +		if(requestPath == null) {
    +			throw new RequestPathInvalidException("非法请求:null", null);
    +		}
    +		// 不允许包含非法字符
    +		for (String item : INVALID_CHARACTER) {
    +			if (requestPath.contains(item)) {
    +				throw new RequestPathInvalidException("非法请求:" + requestPath, requestPath);
    +			}
    +		}
    +		// 不允许出现跨目录
    +		if(requestPath.contains("/.") || requestPath.contains("\\.")) {
    +			throw new RequestPathInvalidException("非法请求:" + requestPath, requestPath);
    +		}
    +	};
    +
    +
    +	/**
    +	 * 当请求 path 校验不通过时处理方案的算法,自定义示例:
    +	 * <pre>
    +	 * 		SaStrategy.instance.requestPathInvalidHandle = (e, extArg1, extArg2) -> {
    +	 * 			// 自定义处理逻辑 ...
    +	 *      };
    +	 * </pre>
    +	 */
    +	public SaRequestPathInvalidHandleFunction requestPathInvalidHandle = null;
    +
     
     	// ----------------------- 重写策略 set连缀风格
     
    
  • sa-token-core/src/main/java/cn/dev33/satoken/util/SaTokenConsts.java+22 0 modified
    @@ -186,6 +186,28 @@ private SaTokenConsts() {
     	 */
     	public static final int ASSEMBLY_ORDER = -100;
     
    +	/**
    +	 * 请求 path 校验过滤器的注册顺序
    +	 */
    +	public static final int PATH_CHECK_FILTER_ORDER = -1000;
    +
    +	/**
    +	 * Content-Type  key
    +	 */
    +	public static final String CONTENT_TYPE_KEY = "Content-Type";
    +
    +	/**
    +	 * Content-Type  text/plain; charset=utf-8
    +	 */
    +	public static final String CONTENT_TYPE_TEXT_PLAIN = "text/plain; charset=utf-8";
    +
    +	/**
    +	 * Content-Type  application/json;charset=UTF-8
    +	 */
    +	public static final String CONTENT_TYPE_APPLICATION_JSON = "application/json;charset=UTF-8";
    +
    +
    +
     	
     	// =================== 废弃 ===================  
     
    
  • sa-token-demo/sa-token-demo-springboot3-redis/src/main/java/com/pj/test/TestController.java+12 2 modified
    @@ -1,10 +1,11 @@
     package com.pj.test;
     
    +import cn.dev33.satoken.context.SaHolder;
    +import cn.dev33.satoken.spring.SpringMVCUtil;
    +import cn.dev33.satoken.util.SaResult;
     import org.springframework.web.bind.annotation.RequestMapping;
     import org.springframework.web.bind.annotation.RestController;
     
    -import cn.dev33.satoken.util.SaResult;
    -
     /**
      * 测试专用Controller 
      * @author click33
    @@ -27,4 +28,13 @@ public SaResult test2() {
     		return SaResult.ok();
     	}
     
    +	// 测试   浏览器访问: http://localhost:8081/test/getRequestPath
    +	@RequestMapping("getRequestPath")
    +	public SaResult getRequestPath() {
    +		System.out.println("-------------- 测试请求 path 获取");
    +		System.out.println("request.getRequestURI() " + SpringMVCUtil.getRequest().getRequestURI());
    +		System.out.println("saRequest.getRequestPath() " + SaHolder.getRequest().getRequestPath());
    +		return SaResult.ok();
    +	}
    +
     }
    
  • sa-token-demo/sa-token-demo-springboot3-redis/src/main/resources/application.yml+1 1 modified
    @@ -19,7 +19,7 @@ sa-token:
         # 是否输出操作日志 
         is-log: true
     
    -spring: 
    +spring:
         data: 
             # redis配置 
             redis:
    
  • sa-token-demo/sa-token-demo-test/pom.xml+1 0 modified
    @@ -11,6 +11,7 @@
     		<groupId>org.springframework.boot</groupId>
     		<artifactId>spring-boot-starter-parent</artifactId>
     		<version>2.5.14</version>
    +		<!--<version>2.3.0.RELEASE</version>-->
     		<!-- <version>1.5.9.RELEASE</version> -->
     		<relativePath/>
     	</parent>
    
  • sa-token-demo/sa-token-demo-test/src/main/java/com/pj/current/NotFoundHandle.java+1 1 modified
    @@ -23,5 +23,5 @@ public Object error(HttpServletRequest request, HttpServletResponse response) th
     		response.setStatus(200);
             return SaResult.get(404, "not found", null);
         }
    -	
    +
     }
    
  • sa-token-demo/sa-token-demo-test/src/main/java/com/pj/test/TestController.java+11 0 modified
    @@ -1,5 +1,7 @@
     package com.pj.test;
     
    +import cn.dev33.satoken.context.SaHolder;
    +import cn.dev33.satoken.spring.SpringMVCUtil;
     import cn.dev33.satoken.stp.SaLoginConfig;
     import cn.dev33.satoken.stp.StpUtil;
     import cn.dev33.satoken.util.SaFoxUtil;
    @@ -41,4 +43,13 @@ public SaResult test2() {
     		return SaResult.ok();
     	}
     
    +	// 测试   浏览器访问: http://localhost:8081/test/getRequestPath
    +	@RequestMapping("getRequestPath")
    +	public SaResult getRequestPath() {
    +		System.out.println("------------ 测试访问路径获取 ");
    +		System.out.println("SpringMVCUtil.getRequest().getRequestURI()  " + SpringMVCUtil.getRequest().getRequestURI());
    +		System.out.println("SaHolder.getRequest().getRequestPath()  " + SaHolder.getRequest().getRequestPath());
    +		return SaResult.ok();
    +	}
    +
     }
    
  • sa-token-demo/sa-token-demo-test/src/main/resources/application.yml+1 1 modified
    @@ -19,7 +19,7 @@ sa-token:
         # 是否输出操作日志 
         is-log: true
     
    -spring: 
    +spring:
         # redis配置 
         redis:
             # Redis数据库索引(默认为0)
    
  • sa-token-demo/sa-token-demo-webflux-springboot3/src/main/java/com/pj/SaTokenWebfluxSpringboot3Application.java+1 2 modified
    @@ -1,10 +1,9 @@
     package com.pj;
     
    +import cn.dev33.satoken.SaManager;
     import org.springframework.boot.SpringApplication;
     import org.springframework.boot.autoconfigure.SpringBootApplication;
     
    -import cn.dev33.satoken.SaManager;
    -
     /**
      * Sa-Token整合webflux 示例 (springboot3)
      * 
    
  • sa-token-demo/sa-token-demo-webflux-springboot3/src/main/resources/application.yml+1 1 modified
    @@ -19,7 +19,7 @@ sa-token:
         # 是否输出操作日志 
         is-log: true
             
    -spring: 
    +spring:
         # redis配置 
         redis:
             # Redis数据库索引(默认为0)
    
  • sa-token-demo/sa-token-demo-webflux/src/main/resources/application.yml+1 1 modified
    @@ -19,7 +19,7 @@ sa-token:
         # 是否输出操作日志 
         is-log: true
             
    -spring: 
    +spring:
         # redis配置 
         redis:
             # Redis数据库索引(默认为0)
    
  • sa-token-starter/sa-token-jakarta-servlet/src/main/java/cn/dev33/satoken/servlet/model/SaRequestForServlet.java+2 1 modified
    @@ -16,6 +16,7 @@
     package cn.dev33.satoken.servlet.model;
     
     import cn.dev33.satoken.SaManager;
    +import cn.dev33.satoken.application.ApplicationInfo;
     import cn.dev33.satoken.context.model.SaRequest;
     import cn.dev33.satoken.exception.SaTokenException;
     import cn.dev33.satoken.servlet.error.SaServletErrorCode;
    @@ -124,7 +125,7 @@ public String getCookieValue(String name) {
     	 */
     	@Override
     	public String getRequestPath() {
    -		return request.getServletPath();
    +		return ApplicationInfo.cutPathPrefix(request.getRequestURI());
     	}
     
     	/**
    
  • sa-token-starter/sa-token-reactor-spring-boot3-starter/src/main/java/cn/dev33/satoken/reactor/filter/SaPathCheckFilterForReactor.java+56 0 added
    @@ -0,0 +1,56 @@
    +/*
    + * Copyright 2020-2099 sa-token.cc
    + *
    + * Licensed under the Apache License, Version 2.0 (the "License");
    + * you may not use this file except in compliance with the License.
    + * You may obtain a copy of the License at
    + *
    + *      http://www.apache.org/licenses/LICENSE-2.0
    + *
    + * Unless required by applicable law or agreed to in writing, software
    + * distributed under the License is distributed on an "AS IS" BASIS,
    + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    + * See the License for the specific language governing permissions and
    + * limitations under the License.
    + */
    +package cn.dev33.satoken.reactor.filter;
    +
    +import cn.dev33.satoken.exception.RequestPathInvalidException;
    +import cn.dev33.satoken.strategy.SaStrategy;
    +import cn.dev33.satoken.util.SaTokenConsts;
    +import org.springframework.core.annotation.Order;
    +import org.springframework.web.server.ServerWebExchange;
    +import org.springframework.web.server.WebFilter;
    +import org.springframework.web.server.WebFilterChain;
    +import reactor.core.publisher.Mono;
    +
    +/**
    + * 校验请求 path 是否合法
    + *
    + * @author click33
    + * @since 1.37.0
    + */
    +@Order(SaTokenConsts.PATH_CHECK_FILTER_ORDER)
    +public class SaPathCheckFilterForReactor implements WebFilter {
    +
    +	@Override
    +	public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
    +
    +		// 校验本次请求 path 是否合法
    +		try {
    +			SaStrategy.instance.checkRequestPath.run(exchange.getRequest().getPath().toString(), exchange, null);
    +		} catch (RequestPathInvalidException e) {
    +			if(SaStrategy.instance.requestPathInvalidHandle == null) {
    +				exchange.getResponse().getHeaders().set(SaTokenConsts.CONTENT_TYPE_KEY, SaTokenConsts.CONTENT_TYPE_TEXT_PLAIN);
    +				return exchange.getResponse().writeWith(Mono.just(exchange.getResponse().bufferFactory().wrap(e.getMessage().getBytes())));
    +			} else {
    +				SaStrategy.instance.requestPathInvalidHandle.run(e, exchange, null);
    +			}
    +			return Mono.empty();
    +		}
    +
    +		// 向下执行
    +		return chain.filter(exchange);
    +	}
    +
    +}
    
  • sa-token-starter/sa-token-reactor-spring-boot3-starter/src/main/java/cn/dev33/satoken/reactor/filter/SaReactorFilter.java+11 12 modified
    @@ -15,28 +15,27 @@
      */
     package cn.dev33.satoken.reactor.filter;
     
    -import java.util.ArrayList;
    -import java.util.Arrays;
    -import java.util.List;
    -
    -import cn.dev33.satoken.filter.SaFilter;
    -import org.springframework.core.annotation.Order;
    -import org.springframework.web.server.ServerWebExchange;
    -import org.springframework.web.server.WebFilter;
    -import org.springframework.web.server.WebFilterChain;
    -
     import cn.dev33.satoken.exception.BackResultException;
     import cn.dev33.satoken.exception.SaTokenException;
     import cn.dev33.satoken.exception.StopMatchException;
    +import cn.dev33.satoken.filter.SaFilter;
     import cn.dev33.satoken.filter.SaFilterAuthStrategy;
     import cn.dev33.satoken.filter.SaFilterErrorStrategy;
     import cn.dev33.satoken.reactor.context.SaReactorHolder;
     import cn.dev33.satoken.reactor.context.SaReactorSyncHolder;
     import cn.dev33.satoken.reactor.error.SaReactorSpringBootErrorCode;
     import cn.dev33.satoken.router.SaRouter;
     import cn.dev33.satoken.util.SaTokenConsts;
    +import org.springframework.core.annotation.Order;
    +import org.springframework.web.server.ServerWebExchange;
    +import org.springframework.web.server.WebFilter;
    +import org.springframework.web.server.WebFilterChain;
     import reactor.core.publisher.Mono;
     
    +import java.util.ArrayList;
    +import java.util.Arrays;
    +import java.util.List;
    +
     /**
      * Reactor 全局鉴权过滤器
      * <p>
    @@ -154,8 +153,8 @@ public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
     			// 2. 写入输出流
     			// 		请注意此处默认 Content-Type 为 text/plain,如果需要返回 JSON 信息,需要在 return 前自行设置 Content-Type 为 application/json
     			// 		例如:SaHolder.getResponse().setHeader("Content-Type", "application/json;charset=UTF-8");
    -			if(exchange.getResponse().getHeaders().getFirst("Content-Type") == null) {
    -				exchange.getResponse().getHeaders().set("Content-Type", "text/plain; charset=utf-8");
    +			if(exchange.getResponse().getHeaders().getFirst(SaTokenConsts.CONTENT_TYPE_KEY) == null) {
    +				exchange.getResponse().getHeaders().set(SaTokenConsts.CONTENT_TYPE_KEY, SaTokenConsts.CONTENT_TYPE_TEXT_PLAIN);
     			}
     			return exchange.getResponse().writeWith(Mono.just(exchange.getResponse().bufferFactory().wrap(result.getBytes())));
     			
    
  • sa-token-starter/sa-token-reactor-spring-boot3-starter/src/main/java/cn/dev33/satoken/reactor/model/SaRequestForReactor.java+6 6 modified
    @@ -16,16 +16,16 @@
     package cn.dev33.satoken.reactor.model;
     
     
    -import org.springframework.http.HttpCookie;
    -import org.springframework.http.server.reactive.ServerHttpRequest;
    -import org.springframework.web.server.ServerWebExchange;
    -import org.springframework.web.server.WebFilterChain;
    -
     import cn.dev33.satoken.SaManager;
    +import cn.dev33.satoken.application.ApplicationInfo;
     import cn.dev33.satoken.context.model.SaRequest;
     import cn.dev33.satoken.reactor.context.SaReactorHolder;
     import cn.dev33.satoken.reactor.context.SaReactorSyncHolder;
     import cn.dev33.satoken.util.SaFoxUtil;
    +import org.springframework.http.HttpCookie;
    +import org.springframework.http.server.reactive.ServerHttpRequest;
    +import org.springframework.web.server.ServerWebExchange;
    +import org.springframework.web.server.WebFilterChain;
     
     import java.util.ArrayList;
     import java.util.List;
    @@ -113,7 +113,7 @@ public String getCookieValue(String name) {
     	 */
     	@Override
     	public String getRequestPath() {
    -		return request.getURI().getPath();
    +		return ApplicationInfo.cutPathPrefix(request.getPath().toString());
     	}
     
     	/**
    
  • sa-token-starter/sa-token-reactor-spring-boot3-starter/src/main/java/cn/dev33/satoken/reactor/spring/SaTokenContextRegister.java+12 2 modified
    @@ -15,9 +15,9 @@
      */
     package cn.dev33.satoken.reactor.spring;
     
    -import org.springframework.context.annotation.Bean;
    -
     import cn.dev33.satoken.context.SaTokenContext;
    +import cn.dev33.satoken.reactor.filter.SaPathCheckFilterForReactor;
    +import org.springframework.context.annotation.Bean;
     
     /**
      * 注册 Sa-Token 所需要的 Bean
    @@ -37,4 +37,14 @@ public SaTokenContext getSaTokenContextForSpringReactor() {
     		return new SaTokenContextForSpringReactor();
     	}
     
    +	/**
    +	 * 请求 path 校验过滤器
    +	 *
    +	 * @return /
    +	 */
    +	@Bean
    +	public SaPathCheckFilterForReactor saPathCheckFilterForReactor() {
    +		return new SaPathCheckFilterForReactor();
    +	}
    +
     }
    
  • sa-token-starter/sa-token-reactor-spring-boot-starter/src/main/java/cn/dev33/satoken/reactor/filter/SaPathCheckFilterForReactor.java+56 0 added
    @@ -0,0 +1,56 @@
    +/*
    + * Copyright 2020-2099 sa-token.cc
    + *
    + * Licensed under the Apache License, Version 2.0 (the "License");
    + * you may not use this file except in compliance with the License.
    + * You may obtain a copy of the License at
    + *
    + *      http://www.apache.org/licenses/LICENSE-2.0
    + *
    + * Unless required by applicable law or agreed to in writing, software
    + * distributed under the License is distributed on an "AS IS" BASIS,
    + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    + * See the License for the specific language governing permissions and
    + * limitations under the License.
    + */
    +package cn.dev33.satoken.reactor.filter;
    +
    +import cn.dev33.satoken.exception.RequestPathInvalidException;
    +import cn.dev33.satoken.strategy.SaStrategy;
    +import cn.dev33.satoken.util.SaTokenConsts;
    +import org.springframework.core.annotation.Order;
    +import org.springframework.web.server.ServerWebExchange;
    +import org.springframework.web.server.WebFilter;
    +import org.springframework.web.server.WebFilterChain;
    +import reactor.core.publisher.Mono;
    +
    +/**
    + * 校验请求 path 是否合法
    + *
    + * @author click33
    + * @since 1.37.0
    + */
    +@Order(SaTokenConsts.PATH_CHECK_FILTER_ORDER)
    +public class SaPathCheckFilterForReactor implements WebFilter {
    +
    +	@Override
    +	public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
    +
    +		// 校验本次请求 path 是否合法
    +		try {
    +			SaStrategy.instance.checkRequestPath.run(exchange.getRequest().getPath().toString(), exchange, null);
    +		} catch (RequestPathInvalidException e) {
    +			if(SaStrategy.instance.requestPathInvalidHandle == null) {
    +				exchange.getResponse().getHeaders().set(SaTokenConsts.CONTENT_TYPE_KEY, SaTokenConsts.CONTENT_TYPE_TEXT_PLAIN);
    +				return exchange.getResponse().writeWith(Mono.just(exchange.getResponse().bufferFactory().wrap(e.getMessage().getBytes())));
    +			} else {
    +				SaStrategy.instance.requestPathInvalidHandle.run(e, exchange, null);
    +			}
    +			return Mono.empty();
    +		}
    +
    +		// 向下执行
    +		return chain.filter(exchange);
    +	}
    +
    +}
    
  • sa-token-starter/sa-token-reactor-spring-boot-starter/src/main/java/cn/dev33/satoken/reactor/filter/SaReactorFilter.java+2 2 modified
    @@ -153,8 +153,8 @@ public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
     			// 2. 写入输出流
     			// 		请注意此处默认 Content-Type 为 text/plain,如果需要返回 JSON 信息,需要在 return 前自行设置 Content-Type 为 application/json
     			// 		例如:SaHolder.getResponse().setHeader("Content-Type", "application/json;charset=UTF-8");
    -			if(exchange.getResponse().getHeaders().getFirst("Content-Type") == null) {
    -				exchange.getResponse().getHeaders().set("Content-Type", "text/plain; charset=utf-8");
    +			if(exchange.getResponse().getHeaders().getFirst(SaTokenConsts.CONTENT_TYPE_KEY) == null) {
    +				exchange.getResponse().getHeaders().set(SaTokenConsts.CONTENT_TYPE_KEY, SaTokenConsts.CONTENT_TYPE_TEXT_PLAIN);
     			}
     			return exchange.getResponse().writeWith(Mono.just(exchange.getResponse().bufferFactory().wrap(result.getBytes())));
     			
    
  • sa-token-starter/sa-token-reactor-spring-boot-starter/src/main/java/cn/dev33/satoken/reactor/model/SaRequestForReactor.java+2 1 modified
    @@ -17,6 +17,7 @@
     
     
     import cn.dev33.satoken.SaManager;
    +import cn.dev33.satoken.application.ApplicationInfo;
     import cn.dev33.satoken.context.model.SaRequest;
     import cn.dev33.satoken.reactor.context.SaReactorHolder;
     import cn.dev33.satoken.reactor.context.SaReactorSyncHolder;
    @@ -112,7 +113,7 @@ public String getCookieValue(String name) {
     	 */
     	@Override
     	public String getRequestPath() {
    -		return request.getURI().getPath();
    +		return ApplicationInfo.cutPathPrefix(request.getPath().toString());
     	}
     
     	/**
    
  • sa-token-starter/sa-token-reactor-spring-boot-starter/src/main/java/cn/dev33/satoken/reactor/spring/SaTokenContextRegister.java+11 0 modified
    @@ -15,6 +15,7 @@
      */
     package cn.dev33.satoken.reactor.spring;
     
    +import cn.dev33.satoken.reactor.filter.SaPathCheckFilterForReactor;
     import org.springframework.context.annotation.Bean;
     
     import cn.dev33.satoken.context.SaTokenContext;
    @@ -37,4 +38,14 @@ public SaTokenContext getSaTokenContextForSpringReactor() {
     		return new SaTokenContextForSpringReactor();
     	}
     
    +	/**
    +	 * 请求 path 校验过滤器
    +	 *
    +	 * @return /
    +	 */
    +	@Bean
    +	public SaPathCheckFilterForReactor saPathCheckFilterForReactor() {
    +		return new SaPathCheckFilterForReactor();
    +	}
    +
     }
    
  • sa-token-starter/sa-token-servlet/src/main/java/cn/dev33/satoken/servlet/model/SaRequestForServlet.java+12 12 modified
    @@ -15,20 +15,20 @@
      */
     package cn.dev33.satoken.servlet.model;
     
    -import java.io.IOException;
    -import java.util.*;
    -
    -import javax.servlet.ServletException;
    -import javax.servlet.http.Cookie;
    -import javax.servlet.http.HttpServletRequest;
    -import javax.servlet.http.HttpServletResponse;
    -
     import cn.dev33.satoken.SaManager;
    +import cn.dev33.satoken.application.ApplicationInfo;
     import cn.dev33.satoken.context.model.SaRequest;
     import cn.dev33.satoken.exception.SaTokenException;
     import cn.dev33.satoken.servlet.error.SaServletErrorCode;
     import cn.dev33.satoken.util.SaFoxUtil;
     
    +import javax.servlet.ServletException;
    +import javax.servlet.http.Cookie;
    +import javax.servlet.http.HttpServletRequest;
    +import javax.servlet.http.HttpServletResponse;
    +import java.io.IOException;
    +import java.util.*;
    +
     /**
      * 对 SaRequest 包装类的实现(Servlet 版)
      *
    @@ -41,15 +41,15 @@ public class SaRequestForServlet implements SaRequest {
     	 * 底层Request对象
     	 */
     	protected HttpServletRequest request;
    -	
    +
     	/**
     	 * 实例化
    -	 * @param request request对象 
    +	 * @param request request对象
     	 */
     	public SaRequestForServlet(HttpServletRequest request) {
     		this.request = request;
     	}
    -	
    +
     	/**
     	 * 获取底层源对象 
     	 */
    @@ -125,7 +125,7 @@ public String getCookieValue(String name) {
     	 */
     	@Override
     	public String getRequestPath() {
    -		return request.getServletPath();
    +		return ApplicationInfo.cutPathPrefix(request.getRequestURI());
     	}
     
     	/**
    
  • sa-token-starter/sa-token-spring-boot3-starter/src/main/java/cn/dev33/satoken/filter/SaPathCheckFilterForJakartaServlet.java+68 0 added
    @@ -0,0 +1,68 @@
    +/*
    + * Copyright 2020-2099 sa-token.cc
    + *
    + * Licensed under the Apache License, Version 2.0 (the "License");
    + * you may not use this file except in compliance with the License.
    + * You may obtain a copy of the License at
    + *
    + *      http://www.apache.org/licenses/LICENSE-2.0
    + *
    + * Unless required by applicable law or agreed to in writing, software
    + * distributed under the License is distributed on an "AS IS" BASIS,
    + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    + * See the License for the specific language governing permissions and
    + * limitations under the License.
    + */
    +package cn.dev33.satoken.filter;
    +
    +import cn.dev33.satoken.exception.RequestPathInvalidException;
    +import cn.dev33.satoken.strategy.SaStrategy;
    +import cn.dev33.satoken.util.SaTokenConsts;
    +import jakarta.servlet.*;
    +import jakarta.servlet.http.HttpServletRequest;
    +import org.springframework.core.annotation.Order;
    +
    +import java.io.IOException;
    +
    +/**
    + * 校验请求 path 是否合法
    + *
    + * @author click33
    + * @since 1.37.0
    + */
    +@Order(SaTokenConsts.PATH_CHECK_FILTER_ORDER)
    +public class SaPathCheckFilterForJakartaServlet implements Filter {
    +
    +	@Override
    +	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    +
    +		// 校验本次请求 path 是否合法
    +		try {
    +			HttpServletRequest req = (HttpServletRequest) request;
    +			SaStrategy.instance.checkRequestPath.run(req.getRequestURI(), request, response);
    +		} catch (RequestPathInvalidException e) {
    +			if(SaStrategy.instance.requestPathInvalidHandle == null) {
    +				response.setContentType("text/plain; charset=utf-8");
    +				response.getWriter().print(e.getMessage());
    +				response.getWriter().flush();
    +            } else {
    +				SaStrategy.instance.requestPathInvalidHandle.run(e, request, response);
    +            }
    +            return;
    +        }
    +		
    +		// 向下执行
    +		chain.doFilter(request, response);
    +	}
    +
    +	@Override
    +	public void init(FilterConfig filterConfig) {
    +	}
    +	
    +	@Override
    +	public void destroy() {
    +	}
    +
    +	
    +	
    +}
    
  • sa-token-starter/sa-token-spring-boot3-starter/src/main/java/cn/dev33/satoken/filter/SaServletFilter.java+8 14 modified
    @@ -15,25 +15,19 @@
      */
     package cn.dev33.satoken.filter;
     
    -import java.io.IOException;
    -import java.util.ArrayList;
    -import java.util.Arrays;
    -import java.util.List;
    -
    -import org.springframework.core.annotation.Order;
    -
     import cn.dev33.satoken.error.SaSpringBootErrorCode;
     import cn.dev33.satoken.exception.BackResultException;
     import cn.dev33.satoken.exception.SaTokenException;
     import cn.dev33.satoken.exception.StopMatchException;
     import cn.dev33.satoken.router.SaRouter;
     import cn.dev33.satoken.util.SaTokenConsts;
    -import jakarta.servlet.Filter;
    -import jakarta.servlet.FilterChain;
    -import jakarta.servlet.FilterConfig;
    -import jakarta.servlet.ServletException;
    -import jakarta.servlet.ServletRequest;
    -import jakarta.servlet.ServletResponse;
    +import jakarta.servlet.*;
    +import org.springframework.core.annotation.Order;
    +
    +import java.io.IOException;
    +import java.util.ArrayList;
    +import java.util.Arrays;
    +import java.util.List;
     
     /**
      * Servlet 全局鉴权过滤器
    @@ -146,7 +140,7 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha
     			// 		请注意此处默认 Content-Type 为 text/plain,如果需要返回 JSON 信息,需要在 return 前自行设置 Content-Type 为 application/json
     			// 		例如:SaHolder.getResponse().setHeader("Content-Type", "application/json;charset=UTF-8");
     			if(response.getContentType() == null) {
    -				response.setContentType("text/plain; charset=utf-8"); 
    +				response.setContentType(SaTokenConsts.CONTENT_TYPE_TEXT_PLAIN);
     			}
     			response.getWriter().print(result);
     			return;
    
  • sa-token-starter/sa-token-spring-boot3-starter/src/main/java/cn/dev33/satoken/spring/SaTokenContextRegister.java+12 2 modified
    @@ -15,9 +15,9 @@
      */
     package cn.dev33.satoken.spring;
     
    -import org.springframework.context.annotation.Bean;
    -
     import cn.dev33.satoken.context.SaTokenContext;
    +import cn.dev33.satoken.filter.SaPathCheckFilterForJakartaServlet;
    +import org.springframework.context.annotation.Bean;
     
     /**
      * 注册 Sa-Token 框架所需要的 Bean
    @@ -37,4 +37,14 @@ public SaTokenContext getSaTokenContextForSpringInJakartaServlet() {
     		return new SaTokenContextForSpringInJakartaServlet();
     	}
     
    +	/**
    +	 * 请求 path 校验过滤器
    +	 *
    +	 * @return /
    +	 */
    +	@Bean
    +	public SaPathCheckFilterForJakartaServlet saPathCheckFilterForJakartaServlet() {
    +		return new SaPathCheckFilterForJakartaServlet();
    +	}
    +
     }
    
  • sa-token-starter/sa-token-spring-boot-autoconfig/src/main/java/cn/dev33/satoken/spring/context/path/ApplicationContextPathLoading.java+68 0 added
    @@ -0,0 +1,68 @@
    +/*
    + * Copyright 2020-2099 sa-token.cc
    + *
    + * Licensed under the Apache License, Version 2.0 (the "License");
    + * you may not use this file except in compliance with the License.
    + * You may obtain a copy of the License at
    + *
    + *      http://www.apache.org/licenses/LICENSE-2.0
    + *
    + * Unless required by applicable law or agreed to in writing, software
    + * distributed under the License is distributed on an "AS IS" BASIS,
    + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    + * See the License for the specific language governing permissions and
    + * limitations under the License.
    + */
    +package cn.dev33.satoken.spring.context.path;
    +
    +import cn.dev33.satoken.application.ApplicationInfo;
    +import cn.dev33.satoken.util.SaFoxUtil;
    +import org.springframework.beans.factory.annotation.Value;
    +import org.springframework.boot.ApplicationArguments;
    +import org.springframework.boot.ApplicationRunner;
    +
    +/**
    + * 应用上下文路径加载器
    + *
    + * @author click33
    + * @since 1.37.0
    + */
    +public class ApplicationContextPathLoading implements ApplicationRunner {
    +
    +    @Value("${server.servlet.context-path:}")
    +    String contextPath;
    +
    +    @Value("${spring.mvc.servlet.path:}")
    +    String servletPath;
    +
    +    @Override
    +    public void run(ApplicationArguments args) throws Exception {
    +
    +        String routePrefix = "";
    +
    +        if(SaFoxUtil.isNotEmpty(contextPath)) {
    +            if(! contextPath.startsWith("/")){
    +                contextPath = "/" + contextPath;
    +            }
    +            if (contextPath.endsWith("/")) {
    +                contextPath = contextPath.substring(0, contextPath.length() - 1);
    +            }
    +            routePrefix += contextPath;
    +        }
    +
    +        if(SaFoxUtil.isNotEmpty(servletPath)) {
    +            if(! servletPath.startsWith("/")){
    +                servletPath = "/" + servletPath;
    +            }
    +            if (servletPath.endsWith("/")) {
    +                servletPath = servletPath.substring(0, servletPath.length() - 1);
    +            }
    +            routePrefix += servletPath;
    +        }
    +
    +        if(SaFoxUtil.isNotEmpty(routePrefix) && ! routePrefix.equals("/") ){
    +            ApplicationInfo.routePrefix = routePrefix;
    +        }
    +    }
    +
    +}
    \ No newline at end of file
    
  • sa-token-starter/sa-token-spring-boot-autoconfig/src/main/java/cn/dev33/satoken/spring/SaBeanRegister.java+11 1 modified
    @@ -15,6 +15,7 @@
      */
     package cn.dev33.satoken.spring;
     
    +import cn.dev33.satoken.spring.context.path.ApplicationContextPathLoading;
     import org.springframework.boot.context.properties.ConfigurationProperties;
     import org.springframework.context.annotation.Bean;
     
    @@ -50,5 +51,14 @@ public SaTokenConfig getSaTokenConfig() {
     	public SaJsonTemplate getSaJsonTemplateForJackson() {
     		return new SaJsonTemplateForJackson();
     	}
    -	
    +
    +	/**
    +	 * 应用上下文路径加载器
    +	 * @return /
    +	 */
    +	@Bean
    +	public ApplicationContextPathLoading getApplicationContextPathLoading() {
    +		return new ApplicationContextPathLoading();
    +	}
    +
     }
    
  • sa-token-starter/sa-token-spring-boot-starter/src/main/java/cn/dev33/satoken/filter/SaPathCheckFilterForServlet.java+68 0 added
    @@ -0,0 +1,68 @@
    +/*
    + * Copyright 2020-2099 sa-token.cc
    + *
    + * Licensed under the Apache License, Version 2.0 (the "License");
    + * you may not use this file except in compliance with the License.
    + * You may obtain a copy of the License at
    + *
    + *      http://www.apache.org/licenses/LICENSE-2.0
    + *
    + * Unless required by applicable law or agreed to in writing, software
    + * distributed under the License is distributed on an "AS IS" BASIS,
    + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    + * See the License for the specific language governing permissions and
    + * limitations under the License.
    + */
    +package cn.dev33.satoken.filter;
    +
    +import cn.dev33.satoken.exception.RequestPathInvalidException;
    +import cn.dev33.satoken.strategy.SaStrategy;
    +import cn.dev33.satoken.util.SaTokenConsts;
    +import org.springframework.core.annotation.Order;
    +
    +import javax.servlet.*;
    +import javax.servlet.http.HttpServletRequest;
    +import java.io.IOException;
    +
    +/**
    + * 校验请求 path 是否合法
    + *
    + * @author click33
    + * @since 1.37.0
    + */
    +@Order(SaTokenConsts.PATH_CHECK_FILTER_ORDER)
    +public class SaPathCheckFilterForServlet implements Filter {
    +
    +	@Override
    +	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    +
    +		// 校验本次请求 path 是否合法
    +		try {
    +			HttpServletRequest req = (HttpServletRequest) request;
    +			SaStrategy.instance.checkRequestPath.run(req.getRequestURI(), request, response);
    +		} catch (RequestPathInvalidException e) {
    +			if(SaStrategy.instance.requestPathInvalidHandle == null) {
    +				response.setContentType("text/plain; charset=utf-8");
    +				response.getWriter().print(e.getMessage());
    +				response.getWriter().flush();
    +            } else {
    +				SaStrategy.instance.requestPathInvalidHandle.run(e, request, response);
    +            }
    +            return;
    +        }
    +		
    +		// 向下执行
    +		chain.doFilter(request, response);
    +	}
    +
    +	@Override
    +	public void init(FilterConfig filterConfig) {
    +	}
    +	
    +	@Override
    +	public void destroy() {
    +	}
    +
    +	
    +	
    +}
    
  • sa-token-starter/sa-token-spring-boot-starter/src/main/java/cn/dev33/satoken/filter/SaServletFilter.java+8 15 modified
    @@ -15,26 +15,19 @@
      */
     package cn.dev33.satoken.filter;
     
    -import java.io.IOException;
    -import java.util.ArrayList;
    -import java.util.Arrays;
    -import java.util.List;
    -
    -import javax.servlet.Filter;
    -import javax.servlet.FilterChain;
    -import javax.servlet.FilterConfig;
    -import javax.servlet.ServletException;
    -import javax.servlet.ServletRequest;
    -import javax.servlet.ServletResponse;
    -
    -import org.springframework.core.annotation.Order;
    -
     import cn.dev33.satoken.error.SaSpringBootErrorCode;
     import cn.dev33.satoken.exception.BackResultException;
     import cn.dev33.satoken.exception.SaTokenException;
     import cn.dev33.satoken.exception.StopMatchException;
     import cn.dev33.satoken.router.SaRouter;
     import cn.dev33.satoken.util.SaTokenConsts;
    +import org.springframework.core.annotation.Order;
    +
    +import javax.servlet.*;
    +import java.io.IOException;
    +import java.util.ArrayList;
    +import java.util.Arrays;
    +import java.util.List;
     
     /**
      * Servlet 全局鉴权过滤器
    @@ -147,7 +140,7 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha
     			// 		请注意此处默认 Content-Type 为 text/plain,如果需要返回 JSON 信息,需要在 return 前自行设置 Content-Type 为 application/json
     			// 		例如:SaHolder.getResponse().setHeader("Content-Type", "application/json;charset=UTF-8");
     			if(response.getContentType() == null) {
    -				response.setContentType("text/plain; charset=utf-8"); 
    +				response.setContentType(SaTokenConsts.CONTENT_TYPE_TEXT_PLAIN);
     			}
     			response.getWriter().print(result);
     			return;
    
  • sa-token-starter/sa-token-spring-boot-starter/src/main/java/cn/dev33/satoken/spring/SaTokenContextRegister.java+12 2 modified
    @@ -15,9 +15,9 @@
      */
     package cn.dev33.satoken.spring;
     
    -import org.springframework.context.annotation.Bean;
    -
     import cn.dev33.satoken.context.SaTokenContext;
    +import cn.dev33.satoken.filter.SaPathCheckFilterForServlet;
    +import org.springframework.context.annotation.Bean;
     
     /**
      * 注册 Sa-Token 框架所需要的 Bean
    @@ -37,4 +37,14 @@ public SaTokenContext getSaTokenContextForSpring() {
     		return new SaTokenContextForSpring();
     	}
     
    +	/**
    +	 * 请求 path 校验过滤器
    +	 *
    +	 * @return /
    +	 */
    +	@Bean
    +	public SaPathCheckFilterForServlet saPathCheckFilterForServlet() {
    +		return new SaPathCheckFilterForServlet();
    +	}
    +
     }
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

4

News mentions

0

No linked articles in our index yet.