我有一个 Spring Boot (3.0.5) 应用程序 - 具有 3 组 API:
/api/**
- 由客户端 UI 应用程序调用 - 使用基于 JWT 的身份验证/integration/**
- 由另一个应用程序调用 - 这些必须通过客户端身份验证进行保护。我将
server.ssl.client-auth
设置为 want
- 因为我不需要所有端点都遵循 X502 身份验证。
我按照这里的一些帖子解决了类似的问题,并提出了如下的安全配置。它按预期工作,但如您所见,我必须进行大量自定义检查。有没有更好的方法来实现这个目标?
此配置设置
/api/**
使用 JWT 验证,其余部分开放供公众访问。
@Bean
public SecurityFilterChain configureSecurityFilters(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(a -> a
.requestMatchers(new AntPathRequestMatcher("/api/**")).authenticated()
.anyRequest().permitAll()
)
.csrf().disable()
.addFilterBefore(jwtValidationFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
这是我的第二个配置,用于自定义验证
/integration/**
API 调用。
@Bean
@Order(1)
public SecurityFilterChain x502Config(HttpSecurity http, HttpServletRequest request) throws Exception {
http
.securityMatcher("/integration/**")
.csrf().disable()
.addFilterBefore(customX509AuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
.authorizeHttpRequests()
.anyRequest().permitAll();
return http.build();
}
这是自定义过滤器 - 用于验证是否发生客户端证书验证以及上下文是否具有有效的 X509 证书:
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.NegatedRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
import java.security.Principal;
import java.security.cert.X509Certificate;
import java.util.Arrays;
@Component
public class CustomX509AuthenticationFilter extends OncePerRequestFilter {
@Value("${server.ssl.client-auth}")
private String mutualTLSConfig;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
X509Certificate[] clientCertificates = (X509Certificate[]) request.getAttribute(
"jakarta.servlet.request.X509Certificate");
if (isMutualTLSEnabled() && (clientCertificates == null
|| clientCertificates.length == 0)) {
response.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.name());
return;
} else if (isMutualTLSEnabled()) {
X509Certificate clientCertificate = clientCertificates[0];
String username = extractUsernameFromCertificate(clientCertificate);
Authentication authentication = new UsernamePasswordAuthenticationToken(username, "",
Arrays.asList());
SecurityContextHolder.getContext().setAuthentication(authentication);
}
filterChain.doFilter(request, response);
}
private String extractUsernameFromCertificate(X509Certificate certificate) {
Principal principal = certificate.getSubjectDN();
String name = principal.getName();
int startIndex = name.indexOf("CN=") + 3;
int endIndex = name.indexOf(",", startIndex);
if (endIndex == -1) {
endIndex = name.length();
}
return name.substring(startIndex, endIndex);
}
// Ignore this validation if "none" is set (dev purposes)
// "need" will never be used in this application (because if used, client-auth will be applied to all requests)
private boolean isMutualTLSEnabled() {
return StringUtils.isNotEmpty(mutualTLSConfig) && mutualTLSConfig.equalsIgnoreCase("want");
}
// I had to add this, otherwise all requests goes through this - which means the "securityMatcher" configuration is not applied properly.
@Override protected boolean shouldNotFilter(HttpServletRequest request) {
RequestMatcher matcher = new NegatedRequestMatcher(new AntPathRequestMatcher("/integration/**"));
return matcher.matches(request);
}
}
我还有第二个过滤器,用于验证
jwtValidationFilter
端点的 JWT (/api/**
)。
@Override
protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res,
FilterChain chain) throws IOException, ServletException {
final String requstUri = req.getRequestURI();
final String jwtAuthToken = getCookieValue(req, AUTH_TOKEN_COOKIE_NAME);
// Validates the token and set the authentication in SecurityContextHolder
....
....
UsernamePasswordAuthenticationToken authentication = getAuthentication(jwtToken, res);
SecurityContextHolder.getContext().setAuthentication(authentication);
chain.doFilter(req, res);
}
此配置按预期工作,但我感觉我的安全配置并不完美。
使用 JDK 17、Spring Boot 3.0.5。
您找到其他解决方案了吗?我有同样的问题,我需要在所有端点上使用 TLS,除了需要 mTLS 的端点之外