如何使用 mTL 保护某些端点以及使用 JWT 保护某些端点

问题描述 投票:0回答:1

我有一个 Spring Boot (3.0.5) 应用程序 - 具有 3 组 API:

  1. /api/**
    - 由客户端 UI 应用程序调用 - 使用基于 JWT 的身份验证
  2. /integration/**
    - 由另一个应用程序调用 - 这些必须通过客户端身份验证进行保护。
  3. 其余部分 - 可公开访问。

我将

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。

java spring spring-boot spring-security java-17
1个回答
0
投票

您找到其他解决方案了吗?我有同样的问题,我需要在所有端点上使用 TLS,除了需要 mTLS 的端点之外

© www.soinside.com 2019 - 2024. All rights reserved.