Glassfish 7.0.16中并发Ajax请求出现403 Forbidden问题排查
核心原因分析
单请求正常但并发场景出现随机403,且请求的Principal和会话属性完全一致,大概率是以下两类问题导致:
过滤器注入SecurityContext的线程安全隐患
Servlet Filter默认是单例实例,若直接@Inject SecurityContext且未指定正确作用域,会导致多个并发请求共享同一个SecurityContext实例。Glassfish中的SecurityContext是绑定到当前请求线程的,单例过滤器里的注入实例无法随请求动态切换,并发时会拿到其他请求的上下文,进而导致角色判断错误。Glassfish角色缓存的并发同步漏洞
Glassfish默认会缓存会话中的角色信息以提升性能,但并发场景下,会话的角色缓存可能存在线程同步问题,导致部分请求读取到未正确初始化的角色数据——即便Principal正确,角色校验仍会失败。过滤器执行顺序问题
如果自定义过滤器在Jakarta Security的内置安全过滤器之前执行,此时SecurityContext可能还未完成初始化,并发下不同请求的初始化时机差异会引发随机403。
解决方案
1. 修正SecurityContext的获取方式(优先推荐)
不要在过滤器中直接注入SecurityContext,改为从当前请求中动态获取:
public class AuthorizationFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) request; HttpServletResponse res = (HttpServletResponse) response; // 从请求属性中获取当前请求绑定的SecurityContext SecurityContext sc = (SecurityContext) req.getAttribute("jakarta.security.auth.message.SecurityContext"); boolean hasRole = req.isUserInRole("ROLE_A"); // 双重校验,兼容不同场景 if (sc != null) { hasRole = hasRole || sc.isCallerInRole("ROLE_A"); } if (hasRole) { chain.doFilter(request, response); } else { // 日志打印调试信息 System.out.printf("Forbidden - Principal: %s, Session ID: %s%n", req.getUserPrincipal(), req.getSession().getId()); res.sendError(HttpServletResponse.SC_FORBIDDEN); } } // init、destroy方法按需实现 }
2. 调整过滤器执行顺序
确保自定义过滤器在Jakarta Security的内置过滤器之后执行:
- 若使用web.xml配置,显式定义过滤器映射,保证顺序在安全约束之后:
<filter> <filter-name>AuthorizationFilter</filter-name> <filter-class>com.yourpackage.AuthorizationFilter</filter-class> </filter> <filter-mapping> <filter-name>AuthorizationFilter</filter-name> <url-pattern>/your-protected-path/*</url-pattern> <dispatcher>REQUEST</dispatcher> </filter-mapping>
- 若使用
@WebFilter注解,添加@Order(100)(数值越大执行越晚,确保在安全过滤器之后):
@WebFilter(urlPatterns = "/your-protected-path/*") @Order(100) public class AuthorizationFilter implements Filter { // ... }
3. 禁用Glassfish角色缓存
修改Glassfish的domain.xml配置,禁用角色缓存以规避并发同步问题:
<security-service> <auth-realm name="your-jdbc-realm" classname="com.sun.enterprise.security.auth.realm.jdbc.JDBCRealm"> <!-- 你的JDBC Realm其他配置 --> <property name="roleCacheEnabled" value="false"></property> </auth-realm> </security-service>
注意:禁用缓存会增加数据库查询次数,需评估性能影响。
4. 升级Glassfish版本
Glassfish 7.0.16可能存在Jakarta Security的并发处理bug,尝试升级到最新稳定版本(如7.0.17及以上),官方可能已修复此类问题。
内容的提问来源于stack exchange,提问作者Isaac Rodriguez

