如何配置Spring Security的SecurityFilterChain实现默认禁止所有端点,仅通过方法注解控制访问权限?
如何配置Spring Security的SecurityFilterChain实现默认禁止所有端点,仅通过方法注解控制访问权限?
哈哈,这个问题我之前也踩过一模一样的坑!你遇到的核心问题其实是Spring Security过滤器链的授权规则优先级比方法级注解高——要么过滤器链直接把所有请求拒了,方法注解根本没机会生效;要么全放开导致未注解的端点暴露,完全违背了“默认禁止”的初衷。
别着急,我给你一套靠谱的解决方案,结合方法级安全的默认规则和过滤器链配置就能完美实现需求:
核心思路
要实现「默认禁止所有端点,仅通过@PreAuthorize等方法注解开放权限」,需要两步配合:
- 给所有未加注解的控制器方法设置默认拒绝的规则;
- 让过滤器链允许请求到达控制器,把授权判断交给方法级安全拦截器处理。
完整代码实现
1. 安全配置类WebSecurityConfig
这里关键是用@EnableMethodSecurity的defaultPreAuthorize属性设置全局默认规则,同时过滤器链只做基础的通行配置:
@EnableWebSecurity // 启用方法级安全,并且给所有未加@PreAuthorize的方法默认设置denyAll() @EnableMethodSecurity(prePostEnabled = true, defaultPreAuthorize = "denyAll()") public class WebSecurityConfig { @Bean public SecurityFilterChain mainFilterChain(HttpSecurity http) throws Exception { return http .authorizeHttpRequests(authorize -> authorize // 允许请求到达控制器,让方法注解处理授权逻辑 // 如果有静态资源(如css/js)需要开放,在这里单独配置: // .mvcMatchers("/css/**", "/js/**").permitAll() .anyRequest().authenticated() ) // 配置表单登录,登录页面直接放开匿名访问 .formLogin(form -> form .loginPage("/login") .permitAll() ) .build(); } // 示例:内存用户认证(实际项目换成数据库认证即可) @Bean public UserDetailsService userDetailsService() { UserDetails user = User.withUsername("user") .password("{noop}password") // {noop}表示不加密,仅测试用 .roles("USER") .build(); return new InMemoryUserDetailsManager(user); } }
2. 控制器类(保持你的原有代码即可)
只需要在需要开放的方法上添加@PreAuthorize注解,未注解的方法会自动被默认规则拒绝:
@Controller @RequestMapping public class MainController { // 允许所有人访问,覆盖默认的denyAll() @GetMapping @PreAuthorize("permitAll()") public String index() { return "index"; } // 仅允许匿名用户访问(登录页面) @GetMapping("/login") @PreAuthorize("isAnonymous()") public String login() { return "login"; } // 仅允许拥有ROLE_USER权限的登录用户访问 @GetMapping("/private") @PreAuthorize("hasAuthority('ROLE_USER')") public String secrets() { return "private"; } // 这个端点没有加注解,会自动应用默认的denyAll(),任何人都无法访问 @GetMapping("/unprotected") public String unprotected() { return "unprotected"; } }
为什么这样能生效?
@EnableMethodSecurity(defaultPreAuthorize = "denyAll()")会自动给所有未标注@PreAuthorize的控制器方法加上默认授权规则,相当于给每个未注解的方法都加了@PreAuthorize("denyAll()");- 过滤器链中的
anyRequest().authenticated()确保只有认证用户能进入控制器,但方法上的@PreAuthorize可以轻松覆盖这个规则(比如permitAll()允许所有人,isAnonymous()允许匿名); - 登录页面通过
formLogin().permitAll()直接放开,避免默认规则影响,你也可以改用@PreAuthorize("isAnonymous()")来控制,效果完全一样。
额外小提示
如果不想在过滤器链中设置authenticated(),也可以改成anyRequest().permitAll()——这样所有请求都能到达控制器,完全由方法级的默认规则和注解来控制权限,效果是一致的,只是过滤器链层面不做认证检查而已。
备注:内容来源于stack exchange,提问作者Slevin




