You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot应用能否同时使用X509证书与JWT双重认证?

Combining X.509 Certificate Authentication with JWT in Spring Boot

Absolutely! You can build a layered security model where your Spring Boot app first uses X.509 certificates to secure the exposed endpoint (only clients with valid certificates can reach your app), then requires a valid JWT token to grant access to any API. This gives you both application-level security via certificates and user-level authorization via JWT. Let's walk through adjusting your existing setup to make this work.

Core Approach

We'll keep your existing X.509 authentication as the first security gate (ensuring only trusted clients can connect). Then we'll add a JWT validation filter as a second layer, which checks for a valid JWT in each request (typically in the Authorization header as Bearer <token>) before allowing API access.

Step 1: Update Your Security Configuration

Modify your SecurityConfig to include both X.509 authentication and JWT validation. Here's the adjusted code:

import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter { 
    @Value("${security.enable-csrf}") 
    private boolean csrfEnabled;
    
    private final JwtAuthenticationFilter jwtAuthenticationFilter;

    // Inject the JWT filter we'll define next
    public SecurityConfig(JwtAuthenticationFilter jwtAuthenticationFilter) {
        this.jwtAuthenticationFilter = jwtAuthenticationFilter;
    }

    @Override 
    protected void configure(HttpSecurity http) throws Exception { 
        http
            // First, enforce X.509 certificate validation for all requests
            .authorizeRequests()
                .anyRequest().authenticated()
                .and()
            .x509()
                .subjectPrincipalRegex("CN=(.*?)(?:,|$)")
                .userDetailsService(userDetailsService())
                .and()
            // Add JWT validation filter before the default username/password filter
            .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);

        if (!csrfEnabled) { 
            http.csrf().disable(); 
        } 
    } 

    @Bean 
    public UserDetailsService userDetailsService() { 
        return (UserDetailsService) username -> { 
            if (username.equals("XXXX")) { 
                return new User(username, "", AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER")); 
            } else { 
                throw new UsernameNotFoundException(String.format("User %s not found", username)); 
            } 
        }; 
    }

    // Expose AuthenticationManager as a bean for the JWT filter to use
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}

Step 2: Implement the JWT Authentication Filter

Create a filter that extracts the JWT from the request header, validates it, and sets the authenticated user in the security context. Here's a sample implementation:

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.security.Key;
import java.util.Date;

public class JwtAuthenticationFilter extends OncePerRequestFilter {
    private final UserDetailsService userDetailsService;
    private final String jwtSecret;
    private final long jwtExpirationMs;

    // Inject JWT properties from application.properties
    public JwtAuthenticationFilter(UserDetailsService userDetailsService, 
                                   @Value("${jwt.secret}") String jwtSecret,
                                   @Value("${jwt.expirationMs}") long jwtExpirationMs) {
        this.userDetailsService = userDetailsService;
        this.jwtSecret = jwtSecret;
        this.jwtExpirationMs = jwtExpirationMs;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, 
                                    HttpServletResponse response, 
                                    FilterChain filterChain) throws ServletException, IOException {
        // Extract JWT from the Authorization header
        String jwt = extractJwtFromRequest(request);

        if (jwt != null && validateJwtToken(jwt)) {
            // Get username from JWT claims
            String username = getUsernameFromJwtToken(jwt);

            // Load user details from your UserDetailsService
            UserDetails userDetails = userDetailsService.loadUserByUsername(username);

            // Set authenticated user in the security context
            UsernamePasswordAuthenticationToken authentication = 
                new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }

        filterChain.doFilter(request, response);
    }

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

    private boolean validateJwtToken(String authToken) {
        try {
            Key key = Keys.hmacShaKeyFor(jwtSecret.getBytes());
            Claims claims = Jwts.parserBuilder()
                .setSigningKey(key)
                .build()
                .parseClaimsJws(authToken)
                .getBody();
            
            // Check if the token is expired
            return !claims.getExpiration().before(new Date());
        } catch (Exception e) {
            logger.error("Invalid JWT token: {}", e.getMessage());
        }
        return false;
    }

    private String getUsernameFromJwtToken(String token) {
        Key key = Keys.hmacShaKeyFor(jwtSecret.getBytes());
        return Jwts.parserBuilder()
            .setSigningKey(key)
            .build()
            .parseClaimsJws(token)
            .getBody()
            .getSubject();
    }
}

Step 3: Add JWT Properties to application.properties

Add these configuration values to your application.properties file:

# JWT Configuration
jwt.secret=your-strong-random-secret-key-here-make-it-at-least-32-characters
jwt.expirationMs=86400000 # 1 day in milliseconds (adjust as needed)

Step 4: Register the JWT Filter as a Bean

Add this bean definition to your configuration class (or a separate configuration bean):

@Bean
public JwtAuthenticationFilter jwtAuthenticationFilter(UserDetailsService userDetailsService) {
    return new JwtAuthenticationFilter(userDetailsService, jwtSecret, jwtExpirationMs);
}

How It Works

  1. X.509 Layer: When a client sends a request, the server first validates the client's X.509 certificate. If the certificate is invalid, the request is rejected immediately.
  2. JWT Layer: If the certificate is valid, the request moves to the JWT filter. The filter checks for a valid JWT in the Authorization header. If the token is missing or invalid, access is denied. If valid, the user's authentication is set in the security context, allowing access to the API.

Notes

  • Ensure your server is properly configured with a truststore containing trusted client certificates for X.509 validation.
  • For production, use a strong, randomly generated jwt.secret and consider shorter token expiration times paired with refresh tokens if needed.
  • You can extend the JWT validation logic to check for custom claims (like user roles) to add fine-grained authorization.

内容的提问来源于stack exchange,提问作者Excalibur

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.09 19:13:01