diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmParallelMultiInstanceBehavior.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmParallelMultiInstanceBehavior.java index 8d824d401b..20fcf54753 100644 --- a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmParallelMultiInstanceBehavior.java +++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmParallelMultiInstanceBehavior.java @@ -56,7 +56,14 @@ public class BpmParallelMultiInstanceBehavior extends ParallelMultiInstanceBehav protected int resolveNrOfInstances(DelegateExecution execution) { // 情况一:UserTask 节点 if (execution.getCurrentFlowElement() instanceof UserTask) { - // 获取任务的所有处理人 + // 第一步,设置 collectionVariable 和 CollectionVariable + // 从 execution.getVariable() 读取所有任务处理人的 key + super.collectionExpression = null; // collectionExpression 和 collectionVariable 是互斥的 + super.collectionVariable = FlowableUtils.formatExecutionCollectionVariable(execution.getCurrentActivityId()); + // 从 execution.getVariable() 读取当前所有任务处理的人的 key + super.collectionElementVariable = FlowableUtils.formatExecutionCollectionElementVariable(execution.getCurrentActivityId()); + + // 第二步,获取任务的所有处理人 @SuppressWarnings("unchecked") Set assigneeUserIds = (Set) execution.getVariable(super.collectionVariable, Set.class); if (assigneeUserIds == null) { diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmSequentialMultiInstanceBehavior.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmSequentialMultiInstanceBehavior.java index 75582a0541..ebf67a46bb 100644 --- a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmSequentialMultiInstanceBehavior.java +++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmSequentialMultiInstanceBehavior.java @@ -47,7 +47,14 @@ public class BpmSequentialMultiInstanceBehavior extends SequentialMultiInstanceB protected int resolveNrOfInstances(DelegateExecution execution) { // 情况一:UserTask 节点 if (execution.getCurrentFlowElement() instanceof UserTask) { - // 获取任务的所有处理人 + // 第一步,设置 collectionVariable 和 CollectionVariable + // 从 execution.getVariable() 读取所有任务处理人的 key + super.collectionExpression = null; // collectionExpression 和 collectionVariable 是互斥的 + super.collectionVariable = FlowableUtils.formatExecutionCollectionVariable(execution.getCurrentActivityId()); + // 从 execution.getVariable() 读取当前所有任务处理的人的 key + super.collectionElementVariable = FlowableUtils.formatExecutionCollectionElementVariable(execution.getCurrentActivityId()); + + // 第二步,获取任务的所有处理人 // 不使用 execution.getVariable 原因:目前依次审批任务回退后 collectionVariable 变量没有清理, 如果重新进入该任务不会重新分配审批人 @SuppressWarnings("unchecked") Set assigneeUserIds = (Set) execution.getVariableLocal(super.collectionVariable, Set.class); diff --git a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/framework/monitor/config/AdminServerConfiguration.java b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/framework/monitor/config/AdminServerConfiguration.java index 0c29865c3a..02068c513b 100644 --- a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/framework/monitor/config/AdminServerConfiguration.java +++ b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/framework/monitor/config/AdminServerConfiguration.java @@ -1,9 +1,107 @@ package cn.iocoder.yudao.module.infra.framework.monitor.config; import de.codecentric.boot.admin.server.config.EnableAdminServer; +import jakarta.servlet.DispatcherType; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; +import org.springframework.security.config.Customizer; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.provisioning.InMemoryUserDetailsManager; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler; +import org.springframework.security.web.csrf.CookieCsrfTokenRepository; +/** + * Spring Boot Admin Server 配置 + * + * 包含 Admin Server 的启用配置和安全配置 + * 安全配置独立于 {@link cn.iocoder.yudao.framework.security.config.YudaoWebSecurityConfigurerAdapter}, + * 使用 HTTP Basic 认证保护 Admin Server 端点,不影响现有的 Token 认证机制 + * + * @author 芋道源码 + */ @Configuration(proxyBeanMethods = false) @EnableAdminServer +@ConditionalOnClass(name = "de.codecentric.boot.admin.server.config.AdminServerProperties") // 目的:按需启动 spring boot admin 监控服务 public class AdminServerConfiguration { + + @Value("${spring.boot.admin.context-path:''}") + private String adminSeverContextPath; + + @Value("${spring.boot.admin.client.username:admin}") + private String username; + + @Value("${spring.boot.admin.client.password:admin}") + private String password; + + /** + * Spring Boot Admin 专用的 InMemoryUserDetailsManager + * 使用内存存储,与系统用户隔离 + */ + @Bean("adminUserDetailsManager") + public InMemoryUserDetailsManager adminUserDetailsManager(PasswordEncoder passwordEncoder) { + UserDetails adminUser = User.builder() + .username(username) + .password(passwordEncoder.encode(password)) + .roles("ADMIN_SERVER") + .build(); + return new InMemoryUserDetailsManager(adminUser); + } + + /** + * Spring Boot Admin Server 的 SecurityFilterChain + * 使用 @Order(1) 确保优先于默认的 SecurityFilterChain 匹配 + */ + @Bean("adminServerSecurityFilterChain") + @Order(1) + public SecurityFilterChain adminServerSecurityFilterChain(HttpSecurity httpSecurity, + InMemoryUserDetailsManager adminUserDetailsManager) throws Exception { + // 登录成功后的处理器 + SavedRequestAwareAuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler(); + successHandler.setTargetUrlParameter("redirectTo"); + successHandler.setDefaultTargetUrl(adminSeverContextPath + "/"); + + // 配置 HttpSecurity 对象 + httpSecurity + // 仅匹配 Admin Server 的路径 + .securityMatcher(adminSeverContextPath + "/**") + // 使用独立的 UserDetailsManager + .userDetailsService(adminUserDetailsManager) + // 授权配置 + .authorizeHttpRequests(auth -> auth + .requestMatchers(adminSeverContextPath + "/assets/**").permitAll() // 静态资源允许匿名访问 + .requestMatchers(adminSeverContextPath + "/login").permitAll() // 登录页面允许匿名访问 + .dispatcherTypeMatchers(DispatcherType.ASYNC).permitAll() // 异步请求允许 + .anyRequest().authenticated() // 其他请求需要认证 + ) + // 表单登录配置(用于 Admin UI 访问) + .formLogin(form -> form + .loginPage(adminSeverContextPath + "/login") + .successHandler(successHandler) + .permitAll() + ) + // 登出配置 + .logout(logout -> logout + .logoutUrl(adminSeverContextPath + "/logout") + .logoutSuccessUrl(adminSeverContextPath + "/login") + ) + // HTTP Basic 认证(用于 Admin Client 注册) + .httpBasic(Customizer.withDefaults()) + // CSRF 配置 + .csrf(csrf -> csrf + .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) + .ignoringRequestMatchers( + adminSeverContextPath + "/instances", // Admin Client 注册端点忽略 CSRF + adminSeverContextPath + "/actuator/**" // Actuator 端点忽略 CSRF + ) + ); + return httpSecurity.build(); + } + } diff --git a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/framework/security/config/SecurityConfiguration.java b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/framework/security/config/SecurityConfiguration.java index 8d3040845b..e3be433e20 100644 --- a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/framework/security/config/SecurityConfiguration.java +++ b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/framework/security/config/SecurityConfiguration.java @@ -1,7 +1,6 @@ package cn.iocoder.yudao.module.infra.framework.security.config; import cn.iocoder.yudao.framework.security.config.AuthorizeRequestsCustomizer; -import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; @@ -13,9 +12,6 @@ import org.springframework.security.config.annotation.web.configurers.AuthorizeH @Configuration(proxyBeanMethods = false, value = "infraSecurityConfiguration") public class SecurityConfiguration { - @Value("${spring.boot.admin.context-path:''}") - private String adminSeverContextPath; - @Bean("infraAuthorizeRequestsCustomizer") public AuthorizeRequestsCustomizer authorizeRequestsCustomizer() { return new AuthorizeRequestsCustomizer() { @@ -32,9 +28,6 @@ public class SecurityConfiguration { .requestMatchers("/actuator/**").permitAll(); // Druid 监控 registry.requestMatchers("/druid/**").permitAll(); - // Spring Boot Admin Server 的安全配置 - registry.requestMatchers(adminSeverContextPath).permitAll() - .requestMatchers(adminSeverContextPath + "/**").permitAll(); // 文件读取 registry.requestMatchers(buildAdminApi("/infra/file/*/get/**")).permitAll(); } diff --git a/yudao-server/src/main/resources/application-dev.yaml b/yudao-server/src/main/resources/application-dev.yaml index 8c1ab5c104..b272991661 100644 --- a/yudao-server/src/main/resources/application-dev.yaml +++ b/yudao-server/src/main/resources/application-dev.yaml @@ -137,6 +137,8 @@ spring: url: http://127.0.0.1:${server.port}/${spring.boot.admin.context-path} # 设置 Spring Boot Admin Server 地址 instance: service-host-type: IP # 注册实例时,优先使用 IP [IP, HOST_NAME, CANONICAL_HOST_NAME] + username: admin + password: admin # Spring Boot Admin Server 服务端的相关配置 context-path: /admin # 配置 Spring diff --git a/yudao-server/src/main/resources/application-local.yaml b/yudao-server/src/main/resources/application-local.yaml index a9485d064a..f6ef1d2401 100644 --- a/yudao-server/src/main/resources/application-local.yaml +++ b/yudao-server/src/main/resources/application-local.yaml @@ -158,6 +158,8 @@ spring: url: http://127.0.0.1:${server.port}/${spring.boot.admin.context-path} # 设置 Spring Boot Admin Server 地址 instance: service-host-type: IP # 注册实例时,优先使用 IP [IP, HOST_NAME, CANONICAL_HOST_NAME] + username: admin + password: admin # Spring Boot Admin Server 服务端的相关配置 context-path: /admin # 配置 Spring