0%

过滤web请求

Spring Security 过滤 Web 请求全解析:从过滤器链到请求授权配置

Spring Security 对 Web 请求的过滤核心是 “代理过滤器 + 链式过滤” 机制:通过 DelegatingFilterProxy 衔接 Servlet 容器与 Spring 容器,再由 FilterChainProxy 管理具体的安全过滤链,最终通过 HttpSecurity 配置请求拦截规则。从 “过滤器链原理→自动注册机制→核心配置→请求授权规则” 四个维度,彻底讲透 Spring Security 如何过滤和保护 Web 请求。

核心过滤器链:DelegatingFilterProxy 与 FilterChainProxy

Spring Security 的过滤能力依赖两层代理机制,解决了 “Servlet Filter 由容器管理,而 Spring Security Filter 是 Spring Bean” 的协同问题。

1. DelegatingFilterProxy:Servlet Filter 与 Spring Bean 的桥梁

DelegatingFilterProxy 是 Servlet 规范中的 Filter 实现,但它本身不处理请求,仅负责将请求委托给 Spring 容器中的 FilterChainProxy Bean(默认 Bean 名为 springSecurityFilterChain)。

核心作用
  • 解耦容器与 Spring:Servlet 容器(如 Tomcat)只能识别并管理 Filter 实例,而 Spring Security 的过滤逻辑封装在 Spring Bean 中,DelegatingFilterProxy 作为中间代理,实现两者通信;
  • 延迟查找目标 Bean:初始化时不直接依赖 FilterChainProxy,而是在请求到来时从 Spring 容器中查找,支持 Spring Bean 的懒加载。
自动注册机制(AbstractSecurityWebApplicationInitializer)

用户提供的代码中,SecurityWebApplicationInitializer 继承 AbstractSecurityWebApplicationInitializer,Spring 会自动检测该类,并在 Servlet 容器启动时注册 DelegatingFilterProxy

1
2
3
4
5
6
7
8
// AbstractSecurityWebApplicationInitializer 核心逻辑
private void insertSpringSecurityFilterChain(ServletContext servletContext) {
String filterName = DEFAULT_FILTER_NAME; // 固定为 "springSecurityFilterChain"
// 创建 DelegatingFilterProxy,指定委托目标为 "springSecurityFilterChain"
DelegatingFilterProxy springSecurityFilterChain = new DelegatingFilterProxy(filterName);
// 注册到 Servlet 容器,拦截所有请求(默认 /*)
registerFilter(servletContext, true, filterName, springSecurityFilterChain);
}
  • 无需手动配置 web.xml:即使不写 web.xml,Spring 也会通过 WebApplicationInitializer 接口自动完成 Filter 注册,这是 Spring 3.1+ 推荐的无 XML 配置方式。

2. FilterChainProxy:Spring Security 的过滤链核心

springSecurityFilterChain 对应的实际类型是 FilterChainProxy,它是 Spring Security 内部的 “过滤链管理器”,本身是一个 Filter,但内部管理多个安全过滤链(如登录过滤链、授权过滤链、CSRF 过滤链等)。

核心特性
  • 多链匹配:根据请求 URL 匹配不同的过滤链(例如,对 /api/**/admin/** 应用不同的安全规则);
  • 集中管理:所有 Spring Security 的 Filter(如 UsernamePasswordAuthenticationFilterFilterSecurityInterceptor)都由它统一调度;
  • 与 Spring 集成:作为 Spring Bean,可依赖注入其他 Spring 组件(如 AuthenticationManager)。
如何被创建?(@EnableWebSecurity 的作用)

@EnableWebSecurity 注解会触发 WebSecurityConfiguration 配置类,其中会创建 FilterChainProxy 的 Bean:

1
2
3
4
5
6
7
8
// WebSecurityConfiguration 核心代码
@Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
public Filter springSecurityFilterChain() throws Exception {
// 构建 WebSecurity,整合所有 WebSecurityConfigurer 配置
WebSecurity web = this.webSecurityBuilder.build();
// 生成 FilterChainProxy
return web.getFilterChainProxy();
}
  • @EnableWebSecurity 是开关:只有添加该注解,才会自动创建 FilterChainProxy,开启 Spring Security 的 Web 安全功能。

核心配置类:WebSecurityConfigurerAdapter

继承 WebSecurityConfigurerAdapter 是自定义 Spring Security 规则的主要方式,它提供三个核心 configure 方法,分别对应认证管理Web 资源忽略请求授权

方法 1:configure (AuthenticationManagerBuilder auth) —— 配置认证来源

该方法用于定义 “如何获取用户信息”,是认证的基础,支持内存用户、数据库用户(自定义 UserDetailsService)等多种来源。

(1)内存用户(测试用)
1
2
3
4
5
6
7
8
9
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 内存中创建测试用户,指定用户名、密码、角色和权限
auth.inMemoryAuthentication()
.withUser("tom") // 用户名
.password("{noop}123456") // 密码:{noop} 表示不加密(仅测试用)
.roles("ADMIN") // 角色(会自动拼接 ROLE_ 前缀,实际权限为 ROLE_ADMIN)
.authorities("UPDATE"); // 直接指定权限(无前缀)
}
  • 密码加密注意:生产环境必须使用PasswordEncoder(如 BCrypt),不能用{noop},示例如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // 配置 BCrypt 密码加密
    @Bean
    public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.inMemoryAuthentication()
    .withUser("tom")
    .password(passwordEncoder().encode("123456")) // 加密密码
    .roles("ADMIN");
    }
(2)数据库用户(生产用)

通过自定义 UserDetailsService 从数据库获取用户信息,这是生产环境的标准用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// 1. 自定义 UserDetailsService(从数据库查询用户)
@Service
public class CustomUserDetailsService implements UserDetailsService {
@Autowired
private AdminDao adminDao;

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 1. 从数据库查询用户
Admin admin = adminDao.findByName(username);
if (admin == null) {
throw new UsernameNotFoundException("用户不存在:" + username);
}

// 2. 构建用户权限(角色需加 ROLE_ 前缀)
List<GrantedAuthority> authorities = Arrays.asList(
new SimpleGrantedAuthority("ROLE_ADMIN"), // 角色
new SimpleGrantedAuthority("USER_UPDATE") // 具体权限
);

// 3. 返回 Spring Security 标准的 UserDetails 对象
return new org.springframework.security.core.userdetails.User(
admin.getUsername(),
admin.getPassword(), // 数据库中存储的加密后密码
admin.isEnabled(), // 账户是否启用
true, true, true, // 账户未过期、凭证未过期、账户未锁定
authorities
);
}
}

// 2. 在 configure 中配置自定义 UserDetailsService
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 配置用户来源为自定义 UserDetailsService
auth.userDetailsService(customUserDetailsService)
.passwordEncoder(passwordEncoder()); // 配置密码加密器
}

方法 2:configure (WebSecurity web) —— 忽略静态资源

该方法用于配置 “无需安全过滤的资源”(如静态文件、Swagger 文档),避免 Spring Security 拦截这些请求,提升性能。

示例:忽略静态资源和 Swagger 路径
1
2
3
4
5
6
7
8
@Override
public void configure(WebSecurity web) throws Exception {
// 忽略静态资源:/css/**、/js/**、/images/**
web.ignoring()
.antMatchers("/css/**", "/js/**", "/images/**")
// 忽略 Swagger 文档路径
.antMatchers("/swagger-ui/**", "/v3/api-docs/**");
}
  • 注意区别WebSecurity.ignoring() 是 “完全忽略”,这些请求不会经过任何 Spring Security Filter;而 HttpSecurity.authorizeRequests().permitAll() 是 “允许匿名访问”,请求仍会经过过滤链,但无需认证。

方法 3:configure (HttpSecurity http) —— 配置请求授权与登录退出

这是最核心的配置方法,用于定义 “哪些请求需要保护”“如何登录”“如何退出”“异常处理” 等规则,用户提供的代码中包含大量配置样例,下面逐一解析核心配置项。

(1)登录配置(formLogin ())

配置自定义登录页面、登录参数、登录成功 / 失败跳转:

1
2
3
4
5
6
7
8
9
http.formLogin()
.loginPage("/login.html") // 自定义登录页面路径(需自己开发 login.html)
.usernameParameter("username") // 登录表单中用户名的参数名(默认是 "username")
.passwordParameter("password") // 登录表单中密码的参数名(默认是 "password")
.loginProcessingUrl("/user/login") // 登录请求的提交路径(表单的 action 属性值)
.defaultSuccessUrl("/index") // 登录成功后默认跳转路径(未指定目标URL时)
.successForwardUrl("/home") // 登录成功后强制跳转路径(无论之前请求什么URL)
.failureUrl("/login.html?error") // 登录失败后跳转路径
.permitAll(); // 允许匿名访问登录相关路径(必须配置,否则登录页面也需要认证)
  • 关键注意loginPage("/login.html") 会改变默认的登录相关路径,需确保 /login.html/user/login 这些路径允许匿名访问(通过 permitAll())。
(2)退出配置(logout ())

配置退出登录的路径、成功跳转和资源清理:

1
2
3
4
5
6
http.logout()
.logoutUrl("/logout") // 退出登录的请求路径(默认是 "/logout")
.logoutSuccessUrl("/login.html?logout") // 退出成功后跳转路径
.invalidateHttpSession(true) // 退出时销毁当前会话(默认 true)
.deleteCookies("JSESSIONID") // 退出时删除指定 Cookie(如会话 Cookie)
.permitAll(); // 允许匿名访问退出路径
(3)请求授权规则(authorizeRequests ())

定义不同请求路径的访问权限,规则匹配按顺序执行,更具体的规则需放在前面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
http.authorizeRequests()
// 1. 允许匿名访问的路径(首页、静态资源)
.antMatchers("/", "/test/**", "/login.html").permitAll()
// 2. 需要 "admin" 权限才能访问
.antMatchers("/admin/**").hasAuthority("admin")
// 3. 需要 "admin" 或 "teacher" 权限之一
.antMatchers("/teacher/**").hasAnyAuthority("admin", "teacher")
// 4. 需要 "ROLE_ADMIN" 角色(自动拼接 ROLE_ 前缀)
.antMatchers("/super/**").hasRole("admin")
// 5. 需要指定 IP 才能访问(如本地 IP)
.antMatchers("/ip/**").hasIpAddress("127.0.0.1")
// 6. 仅允许通过 "记住我" 认证的用户访问
.antMatchers("/remember/**").rememberMe()
// 7. 拒绝所有访问(通常用于测试或特殊路径)
.antMatchers("/deny/**").denyAll()
// 8. 其他所有请求都需要认证(必须放在最后)
.anyRequest().authenticated();
权限规则方法 作用 示例
permitAll() 允许匿名访问 .antMatchers("/login").permitAll()
authenticated() 需已认证(登录) .anyRequest().authenticated()
hasAuthority() 需具备指定权限(无前缀) .hasAuthority("USER_UPDATE")
hasAnyAuthority() 需具备任意一个指定权限 .hasAnyAuthority("ADMIN", "USER")
hasRole() 需具备指定角色(自动加 ROLE_ 前缀) .hasRole("admin") → 匹配 ROLE_ADMIN
hasAnyRole() 需具备任意一个指定角色 .hasAnyRole("admin", "teacher")
hasIpAddress() 需来自指定 IP 或 IP 段 .hasIpAddress("192.168.1.0/24")
rememberMe() 需通过 “记住我” 认证 .antMatchers("/remember").rememberMe()
denyAll() 拒绝所有访问 .antMatchers("/secret").denyAll()
(4)其他常用配置
  • CSRF 保护:默认开启,防止跨站请求伪造,前后端分离项目可关闭:

    1
    http.csrf().disable(); // 关闭 CSRF 保护(前后端分离常用)
  • 记住我(Remember Me):基于 Cookie 存储认证信息,无需每次登录:

    1
    2
    3
    4
    http.rememberMe()
    .tokenValiditySeconds(3600) // Cookie 有效期(秒)
    .key("my-secret-key") // 加密 Cookie 的密钥(生产环境需复杂密钥)
    .userDetailsService(customUserDetailsService); // 用于加载用户信息
  • 异常处理:配置无权限访问时的跳转页面:

    1
    2
    http.exceptionHandling()
    .accessDeniedPage("/unauth.html"); // 403 无权限时跳转的页面
  • HTTPS 强制:要求所有请求通过 HTTPS 访问:

    1
    2
    http.requiresChannel()
    .anyRequest().requiresSecure(); // 所有请求强制 HTTPS

Web 请求过滤的完整流程

结合上述组件,一个 Web 请求的安全过滤流程如下:

  1. 请求到达 Servlet 容器:浏览器发送请求(如 /admin/user);
  2. DelegatingFilterProxy 拦截:转发请求给 Spring 容器中的 FilterChainProxy
  3. FilterChainProxy 匹配过滤链:根据请求路径(/admin/**)匹配对应的安全过滤链;
  4. 执行过滤链中的 Filter:
    • 登录相关 Filter(如 UsernamePasswordAuthenticationFilter):处理登录请求;
    • 授权 Filter(如 FilterSecurityInterceptor):检查请求是否有权限;
    • CSRF Filter(如 CsrfFilter):验证 CSRF Token;
  5. 权限校验FilterSecurityInterceptor 调用 AuthenticationManager 校验用户权限,通过则放行请求,否则返回 403 或跳转登录页;
  6. 请求到达 Controller:安全校验通过后,请求最终转发到 Spring MVC 的 Controller 处理。

关键注意事项

  1. 规则匹配顺序authorizeRequests() 中的规则按代码顺序执行,更具体的规则必须放在前面,否则会被后面的通用规则覆盖(例如,/admin/** 需放在 anyRequest() 之前);
  2. 密码加密:生产环境必须使用 PasswordEncoder(如 BCrypt、Argon2),禁止使用 {noop} 明文存储;
  3. 静态资源忽略:通过 WebSecurity.ignoring() 忽略静态资源,避免过滤链处理静态文件,提升性能;
  4. 前后端分离适配:前后端分离项目需关闭 CSRF(csrf().disable()),并配置 loginProcessingUrl 为接口路径(如 /api/login),返回 JSON 结果而非跳转页面。

总结

Spring Security 过滤 Web 请求的核心是 “代理转发 + 链式规则”:

  • DelegatingFilterProxy:衔接 Servlet 容器与 Spring 容器,解决 Filter 管理边界问题;
  • FilterChainProxy:管理内部安全过滤链,按请求匹配执行对应的安全逻辑;
  • WebSecurityConfigurerAdapter:通过三个 configure 方法,分别配置认证来源、资源忽略、请求授权,实现灵活的安全规则定义

欢迎关注我的其它发布渠道