JeecgBoot低代码模式拦截器逻辑缺陷分析与修复
引言
在企业级低代码开发平台JeecgBoot中,低代码模式拦截器(LowCodeModeInterceptor)承担着重要的安全防护职责。该拦截器用于控制在线开发功能的访问权限,确保在发布模式下只有授权角色能够进行配置操作。然而,在实际使用过程中,我们发现该拦截器存在一些逻辑缺陷,可能导致安全漏洞或功能异常。
低代码模式拦截器架构分析
核心组件结构
拦截器配置机制
低代码模式拦截器通过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())) {
// 业务逻辑
}
风险分析:
jeecgBaseConfig.getFirewall()可能返回null- 未对
commonAPI的获取结果进行null检查 - 后续的
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);
风险分析:
hasRoles可能为null,导致CommonUtils.hasIntersection()方法异常- 未处理
commonAPI.queryUserRolesById()返回null的情况 - 角色验证逻辑缺乏完整的异常处理
缺陷三:配置加载时机问题
拦截器在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低代码模式拦截器的深入分析,我们识别并修复了多个关键逻辑缺陷:
- 空指针异常防护:增强了配置和依赖对象的空值检查
- 角色验证完整性:完善了用户角色获取和权限验证逻辑
- 配置加载时机:优化了配置预加载和动态刷新机制
- 异常处理策略:添加了完整的异常处理和降级方案
修复后的拦截器具有更高的稳定性和安全性,能够有效保护低代码平台的在线开发功能,同时确保系统的可靠运行。建议在升级时进行充分的测试验证,确保与现有系统的兼容性。
注意事项:
- 升级前备份原有配置
- 在生产环境部署前进行完整测试
- 监控拦截器的运行状态和性能指标
- 定期审查和更新允许的开发角色配置
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



