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,例如 UsernamePasswordAuthenticationFilter、BasicAuthenticationFilter、AuthorizationFilter 等。但这些默认 Filter 往往不能满足微服务环境下的复杂需求,因此我们需要定制 Filter Chain,添加自定义的 Filter,实现细粒度鉴权。
3. 定制 Filter Chain 的基本步骤
定制 Filter Chain 的基本步骤如下:
- 创建自定义 Filter: 实现
javax.servlet.Filter接口,编写自定义的鉴权逻辑。 - 配置 Filter: 在 Spring Security 配置类中,将自定义 Filter 添加到 Filter Chain 中。
- 调整 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 实现了以下逻辑:
- 从请求头中获取
AuthorizationToken。 - 调用
isValidToken()方法验证 Token 的有效性。 - 如果 Token 有效,则将用户信息放入
SecurityContextHolder中。SecurityContextHolder是 Spring Security 用于存储当前用户信息的上下文。 - 继续执行 Filter Chain,将请求传递给下一个 Filter。
- 如果 Token 无效,则返回 401 Unauthorized 错误。
5. 配置 Filter
接下来,我们需要在 Spring Security 配置类中,将自定义 Filter 添加到 Filter Chain 中。Spring Security 提供了多种方式来配置 Filter Chain,例如使用 WebSecurityConfigurerAdapter 或 SecurityFilterChain 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,例如 UsernamePasswordAuthenticationFilter、BasicAuthenticationFilter、AuthorizationFilter 等。我们可以通过 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 实现了以下逻辑:
- 获取当前用户的角色。
- 调用
hasRole()方法判断用户是否拥有访问该资源的权限。 - 如果用户拥有权限,则继续执行 Filter Chain。
- 如果用户没有权限,则返回 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 的核心技能。