基于自定义条件的访问授权:AccessDecisionVoter适用性及实现示例
嘿,完全理解你的需求!AccessDecisionVoter确实是Spring Security里处理这类自定义访问控制的绝佳方案——它刚好能让你脱离单纯的角色/权限校验,基于任意自定义条件来决定是否允许用户访问某个资源,完全适配你的场景。
为什么AccessDecisionVoter是合适的选择?
Spring Security的访问决策体系中,AccessDecisionVoter就是用来处理自定义投票逻辑的核心组件。它可以和AccessDecisionManager配合,针对特定资源(比如你的/getAllOrders URL),根据你定义的任意条件(而非固定角色/权限)来决定是否放行。相比注解式的权限控制(比如@PreAuthorize),它更适合全局URL级别的统一控制,不用在每个Controller方法上重复配置。
可运行的代码示例
下面是完整的实现步骤,你可以直接基于自己的项目调整:
1. 创建自定义投票器(CustomOrderAccessVoter)
这个类负责实现你的核心条件判断逻辑:
import org.springframework.security.access.AccessDecisionVoter; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.core.Authentication; import org.springframework.security.web.FilterInvocation; import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest; import java.time.LocalTime; import java.util.Collection; @Component public class CustomOrderAccessVoter implements AccessDecisionVoter<Object> { // 目标拦截URL private static final String TARGET_ORDERS_URL = "/getAllOrders"; @Override public boolean supports(ConfigAttribute attribute) { // 支持所有配置属性,你也可以在这里过滤特定的权限标识 return true; } @Override public boolean supports(Class<?> clazz) { // 处理HTTP请求的FilterInvocation类型 return FilterInvocation.class.isAssignableFrom(clazz); } @Override public int vote(Authentication authentication, Object object, Collection<ConfigAttribute> attributes) { FilterInvocation filterInvocation = (FilterInvocation) object; HttpServletRequest request = filterInvocation.getRequest(); String requestUri = request.getRequestURI(); // 只对目标URL做校验,其他请求投弃权票 if (!TARGET_ORDERS_URL.equals(requestUri)) { return ACCESS_ABSTAIN; } // -------------------------- // 这里替换成你的自定义条件逻辑 // 示例条件:1. 用户账户处于活跃状态;2. 当前时间在工作时段(9:00-18:00) // -------------------------- // 1. 获取当前登录用户的自定义信息(假设你有CustomUserDetails实现了UserDetails) CustomUserDetails currentUser = (CustomUserDetails) authentication.getPrincipal(); boolean isUserActive = currentUser.isActive(); // 2. 判断当前时间是否在工作时段 LocalTime now = LocalTime.now(); boolean isInWorkingHours = now.isAfter(LocalTime.of(9, 0)) && now.isBefore(LocalTime.of(18, 0)); // 满足所有条件则允许访问 if (isUserActive && isInWorkingHours) { return ACCESS_GRANTED; } // 不满足条件则拒绝访问 return ACCESS_DENIED; } }
2. 配置Spring Security,将投票器加入访问决策链
接下来需要在Security配置中把这个投票器注册到访问决策管理器里,让它生效:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.access.AccessDecisionManager; import org.springframework.security.access.AccessDecisionVoter; import org.springframework.security.access.vote.AffirmativeBased; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.web.SecurityFilterChain; import java.util.List; @Configuration @EnableWebSecurity public class SecurityConfig { private final CustomOrderAccessVoter customOrderAccessVoter; // 构造注入自定义投票器 public SecurityConfig(CustomOrderAccessVoter customOrderAccessVoter) { this.customOrderAccessVoter = customOrderAccessVoter; } @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(auth -> auth // 对目标URL应用自定义访问决策逻辑 .requestMatchers("/getAllOrders").access(accessConfig -> accessConfig .decisionManager(accessDecisionManager()) ) // 其他URL的权限配置,根据你的需求调整 .anyRequest().authenticated() ) // 这里可以添加你的登录配置(比如表单登录、JWT等) .formLogin(form -> form.permitAll()); return http.build(); } @Bean public AccessDecisionManager accessDecisionManager() { // 使用AffirmativeBased策略:只要有一个投票器投同意,就允许访问 // 你也可以根据需求选择ConsensusBased(多数同意)或UnanimousBased(全票同意) List<AccessDecisionVoter<?>> voters = List.of(customOrderAccessVoter); return new AffirmativeBased(voters); } }
3. 补充说明
CustomUserDetails是你需要自己实现的UserDetails子类,用来封装用户的自定义属性(比如示例中的isActive()方法),你需要根据自己的用户实体来调整这部分代码。- 你可以完全替换
vote方法里的条件逻辑,比如判断用户所在部门、是否有特定标签、甚至请求中的参数值等等,只要是能通过Authentication或HttpServletRequest获取到的信息,都可以作为判断条件。 - 如果需要多个投票器共同生效,只需要把它们都加入到
accessDecisionManager()的voters列表中即可。
内容的提问来源于stack exchange,提问作者Kathiresa J
相关产品推荐
相关产品推荐

