You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Security自定义访问控制:拒绝访问时强制登录后继续请求

嘿,我来帮你搞定这个Spring Security自定义访问控制的需求!结合你给出的AccessManager,咱们一步步实现「满足条件就放行,不满足就跳登录、登录后回到原操作」的逻辑:

实现思路

核心逻辑是:在用户已认证的前提下,用自定义过滤器拦截请求,调用你的AccessManager检查条件;如果不满足,就保存当前请求的URL,重定向到登录页;用户登录成功后,Spring Security会自动跳回之前保存的请求路径,继续执行原操作。

步骤1:完善你的AccessManager

首先给AccessManager的方法添加HttpServletRequest参数,这样你可以获取当前请求的路径、参数,或者结合当前登录用户信息做判断:

@Component("accessManager")
public class AccessManager {
    public boolean hasAccess(HttpServletRequest request) {
        // 这里替换成你的实际判断逻辑
        // 示例:检查当前用户的会话状态、请求路径是否符合规则等
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        if (auth != null && auth.isAuthenticated()) {
            // 比如判断用户是否有特定属性,或者请求是否是特定接口
            return false;
        }
        return false;
    }
}
步骤2:编写自定义访问控制过滤器

创建一个OncePerRequestFilter的实现,集成AccessManager来处理访问控制逻辑,同时负责保存原请求、重定向登录:

@Component
public class CustomAccessControlFilter extends OncePerRequestFilter {

    @Autowired
    private AccessManager accessManager;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        // 排除登录页、静态资源等无需检查的路径,避免循环重定向
        String path = request.getServletPath();
        if (path.startsWith("/login") || path.startsWith("/css/") || path.startsWith("/js/")) {
            filterChain.doFilter(request, response);
            return;
        }

        // 先检查用户是否已登录,未登录直接交给Spring Security处理跳转登录
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        if (auth == null || !auth.isAuthenticated() || "anonymousUser".equals(auth.getPrincipal())) {
            response.sendRedirect(request.getContextPath() + "/login");
            return;
        }

        // 调用自定义AccessManager检查条件
        if (!accessManager.hasAccess(request)) {
            // 保存当前请求到Session,Spring Security登录成功后会自动跳回这个请求
            request.getSession().setAttribute("SPRING_SECURITY_SAVED_REQUEST", new DefaultSavedRequest(request));
            // 重定向到登录页
            response.sendRedirect(request.getContextPath() + "/login");
            return;
        }

        // 满足条件,继续执行后续请求链
        filterChain.doFilter(request, response);
    }
}
步骤3:配置Spring Security过滤链

把自定义过滤器加到Spring Security的过滤链中,同时配置登录页、登录成功跳转逻辑:

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Autowired
    private CustomAccessControlFilter customAccessControlFilter;

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(auth -> auth
                // 允许登录页和静态资源匿名访问
                .requestMatchers("/login", "/css/**", "/js/**").permitAll()
                // 其他所有请求需要先认证
                .anyRequest().authenticated()
            )
            .formLogin(form -> form
                .loginPage("/login") // 配置你的自定义登录页路径
                .permitAll()
                // 登录成功后,Spring Security会优先跳回之前保存的请求,没有的话跳默认页
                .defaultSuccessUrl("/")
            )
            .logout(logout -> logout
                .permitAll()
            )
            // 把自定义过滤器加到UsernamePasswordAuthenticationFilter之前,确保登录后先检查我们的规则
            .addFilterBefore(customAccessControlFilter, UsernamePasswordAuthenticationFilter.class);

        return http.build();
    }
}
可选:方法级安全集成

如果你想在Controller方法上更灵活地使用这个规则,可以配置方法级安全,用@PreAuthorize调用你的AccessManager:

// 在SecurityConfig里添加这个Bean
@Bean
public MethodSecurityExpressionHandler methodSecurityExpressionHandler() {
    DefaultMethodSecurityExpressionHandler handler = new DefaultMethodSecurityExpressionHandler();
    // 把AccessManager注册到表达式处理器中
    handler.setPermissionEvaluator(new CustomPermissionEvaluator(accessManager));
    return handler;
}

// 自定义PermissionEvaluator,用于衔接方法级注解和AccessManager
static class CustomPermissionEvaluator implements PermissionEvaluator {
    private final AccessManager accessManager;

    public CustomPermissionEvaluator(AccessManager accessManager) {
        this.accessManager = accessManager;
    }

    @Override
    public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) {
        // 获取当前请求
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        return accessManager.hasAccess(request);
    }

    @Override
    public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
        // 按需实现基于资源ID的权限判断,不需要可以返回false或抛出异常
        return false;
    }
}

然后在Controller方法上使用:

@PreAuthorize("hasPermission(null, null)")
@GetMapping("/your-protected-path")
public String protectedPage() {
    return "protected";
}
关键细节提醒
  • 保存原请求:我们用SPRING_SECURITY_SAVED_REQUEST这个Session属性,Spring Security的默认登录成功处理器会自动读取它,跳回用户之前想要访问的页面,不用自己额外处理跳转逻辑。
  • 过滤器顺序:把自定义过滤器放在UsernamePasswordAuthenticationFilter之前,确保用户登录后,先经过我们的自定义规则检查,再执行后续的授权逻辑。
  • 排除路径:一定要排除登录页和静态资源,否则会出现「检查不满足→跳登录→再检查→再跳登录」的循环。

内容的提问来源于stack exchange,提问作者Sviatlana

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 06:32:46