Spring Security:如何定制Filter Chain实现微服务中的细粒度鉴权

Spring Security:定制 Filter Chain 实现微服务中的细粒度鉴权

大家好,今天我们来深入探讨 Spring Security 如何定制 Filter Chain,以实现微服务架构下的细粒度鉴权。在微服务环境中,服务数量众多,安全需求复杂,传统的集中式鉴权方式往往难以满足。我们需要更灵活、更精细的鉴权机制,才能确保微服务之间安全可靠的交互。

1. 微服务鉴权的挑战

在深入定制 Filter Chain 之前,我们需要了解微服务鉴权面临的挑战:

  • 服务数量庞大: 微服务架构下,服务数量众多,每个服务都有不同的安全需求。
  • 鉴权逻辑复杂: 不同的服务可能需要基于角色、权限、资源等多种因素进行鉴权。
  • 单点故障风险: 传统的集中式鉴权方式容易出现单点故障,影响整个系统的可用性。
  • 性能瓶颈: 集中式鉴权在高并发场景下可能成为性能瓶颈。
  • 可维护性差: 集中式鉴权逻辑复杂,维护成本高。

2. Spring Security Filter Chain 简介

Spring Security 基于 Servlet Filter 构建安全机制。当一个请求到达服务器时,会依次经过一系列的 Filter,每个 Filter 负责处理特定的安全任务,例如认证、授权、防止 CSRF 攻击等。这些 Filter 按照一定的顺序组成一个 Filter Chain。

Spring Security 默认提供了一组 Filter,例如 UsernamePasswordAuthenticationFilterBasicAuthenticationFilterAuthorizationFilter 等。但这些默认 Filter 往往不能满足微服务环境下的复杂需求,因此我们需要定制 Filter Chain,添加自定义的 Filter,实现细粒度鉴权。

3. 定制 Filter Chain 的基本步骤

定制 Filter Chain 的基本步骤如下:

  1. 创建自定义 Filter: 实现 javax.servlet.Filter 接口,编写自定义的鉴权逻辑。
  2. 配置 Filter: 在 Spring Security 配置类中,将自定义 Filter 添加到 Filter Chain 中。
  3. 调整 Filter 顺序: 根据实际需求,调整 Filter 在 Filter Chain 中的顺序。

4. 创建自定义 Filter

首先,我们需要创建一个自定义的 Filter,实现 javax.servlet.Filter 接口。以下是一个简单的自定义 Filter 示例:

import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component
public class CustomAuthenticationFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // 初始化操作,例如加载配置
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;

        // 1. 从请求头或请求参数中获取 Token
        String token = httpRequest.getHeader("Authorization");

        // 2. 验证 Token 的有效性
        if (isValidToken(token)) {
            // 3. 如果 Token 有效,则将用户信息放入 SecurityContextHolder 中
            // 例如:
            // Authentication authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
            // SecurityContextHolder.getContext().setAuthentication(authentication);

            //  模拟一个UserDetails
            UserDetails userDetails = new UserDetails("user1","ROLE_ADMIN");
            Authentication authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
            SecurityContextHolder.getContext().setAuthentication(authentication);

            // 4. 继续执行 Filter Chain
            chain.doFilter(request, response);
        } else {
            // 5. 如果 Token 无效,则返回 401 Unauthorized 错误
            httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            httpResponse.getWriter().write("Invalid Token");
        }
    }

    @Override
    public void destroy() {
        // 销毁操作,例如释放资源
    }

    private boolean isValidToken(String token) {
        // 在这里实现 Token 验证的逻辑
        // 例如,从数据库或缓存中查询 Token 是否存在,是否过期等
        // 这里只是一个示例,实际应用中需要根据具体的 Token 机制进行验证
        return token != null && token.startsWith("Bearer ");
    }
}

class UserDetails {
    private String username;
    private String role;

    public UserDetails(String username,String role){
        this.username = username;
        this.role = role;
    }

    public String getUsername(){
        return this.username;
    }

    public String getRole(){
        return this.role;
    }

    public Collection<? extends GrantedAuthority> getAuthorities() {
        List<GrantedAuthority> authorities = new ArrayList<>();
        authorities.add(new SimpleGrantedAuthority(this.role));
        return authorities;
    }
}

在这个示例中,CustomAuthenticationFilter 实现了以下逻辑:

  1. 从请求头中获取 Authorization Token。
  2. 调用 isValidToken() 方法验证 Token 的有效性。
  3. 如果 Token 有效,则将用户信息放入 SecurityContextHolder 中。SecurityContextHolder 是 Spring Security 用于存储当前用户信息的上下文。
  4. 继续执行 Filter Chain,将请求传递给下一个 Filter。
  5. 如果 Token 无效,则返回 401 Unauthorized 错误。

5. 配置 Filter

接下来,我们需要在 Spring Security 配置类中,将自定义 Filter 添加到 Filter Chain 中。Spring Security 提供了多种方式来配置 Filter Chain,例如使用 WebSecurityConfigurerAdapterSecurityFilterChain Bean。

使用 SecurityFilterChain Bean 方式是 Spring Security 5.7 之后推荐的方式,因为它更加灵活和简洁。以下是一个使用 SecurityFilterChain Bean 配置 Filter 的示例:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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 org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    private final CustomAuthenticationFilter customAuthenticationFilter;

    public SecurityConfig(CustomAuthenticationFilter customAuthenticationFilter) {
        this.customAuthenticationFilter = customAuthenticationFilter;
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .csrf().disable() // 禁用 CSRF 保护,微服务通常不需要
            .authorizeHttpRequests(authz -> authz
                .requestMatchers(new AntPathRequestMatcher("/public/**")).permitAll() // 允许访问 /public/** 路径
                .requestMatchers(new AntPathRequestMatcher("/admin/**")).hasRole("ADMIN") // 需要 ADMIN 角色才能访问 /admin/** 路径
                .anyRequest().authenticated() // 其他所有请求都需要认证
            )
            .addFilterBefore(customAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); // 在 UsernamePasswordAuthenticationFilter 之前添加自定义 Filter

        return http.build();
    }
}

在这个示例中,SecurityConfig 类使用 SecurityFilterChain Bean 配置 Filter Chain。

  • csrf().disable() 禁用 CSRF 保护。在微服务架构中,通常由 API Gateway 或前端应用负责处理 CSRF 保护,因此微服务本身可以禁用 CSRF 保护。
  • authorizeHttpRequests() 方法配置请求的授权规则。
    • requestMatchers(new AntPathRequestMatcher("/public/**")).permitAll() 允许访问 /public/** 路径。
    • requestMatchers(new AntPathRequestMatcher("/admin/**")).hasRole("ADMIN") 需要 ADMIN 角色才能访问 /admin/** 路径。
    • anyRequest().authenticated() 其他所有请求都需要认证。
  • addFilterBefore(customAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)UsernamePasswordAuthenticationFilter 之前添加自定义 Filter。这意味着 CustomAuthenticationFilter 会在用户名密码认证之前执行。

6. 调整 Filter 顺序

Filter 在 Filter Chain 中的顺序非常重要,它决定了 Filter 的执行顺序。Spring Security 提供了一些默认的 Filter,例如 UsernamePasswordAuthenticationFilterBasicAuthenticationFilterAuthorizationFilter 等。我们可以通过 addFilterBefore()addFilterAfter()addFilterAt() 等方法来调整 Filter 的顺序。

例如,如果我们希望自定义 Filter 在所有 Filter 之前执行,可以使用 addFilterBefore(customAuthenticationFilter, FirstFilter.class),其中 FirstFilter.class 是 Spring Security Filter Chain 中的第一个 Filter。

Spring Security默认的过滤器链如下:

过滤器名称 作用
ChannelProcessingFilter 负责将请求重定向到正确的协议(如HTTPS)。
WebAsyncManagerIntegrationFilter 将SecurityContext与Spring WebAsyncManager集成,以便异步请求可以访问安全上下文。
SecurityContextPersistenceFilter 在请求处理前后保存和恢复SecurityContext,这样用户的认证信息可以在多个请求之间保持。
HeaderWriterFilter 添加HTTP响应头,例如防止缓存。
CsrfFilter 防止跨站请求伪造(CSRF)攻击。
LogoutFilter 处理注销请求,清除用户的认证信息。
X509AuthenticationFilter 从X.509证书中提取用户信息进行认证。
AbstractPreAuthenticatedProcessingFilter 用于预认证,例如基于客户端证书或请求头。
CasAuthenticationFilter 集成CAS(Central Authentication Service)单点登录。
UsernamePasswordAuthenticationFilter 处理基于用户名和密码的表单登录请求。
ConcurrentSessionFilter 用于管理并发会话,例如防止用户在多个地方同时登录。
DigestAuthenticationFilter 使用摘要认证(Digest Authentication)进行认证。
BearerTokenAuthenticationFilter 处理OAuth 2.0 Bearer Token认证。
BasicAuthenticationFilter 处理HTTP基本认证。
RequestCacheAwareFilter 将请求缓存到RequestCache中,以便在认证成功后重定向到原始请求。
SecurityContextHolderAwareRequestFilter 提供对Servlet API的增强,以便在JSP等视图技术中访问安全上下文。
JaasApiIntegrationFilter 集成Java Authentication and Authorization Service (JAAS)。
RememberMeAuthenticationFilter 提供“记住我”功能,允许用户在关闭浏览器后保持登录状态。
AnonymousAuthenticationFilter 如果SecurityContext中没有认证信息,则创建一个匿名用户。
OAuth2AuthorizationRequestRedirectFilter 用于OAuth 2.0授权流程,重定向用户到授权服务器。
OAuth2LoginAuthenticationFilter 处理OAuth 2.0登录流程。
OpenIDAuthenticationFilter 集成OpenID身份验证。
DefaultLoginPageGeneratingFilter 如果没有配置自定义登录页面,则生成默认的登录页面。
DefaultLogoutPageGeneratingFilter 如果没有配置自定义注销页面,则生成默认的注销页面。
ConcurrentSessionControlAuthenticationStrategy 用于控制并发会话,例如限制同一用户同时登录的数量。
ExceptionTranslationFilter 处理Spring Security抛出的异常,例如AuthenticationException和AccessDeniedException。
FilterSecurityInterceptor 负责授权,决定用户是否有权访问受保护的资源。这是过滤器链中最后一个主要的过滤器。

7. 实现细粒度鉴权

通过定制 Filter Chain,我们可以实现微服务中的细粒度鉴权。以下是一些常见的细粒度鉴权策略:

  • 基于角色的鉴权: 根据用户的角色来决定是否允许访问某个资源。例如,只有管理员角色才能访问 /admin/** 路径。
  • 基于权限的鉴权: 根据用户的权限来决定是否允许访问某个资源。例如,只有拥有 READ_PRODUCT 权限的用户才能访问 /products/** 路径。
  • 基于资源的鉴权: 根据用户对资源的访问权限来决定是否允许访问某个资源。例如,只有资源的拥有者才能修改该资源。
  • 基于属性的鉴权: 根据资源的属性来决定是否允许访问某个资源。例如,只有状态为 PUBLISHED 的文章才能被公开访问。

我们可以将这些鉴权逻辑封装到自定义 Filter 中,并根据实际需求调整 Filter 在 Filter Chain 中的顺序。

8. 代码示例:基于角色的鉴权

以下是一个基于角色的鉴权示例:

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.ArrayList;

@Component
public class RoleBasedAuthorizationFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // 初始化操作
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;

        // 1. 获取当前用户的角色
        Collection<? extends GrantedAuthority> authorities = SecurityContextHolder.getContext().getAuthentication().getAuthorities();

        // 2. 判断用户是否拥有访问该资源的权限
        if (hasRole(authorities, getRequiredRole(httpRequest.getRequestURI()))) {
            // 3. 如果用户拥有权限,则继续执行 Filter Chain
            chain.doFilter(request, response);
        } else {
            // 4. 如果用户没有权限,则返回 403 Forbidden 错误
            httpResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);
            httpResponse.getWriter().write("Forbidden");
        }
    }

    @Override
    public void destroy() {
        // 销毁操作
    }

    private boolean hasRole(Collection<? extends GrantedAuthority> authorities, String requiredRole) {
        if (requiredRole == null) {
            return true; // 如果没有指定角色,则允许访问
        }
        for (GrantedAuthority authority : authorities) {
            if (authority.getAuthority().equals(requiredRole)) {
                return true;
            }
        }
        return false;
    }

    private String getRequiredRole(String uri) {
        // 在这里根据 URI 获取所需的角色
        // 例如:
        if (uri.startsWith("/admin")) {
            return "ROLE_ADMIN";
        } else if (uri.startsWith("/user")) {
            return "ROLE_USER";
        } else {
            return null; // 不需要角色
        }
    }
}

在这个示例中,RoleBasedAuthorizationFilter 实现了以下逻辑:

  1. 获取当前用户的角色。
  2. 调用 hasRole() 方法判断用户是否拥有访问该资源的权限。
  3. 如果用户拥有权限,则继续执行 Filter Chain。
  4. 如果用户没有权限,则返回 403 Forbidden 错误。

getRequiredRole() 方法根据 URI 获取所需的角色。例如,访问 /admin 路径需要 ROLE_ADMIN 角色,访问 /user 路径需要 ROLE_USER 角色。

需要在 SecurityConfig 中配置这个Filter。

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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 org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    private final CustomAuthenticationFilter customAuthenticationFilter;
    private final RoleBasedAuthorizationFilter roleBasedAuthorizationFilter;

    public SecurityConfig(CustomAuthenticationFilter customAuthenticationFilter,RoleBasedAuthorizationFilter roleBasedAuthorizationFilter) {
        this.customAuthenticationFilter = customAuthenticationFilter;
        this.roleBasedAuthorizationFilter = roleBasedAuthorizationFilter;
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .csrf().disable() // 禁用 CSRF 保护,微服务通常不需要
            .authorizeHttpRequests(authz -> authz
                .requestMatchers(new AntPathRequestMatcher("/public/**")).permitAll() // 允许访问 /public/** 路径
                //.requestMatchers(new AntPathRequestMatcher("/admin/**")).hasRole("ADMIN") // 需要 ADMIN 角色才能访问 /admin/** 路径,这个配置被 RoleBasedAuthorizationFilter 替代
                .anyRequest().authenticated() // 其他所有请求都需要认证
            )
            .addFilterBefore(customAuthenticationFilter, UsernamePasswordAuthenticationFilter.class) // 在 UsernamePasswordAuthenticationFilter 之前添加自定义 Filter
            .addFilterAfter(roleBasedAuthorizationFilter,CustomAuthenticationFilter.class);

        return http.build();
    }
}

9. 总结

通过定制 Spring Security Filter Chain,我们可以实现微服务中的细粒度鉴权。这种方式更加灵活、可扩展,能够满足微服务架构下的复杂安全需求。定制 Filter Chain 的关键在于创建自定义 Filter,并根据实际需求调整 Filter 在 Filter Chain 中的顺序。

10. 细粒度鉴权策略及代码示例

鉴权策略 描述 代码示例
基于角色 根据用户的角色决定是否允许访问资源。 (上面RoleBasedAuthorizationFilter的代码示例)
基于权限 根据用户拥有的权限决定是否允许访问资源。 (后续补充)
基于资源所有者 只有资源的创建者或所有者才能修改或删除该资源。 (后续补充)
基于属性 资源的某些属性值满足特定条件时才允许访问。例如,只有状态为"已发布"的文章才能被公开访问。 (后续补充)
RBAC 基于角色的访问控制(Role-Based Access Control),将用户分配到不同的角色,每个角色拥有不同的权限。 (上面RoleBasedAuthorizationFilter的代码示例可以看作是RBAC的一种简化实现)
ABAC 基于属性的访问控制(Attribute-Based Access Control),根据用户的属性、资源属性、环境属性等多种因素综合判断是否允许访问。 (后续补充)

掌握 Spring Security 的核心特性,构建安全的微服务架构

Spring Security 提供了强大的安全功能,通过定制 Filter Chain,我们可以灵活地实现各种鉴权策略,从而构建安全的微服务架构。理解 Filter Chain 的工作原理,掌握自定义 Filter 的方法,是 Spring Security 的核心技能。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注