5.过滤器

在现代 Web 应用程序中,安全性是一个至关重要的环节。Spring Security 是一个功能强大的安全框架,专注于为 Java 应用提供全面的安全解决方案。Spring Security 的核心之一是过滤器(Filter),它负责处理 HTTP 请求和响应,确保安全规则得以应用。在这篇文章中,我们将深入探讨 Spring Security 的过滤器机制,从基础概念到高级实现,逐步解析其背后的工作原理和配置方法。

1. 什么是过滤器?

过滤器(Filter)是处理 HTTP 请求和响应的一个组件。它可以对请求进行预处理(如认证、授权、日志记录),也可以对响应进行后处理(如压缩、加密)。在 Java EE 中,过滤器是基于 Servlet API 的,可以通过 javax.servlet.Filter 接口来实现。

过滤器的主要功能包括:

  • 拦截和修改请求和响应
  • 执行安全检查
  • 记录请求日志
  • 进行请求参数验证

2. Spring Security 过滤器链

过滤器链的工作原理

Spring Security 通过一个过滤器链来处理 HTTP 请求。过滤器链由多个过滤器组成,每个过滤器负责处理请求的某个方面。例如,一个过滤器可能负责认证用户,另一个过滤器可能负责检查用户是否有访问某个资源的权限。

过滤器链的工作原理如下:

  1. HTTP 请求进入过滤器链的第一个过滤器。
  2. 每个过滤器对请求进行处理,然后将请求传递给链中的下一个过滤器。
  3. 最后一个过滤器处理完请求后,将请求传递给实际的目标资源(如 Servlet 或 Controller)。
  4. 目标资源生成响应,过滤器链中的每个过滤器可以对响应进行处理,然后将响应传递给链中的上一个过滤器。
  5. 最后一个过滤器将最终的响应返回给客户端。

默认过滤器列表

Spring Security 提供了一组默认的过滤器,每个过滤器都有特定的功能。这些过滤器按顺序排列,形成一个过滤器链。以下是一些常见的 Spring Security 默认过滤器:

  1. SecurityContextPersistenceFilter
  2. LogoutFilter
  3. UsernamePasswordAuthenticationFilter
  4. BasicAuthenticationFilter
  5. CsrfFilter
  6. ExceptionTranslationFilter
  7. FilterSecurityInterceptor

3. 常见的 Spring Security 过滤器

SecurityContextPersistenceFilter

SecurityContextPersistenceFilter 是 Spring Security 过滤器链中的第一个过滤器。它负责从请求中恢复 SecurityContext,并在请求处理完成后清理 SecurityContextSecurityContext 持有当前认证用户的信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class SecurityContextPersistenceFilter extends GenericFilterBean {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;

// 从请求中恢复 SecurityContext
SecurityContext contextBeforeChainExecution = loadContext(httpRequest);

try {
// 将 SecurityContext 设置到 SecurityContextHolder
SecurityContextHolder.setContext(contextBeforeChainExecution);

// 执行过滤器链
chain.doFilter(request, response);
} finally {
// 清理 SecurityContext
SecurityContextHolder.clearContext();
}
}
}

UsernamePasswordAuthenticationFilter

UsernamePasswordAuthenticationFilter 负责处理基于用户名和密码的表单登录认证。它通常拦截 /login 请求,验证用户凭证,并创建一个 Authentication 对象。

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
public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {

@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
String username = obtainUsername(request);
String password = obtainPassword(request);

if (username == null) {
username = "";
}

if (password == null) {
password = "";
}

username = username.trim();

UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);

// 允许子类设置详细信息
setDetails(request, authRequest);

return this.getAuthenticationManager().authenticate(authRequest);
}
}

BasicAuthenticationFilter

BasicAuthenticationFilter 负责处理 HTTP Basic 认证。它从请求头中提取用户名和密码,并进行认证。

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
public class BasicAuthenticationFilter extends OncePerRequestFilter {

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
String header = request.getHeader("Authorization");

if (header == null || !header.startsWith("Basic ")) {
chain.doFilter(request, response);
return;
}

try {
String[] tokens = extractAndDecodeHeader(header, request);
assert tokens.length == 2;

String username = tokens[0];

// 根据用户名和密码进行认证
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, tokens[1]);

Authentication authResult = this.getAuthenticationManager().authenticate(authRequest);

// 将认证结果设置到 SecurityContext
SecurityContextHolder.getContext().setAuthentication(authResult);
} catch (AuthenticationException failed) {
// 处理认证失败
SecurityContextHolder.clearContext();
this.authenticationEntryPoint.commence(request, response, failed);
return;
}

chain.doFilter(request, response);
}
}

CsrfFilter

CsrfFilter 负责处理跨站点请求伪造(CSRF)攻击。它生成和验证 CSRF 令牌,确保请求的合法性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class CsrfFilter extends OncePerRequestFilter {

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
CsrfToken csrfToken = (CsrfToken) request.getAttribute(CsrfToken.class.getName());

if (csrfToken != null) {
// 验证 CSRF 令牌
String actualToken = request.getHeader(csrfToken.getHeaderName());

if (actualToken == null) {
actualToken = request.getParameter(csrfToken.getParameterName());
}

if (!csrfToken.getToken().equals(actualToken)) {
throw new InvalidCsrfTokenException(csrfToken, actualToken);
}
}

filterChain.doFilter(request, response);
}
}

ExceptionTranslationFilter

ExceptionTranslationFilter 负责捕获过滤器链中的任何异常,并将其转换为适当的 HTTP 响应。例如,当用户未认证时,可以将异常转换为 401 Unauthorized 响应。

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
public class ExceptionTranslationFilter extends GenericFilterBean {

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
try {
chain.doFilter(request, response);
} catch (AuthenticationException ex) {
// 处理认证异常
commenceAuthentication((HttpServletRequest) request, (HttpServletResponse) response, ex);
} catch (AccessDeniedException ex) {
// 处理访问拒绝异常
handleAccessDenied((HttpServletRequest) request, (HttpServletResponse) response, ex);
}
}

private void commenceAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException ex) throws IOException, ServletException {
// 将异常转换为 401 Unauthorized 响应
SecurityContextHolder.clearContext();
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, ex.getMessage());
}

private void handleAccessDenied(HttpServletRequest request, HttpServletResponse response, AccessDeniedException ex) throws IOException, ServletException {
// 将异常转换为 403 Forbidden 响应
response.sendError(HttpServletResponse.SC_FORBIDDEN, ex.getMessage());
}
}

FilterSecurityInterceptor

FilterSecurityInterceptor 是过滤器链中的最后一个过滤器,负责执行最终的访问控制决策。它检查用户是否有权限访问请求的资源。

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
public class FilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
FilterInvocation fi = new FilterInvocation(request, response, chain);
invoke(fi);
}

public void invoke(FilterInvocation fi) throws IOException, ServletException {
if (fi.getRequest() != null && fi.getRequest().getMethod().equals("OPTIONS"))
{
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
return;
}

// 执行访问控制决策
InterceptorStatusToken token = super.beforeInvocation(fi);

try {
// 执行过滤器链中的下一个过滤器
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
} finally {
super.afterInvocation(token, null);
}
}
}

4. 自定义过滤器

创建自定义过滤器

开发者可以根据需要创建自定义过滤器,处理特定的安全需求。自定义过滤器需要实现 javax.servlet.Filter 接口或扩展 Spring Security 提供的过滤器基类。

示例:创建一个记录请求日志的自定义过滤器

1
2
3
4
5
6
7
8
9
10
11
12
public class RequestLoggingFilter extends GenericFilterBean {

private static final Logger logger = LoggerFactory.getLogger(RequestLoggingFilter.class);

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
logger.info("Incoming request: {} {}", httpRequest.getMethod(), httpRequest.getRequestURI());

chain.doFilter(request, response);
}
}

注册自定义过滤器

创建自定义过滤器后,需要将其注册到 Spring Security 的过滤器链中。可以通过扩展 WebSecurityConfigurerAdapter 并在配置中添加自定义过滤器。

示例:注册自定义过滤器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Autowired
private RequestLoggingFilter requestLoggingFilter;

@Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterBefore(requestLoggingFilter, UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.permitAll();
}
}

在这个示例中,我们将 RequestLoggingFilter 添加到 UsernamePasswordAuthenticationFilter 之前。

5. 高级过滤器配置

过滤器链的自定义

Spring Security 允许开发者自定义过滤器链,添加、移除或替换默认过滤器。通过扩展 WebSecurityConfigurerAdapter 并在配置中自定义过滤器链,可以实现特定的安全需求。

示例:自定义过滤器链

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterAt(new CustomFilter(), UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.permitAll();
}
}

多个过滤器链的配置

在某些复杂的应用中,可能需要配置多个过滤器链。Spring Security 允许为不同的 URL 模式配置不同的过滤器链。

示例:配置多个过滤器链

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/api/**")
.authorizeRequests()
.anyRequest().hasRole("API_USER")
.and()
.httpBasic();

http.antMatcher("/admin/**")
.authorizeRequests()
.anyRequest().hasRole("ADMIN")
.and()
.formLogin()
.loginPage("/admin/login")
.permitAll()
.and()
.logout()
.permitAll();
}
}

在这个示例中,我们为 /api/**/admin/** 配置了不同的过滤器链。

条件过滤器

有时需要根据特定条件启用或禁用过滤器。可以在自定义过滤器中添加条件逻辑,根据请求的属性或其他因素决定是否处理请求。

示例:条件过滤器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class ConditionalFilter extends GenericFilterBean {

private boolean condition;

public ConditionalFilter(boolean condition) {
this.condition = condition;
}

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
if (condition) {
// 执行过滤逻辑
HttpServletRequest httpRequest = (HttpServletRequest) request;
logger.info("Conditional filter applied: {} {}", httpRequest.getMethod(), httpRequest.getRequestURI());
}

chain.doFilter(request, response);
}
}

在配置中使用条件过滤器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Override
protected void configure(HttpSecurity http) throws Exception {
boolean condition = true; // 根据实际需求设置条件
http.addFilterBefore(new ConditionalFilter(condition), UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.permitAll();
}
}

6. 实战案例分析

案例 1:保护 REST API 的过滤器链

一个 REST API 需要实现基于 JWT 的认证和授权。通过使用 Spring Security,可以配置一个过滤器链来处理 JWT 认证和授权。

需求

  1. 使用 JWT 进行认证。
  2. 保护 API 端点,只允许经过认证的用户访问。

解决方案

使用自定义的 JWT 过滤器处理认证,将其添加到过滤器链中。

JWT 过滤器实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class JwtAuthenticationFilter extends OncePerRequestFilter {

@Autowired
private JwtTokenProvider tokenProvider;

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String token = tokenProvider.resolveToken(request);

if (token != null && tokenProvider.validateToken(token)) {
Authentication auth = tokenProvider.getAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(auth);
}

filterChain.doFilter(request, response);
}
}

JWT 令牌提供者实现

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
@Service
public class JwtTokenProvider {

@Value("${jwt.secret}")
private String secretKey;

@Value("${jwt.expiration}")
private long validityInMilliseconds;

public String createToken(String username, List<String> roles) {
Claims claims = Jwts.claims().setSubject(username);
claims.put("roles", roles);

Date now = new Date();
Date validity = new Date(now.getTime() + validityInMilliseconds);

return Jwts.builder()
.setClaims(claims)
.setIssuedAt(now)
.setExpiration(validity)
.signWith(SignatureAlgorithm.HS256, secretKey)
.compact();
}

public String resolveToken(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
}

public boolean validateToken(String token) {
try {
Jws<Claims> claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token);
return !claims.getBody().getExpiration().before(new Date());
} catch (JwtException | IllegalArgumentException e) {
return false;
}
}

public Authentication getAuthentication(String token) {
UserDetails userDetails = User.withUsername(getUsername(token)).password("").authorities(getRoles(token)).build();
return new UsernamePasswordAuthenticationToken(userDetails, "", userDetails.getAuthorities());
}

public String getUsername(String token) {
return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody().getSubject();
}

public List<String> getRoles(String token) {
Claims claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody();
return (List<String>) claims.get("roles");
}
}

配置 JWT 过滤器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Autowired
private JwtAuthenticationFilter jwtAuthenticationFilter;

@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()
.antMatchers("/api/auth/**").permitAll()
.anyRequest().authenticated();
}
}

在这个案例中,我们实现了一个基于 JWT 的 REST API 认证和授权机制,通过自定义过滤器处理 JWT 令牌。

案例 2:电子商务网站的安全过滤器

一个电子商务网站需要保护用户数据和交易信息,防止常见的安全攻击。通过使用 Spring Security,可以配置一组过滤器来处理认证和授权。

需求

  1. 保护用户数据和交易信息。
  2. 实现基于角色的访问控制。
  3. 防止 CSRF 攻击。

解决方案

使用默认的 Spring Security 过滤器,并添加自定义的日志记录过滤器。

配置示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Autowired
private RequestLoggingFilter requestLoggingFilter;

@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().and()
.addFilterBefore(requestLoggingFilter, UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()
.antMatchers("/checkout/**").hasRole("USER")
.antMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.permitAll();
}
}

在这个案例中,我们通过配置 Spring Security 过滤器保护电子商务网站的用户数据和交易信息,并防止 CSRF 攻击。

7. Spring Security 过滤器的最佳实践

使用适当的过滤器顺序

确保过滤器按照正确的顺序执行,以避免安全漏洞。可以使用 addFilterBeforeaddFilterAfter 方法将自定义过滤器插入到适当的位置。

实现详细的日志记录

实现详细的日志记录,监控和审计请求和响应,及时发现和响应安全事件。

1
2
3
4
@Bean
public LoggerListener loggerListener() {
return new LoggerListener();
}

防止 CSRF 攻击

启用 CSRF 保护,防止跨站点请求伪造攻击。Spring Security 默认启用 CSRF 保护,开发者可以通过配置 CsrfTokenRepository 来自定义 CSRF 令牌存储和验证逻辑。

1
2
3
4
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
}

使用 HTTPS 加密通信

启用 HTTPS 加密通信,确保数据在传输过程中不被窃取或篡改。通过配置 Spring Security 的通道安全功能,可以强制所有请求使用 HTTPS。

1
2
3
4
5
6
@Override
protected void configure(HttpSecurity http) throws Exception {
http.requiresChannel()
.anyRequest()
.requiresSecure();
}

定期审查过滤器配置

定期审查过滤器配置,确保其符合最新的安全要求。特别是在应用程序发生重大变化时,重新审查过滤器链中的每个过滤器及其配置。

8. 总结

Spring Security 是一个功能强大且灵活的安全框架,提供了全面的过滤器机制。通过本文的深入介绍,我们探讨了 Spring Security 的各种过滤器机制,包括默认过滤器、自定义过滤器和高级过滤器配置。我们还通过实战案例展示了 Spring Security 过滤器在实际应用中的应用。

通过遵循最佳实践,开发者可以充分利用 Spring Security 的强大功能,为应用构建坚实的安全保护,确保请求和响应的安全性和可靠性。

开始使用 Spring Security 吧,为你的应用程序构筑坚不可摧的安全过滤器链,确保系统资源的安全性和可靠性!