JeecgBoot低代码模式拦截器逻辑缺陷分析与修复

JeecgBoot低代码模式拦截器逻辑缺陷分析与修复

【免费下载链接】JeecgBoot 🔥企业级低代码平台集成了AI应用平台,帮助企业快速实现低代码开发和构建AI应用!前后端分离架构 SpringBoot,SpringCloud、Mybatis,Ant Design4、 Vue3.0、TS+vite!强大的代码生成器让前后端代码一键生成,无需写任何代码! 引领AI低代码开发模式: AI生成->OnlineCoding-> 代码生成-> 手工MERGE,显著的提高效率,又不失灵活~ 【免费下载链接】JeecgBoot 项目地址: https://gitcode.com/jeecgboot/JeecgBoot

引言

在企业级低代码开发平台JeecgBoot中,低代码模式拦截器(LowCodeModeInterceptor)承担着重要的安全防护职责。该拦截器用于控制在线开发功能的访问权限,确保在发布模式下只有授权角色能够进行配置操作。然而,在实际使用过程中,我们发现该拦截器存在一些逻辑缺陷,可能导致安全漏洞或功能异常。

低代码模式拦截器架构分析

核心组件结构

mermaid

拦截器配置机制

低代码模式拦截器通过Spring MVC的拦截器机制实现,配置类LowCodeModeConfiguration将拦截器注册到指定的URL模式:

@Configuration
public class LowCodeModeConfiguration implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(lowCodeModeInterceptor)
                .addPathPatterns(LowCodeUrlsEnum.getLowCodeInterceptUrls());
    }
}

逻辑缺陷分析

缺陷一:空指针异常风险

LowCodeModeInterceptor.preHandle()方法中,存在多个潜在的空指针异常风险点:

// 问题代码片段
if (jeecgBaseConfig == null) {
    jeecgBaseConfig = SpringContextUtils.getBean(JeecgBaseConfig.class);
}
if (commonAPI == null) {
    commonAPI = SpringContextUtils.getBean(CommonAPI.class);
}

// 此处jeecgBaseConfig.getFirewall()可能返回null
if (jeecgBaseConfig.getFirewall()!=null && 
    LowCodeModeInterceptor.LOW_CODE_MODE_PROD.equals(
        jeecgBaseConfig.getFirewall().getLowCodeMode())) {
    // 业务逻辑
}

风险分析:

  1. jeecgBaseConfig.getFirewall()可能返回null
  2. 未对commonAPI的获取结果进行null检查
  3. 后续的commonAPI.getUserByName()调用可能抛出NPE

缺陷二:角色验证逻辑不完整

// 问题代码片段
LoginUser loginUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
Set<String> hasRoles = null;
if (loginUser == null) {
    loginUser = commonAPI.getUserByName(
        JwtUtil.getUserNameByToken(SpringContextUtils.getHttpServletRequest()));
}

if (loginUser != null) {
    hasRoles = commonAPI.queryUserRolesById(loginUser.getId());
}

// 此处hasRoles可能为null
boolean hasIntersection = CommonUtils.hasIntersection(hasRoles, CommonConstant.allowDevRoles);

风险分析:

  1. hasRoles可能为null,导致CommonUtils.hasIntersection()方法异常
  2. 未处理commonAPI.queryUserRolesById()返回null的情况
  3. 角色验证逻辑缺乏完整的异常处理

缺陷三:配置加载时机问题

拦截器在Spring容器初始化阶段就被创建,但相关配置可能尚未完全加载:

@Component
public class LowCodeModeInterceptor implements HandlerInterceptor {
    @Resource
    private JeecgBaseConfig jeecgBaseConfig; // 可能为null
    
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        // 运行时才尝试获取配置,存在时序问题
        if (jeecgBaseConfig == null) {
            jeecgBaseConfig = SpringContextUtils.getBean(JeecgBaseConfig.class);
        }
    }
}

修复方案

方案一:增强空值检查和安全访问

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
    try {
        // 增强配置加载逻辑
        if (jeecgBaseConfig == null) {
            jeecgBaseConfig = SpringContextUtils.getBean(JeecgBaseConfig.class);
            if (jeecgBaseConfig == null) {
                log.warn("JeecgBaseConfig bean not found, skip low code mode check");
                return true;
            }
        }
        
        if (commonAPI == null) {
            commonAPI = SpringContextUtils.getBean(CommonAPI.class);
            if (commonAPI == null) {
                log.warn("CommonAPI bean not found, skip low code mode check");
                return true;
            }
        }
        
        // 安全访问配置
        JeecgBaseConfig.FirewallConfig firewall = jeecgBaseConfig.getFirewall();
        if (firewall == null || !LOW_CODE_MODE_PROD.equals(firewall.getLowCodeMode())) {
            return true;
        }
        
        // 增强用户信息获取逻辑
        LoginUser loginUser = getLoginUserSafely(request);
        if (loginUser == null) {
            returnErrorMessage(response);
            return false;
        }
        
        // 安全获取角色信息
        Set<String> hasRoles = getUsersRolesSafely(loginUser.getId());
        boolean hasPermission = checkUserPermission(loginUser, hasRoles);
        
        if (!hasPermission) {
            returnErrorMessage(response);
            return false;
        }
        
        return true;
    } catch (Exception e) {
        log.error("Low code mode interceptor error", e);
        // 发生异常时保守策略:拒绝访问
        returnErrorMessage(response);
        return false;
    }
}

private LoginUser getLoginUserSafely(HttpServletRequest request) {
    try {
        LoginUser loginUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
        if (loginUser == null) {
            String token = JwtUtil.getToken(SpringContextUtils.getHttpServletRequest());
            if (token != null) {
                String username = JwtUtil.getUserNameByToken(token);
                if (username != null) {
                    return commonAPI.getUserByName(username);
                }
            }
        }
        return loginUser;
    } catch (Exception e) {
        log.warn("Get login user failed", e);
        return null;
    }
}

private Set<String> getUsersRolesSafely(String userId) {
    try {
        Set<String> roles = commonAPI.queryUserRolesById(userId);
        return roles != null ? roles : Collections.emptySet();
    } catch (Exception e) {
        log.warn("Get user roles failed for user: {}", userId, e);
        return Collections.emptySet();
    }
}

private boolean checkUserPermission(LoginUser loginUser, Set<String> hasRoles) {
    if (loginUser == null) {
        return false;
    }
    
    // 超级管理员直接放行
    if ("admin".equals(loginUser.getUsername())) {
        return true;
    }
    
    // 检查角色权限
    if (hasRoles != null && !hasRoles.isEmpty()) {
        return CommonUtils.hasIntersection(hasRoles, CommonConstant.allowDevRoles);
    }
    
    return false;
}

方案二:配置预加载和缓存机制

@Component
public class LowCodeModeInterceptor implements HandlerInterceptor, InitializingBean {
    
    @Resource
    private JeecgBaseConfig jeecgBaseConfig;
    @Resource
    private CommonAPI commonAPI;
    
    private volatile boolean lowCodeModeEnabled = false;
    private volatile Set<String> allowedRoles = new HashSet<>();
    
    @Override
    public void afterPropertiesSet() throws Exception {
        // 预加载配置
        refreshConfiguration();
    }
    
    private void refreshConfiguration() {
        try {
            if (jeecgBaseConfig != null && jeecgBaseConfig.getFirewall() != null) {
                lowCodeModeEnabled = LOW_CODE_MODE_PROD.equals(
                    jeecgBaseConfig.getFirewall().getLowCodeMode());
            }
            
            // 预加载允许的角色
            allowedRoles = new HashSet<>(Arrays.asList(CommonConstant.allowDevRoles));
        } catch (Exception e) {
            log.error("Refresh low code configuration failed", e);
            lowCodeModeEnabled = false;
            allowedRoles = Collections.emptySet();
        }
    }
    
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        if (!lowCodeModeEnabled) {
            return true;
        }
        
        // 简化的权限检查逻辑
        try {
            LoginUser loginUser = getLoginUserSafely(request);
            if (loginUser == null || !hasPermission(loginUser)) {
                returnErrorMessage(response);
                return false;
            }
            return true;
        } catch (Exception e) {
            log.error("Low code mode check failed", e);
            returnErrorMessage(response);
            return false;
        }
    }
    
    private boolean hasPermission(LoginUser loginUser) {
        if (loginUser == null) return false;
        if ("admin".equals(loginUser.getUsername())) return true;
        
        Set<String> userRoles = getUsersRolesSafely(loginUser.getId());
        return !Collections.disjoint(userRoles, allowedRoles);
    }
}

方案三:添加配置监控和动态刷新

@Component
public class LowCodeModeInterceptor implements HandlerInterceptor, ApplicationListener<EnvironmentChangeEvent> {
    
    // ... 其他字段
    
    @Override
    public void onApplicationEvent(EnvironmentChangeEvent event) {
        if (event.getKeys().contains("jeecg.firewall.low-code-mode") ||
            event.getKeys().contains("jeecg.firewall.allow-dev-roles")) {
            refreshConfiguration();
            log.info("Low code mode configuration refreshed");
        }
    }
    
    // 添加配置变更监听
    @Scheduled(fixedRate = 300000) // 每5分钟检查一次配置
    public void scheduledRefresh() {
        refreshConfiguration();
    }
}

测试验证方案

单元测试用例

@SpringBootTest
@ExtendWith(MockitoExtension.class)
class LowCodeModeInterceptorTest {
    
    @Mock
    private JeecgBaseConfig jeecgBaseConfig;
    @Mock
    private JeecgBaseConfig.FirewallConfig firewallConfig;
    @Mock
    private CommonAPI commonAPI;
    @Mock
    private HttpServletRequest request;
    @Mock
    private HttpServletResponse response;
    
    @InjectMocks
    private LowCodeModeInterceptor interceptor;
    
    @Test
    void testPreHandle_WhenFirewallConfigNull_ShouldReturnTrue() {
        when(jeecgBaseConfig.getFirewall()).thenReturn(null);
        
        boolean result = interceptor.preHandle(request, response, null);
        
        assertTrue(result);
    }
    
    @Test
    void testPreHandle_WhenLowCodeModeDev_ShouldReturnTrue() {
        when(jeecgBaseConfig.getFirewall()).thenReturn(firewallConfig);
        when(firewallConfig.getLowCodeMode()).thenReturn("dev");
        
        boolean result = interceptor.preHandle(request, response, null);
        
        assertTrue(result);
    }
    
    @Test
    void testPreHandle_WhenUserHasAdminRole_ShouldReturnTrue() {
        when(jeecgBaseConfig.getFirewall()).thenReturn(firewallConfig);
        when(firewallConfig.getLowCodeMode()).thenReturn("prod");
        
        LoginUser adminUser = createAdminUser();
        when(commonAPI.getUserByName(anyString())).thenReturn(adminUser);
        when(commonAPI.queryUserRolesById(anyString())).thenReturn(Set.of("admin"));
        
        boolean result = interceptor.preHandle(request, response, null);
        
        assertTrue(result);
    }
    
    @Test
    void testPreHandle_WhenCommonAPINull_ShouldReturnTrue() {
        // 测试异常情况下的降级处理
        interceptor.setCommonAPI(null);
        when(jeecgBaseConfig.getFirewall()).thenReturn(firewallConfig);
        when(firewallConfig.getLowCodeMode()).thenReturn("prod");
        
        boolean result = interceptor.preHandle(request, response, null);
        
        assertTrue(result); // 或者根据具体策略决定
    }
}

集成测试场景

测试场景预期结果测试要点
配置为空放行所有请求空配置安全性
开发模式放行所有请求模式切换正确性
发布模式+管理员允许访问权限验证正确性
发布模式+无权限用户拒绝访问安全防护有效性
服务异常降级处理系统稳定性

部署和监控建议

配置管理

# application.yml 配置示例
jeecg:
  firewall:
    low-code-mode: prod # dev:开发模式, prod:发布模式
    allow-dev-roles: lowdeveloper,admin
    enable-dynamic-refresh: true
    refresh-interval: 300000

监控指标

// 添加监控指标
@Bean
public MeterBinder lowCodeModeMetrics(LowCodeModeInterceptor interceptor) {
    return registry -> {
        Gauge.builder("jeecg.lowcode.mode.enabled", 
                     () -> interceptor.isLowCodeModeEnabled() ? 1 : 0)
            .description("Low code mode enabled status")
            .register(registry);
        
        Counter.builder("jeecg.lowcode.interceptor.requests")
            .description("Total low code mode interceptor requests")
            .register(registry);
    };
}

总结

通过对JeecgBoot低代码模式拦截器的深入分析,我们识别并修复了多个关键逻辑缺陷:

  1. 空指针异常防护:增强了配置和依赖对象的空值检查
  2. 角色验证完整性:完善了用户角色获取和权限验证逻辑
  3. 配置加载时机:优化了配置预加载和动态刷新机制
  4. 异常处理策略:添加了完整的异常处理和降级方案

修复后的拦截器具有更高的稳定性和安全性,能够有效保护低代码平台的在线开发功能,同时确保系统的可靠运行。建议在升级时进行充分的测试验证,确保与现有系统的兼容性。

注意事项:

  • 升级前备份原有配置
  • 在生产环境部署前进行完整测试
  • 监控拦截器的运行状态和性能指标
  • 定期审查和更新允许的开发角色配置

【免费下载链接】JeecgBoot 🔥企业级低代码平台集成了AI应用平台,帮助企业快速实现低代码开发和构建AI应用!前后端分离架构 SpringBoot,SpringCloud、Mybatis,Ant Design4、 Vue3.0、TS+vite!强大的代码生成器让前后端代码一键生成,无需写任何代码! 引领AI低代码开发模式: AI生成->OnlineCoding-> 代码生成-> 手工MERGE,显著的提高效率,又不失灵活~ 【免费下载链接】JeecgBoot 项目地址: https://gitcode.com/jeecgboot/JeecgBoot

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值