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

Spring Security:为不同端点配置JWT与Basic Auth共存

Spring Security配置JWT与Basic Auth分保护端点问题

我在Spring Security中配置JWT与Basic Auth分别保护不同端点时遇到困难,试过多种方法,包括参考Stack Overflow上的多认证类型配置方案。我的特殊情况是:用户信息不从数据库直接获取,而是通过OpenFeign调用EMP-S(员工服务)微服务获取。以下是我的相关配置代码:

SecurityConfiguration.java

@SuppressWarnings(value = "unused")
@Profile(value = "!test")
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor(onConstructor_ = @__(@Autowired))
public class SecurityConfiguration {

    // Final Constructors
    private final BasicAuthEntryPoint basicAuthEntryPoint;
    private final JwtAuthEntryPoint jwtAuthEntryPoint;
    private final JwtFilter jwtFilter;

    // Retrieving username & password from environment variables
    @Value("${EMP_CONFIG_USERNAME}")
    private String envUsername;

    @Value("${EMP_CONFIG_PASSWORD}")
    private String envPassword;

    @Value("${EMP_ENCRYPT_KEY}")
    private String envEncryptKey;

    // PasswordEncoder
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    // RequestInterceptor (Authentication with external Microservices - OpenFeign)
    @Bean
    public RequestInterceptor basicAuthRequestInterceptor() {
        return requestTemplate -> {
            String authHeader = "Basic " + Base64.getEncoder()
                    .encodeToString((envUsername + ":" + envEncryptKey).getBytes());
            requestTemplate.header("Authorization", authHeader);
        };
    }

    // Setting SecurityFilterChain - Basic Auth
    @Bean
    @Order(1)
    public SecurityFilterChain securityFilterChainBasicAuth(HttpSecurity http) throws Exception {
        return http
                .securityMatcher("/actuator/**")
                .authorizeHttpRequests(req -> req
                        .anyRequest().hasRole("ADMIN"))
                .httpBasic(withDefaults())
                .sessionManagement(session -> session
                        .sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .exceptionHandling(ex -> ex.authenticationEntryPoint(basicAuthEntryPoint))
                .csrf(AbstractHttpConfigurer::disable)
                .build();
    }

    // Setting SecurityFilterChain - JWT Auth
    @Bean
    @Order(2)
    public SecurityFilterChain securityFilterChainJwt(HttpSecurity http) throws Exception {
        return http
                .csrf(AbstractHttpConfigurer::disable)
                .authorizeHttpRequests(request -> request
                        .requestMatchers(HttpMethod.POST, "/v1/auth/**").permitAll()
                        .anyRequest().authenticated())
                .sessionManagement(session -> session
                        .sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                //.exceptionHandling(ex -> ex.authenticationEntryPoint(jwtAuthEntryPoint))
                .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class)
                .build();
    }


    // AuthenticationManager (JWT)
    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
        return configuration.getAuthenticationManager();
    }

    // InMemory User - Admins Only (Basic Auth)
    @Bean
    public UserDetailsService adminUser() {

        // Admin
        return new InMemoryUserDetailsManager(
                User.withUsername(envUsername)
                        .password(envPassword)
                        .roles("ADMIN")
                        .build()
        );
    }
}

JwtFilter.java

@Component
@RequiredArgsConstructor(onConstructor_ = @__(@Autowired))
public class JwtFilter extends OncePerRequestFilter {

    // Final Constructors
    private final JwtUtils jwtUtils;
    private final AuthServiceProxy authServiceProxy;
    private final Logger logger = LoggerFactory.getLogger(JwtFilter.class);

    @Override
    protected void doFilterInternal(
            @NonNull HttpServletRequest request,
            @NonNull HttpServletResponse response,
            @NonNull FilterChain filterChain
    ) throws ServletException, IOException {

        // Extracting 'Authorization' from request Headers
        String authHeader = getAuthHeader(request);
        String requestPath = request.getRequestURI();

        // 1. Skipping JWT if Authorization Header is 'Basic'
        if (authHeader != null && authHeader.startsWith("Basic ")) {
            logger.debug("Skipping JWT filter for Basic Auth request [{}]", requestPath);
            filterChain.doFilter(request, response);
            return;
        }

        // 2. Validating
        if (authHeader == null || !authHeader.startsWith("Bearer ")) {
            logger.debug("No Bearer token found in request [{}], skipping JWT filter", requestPath);
            filterChain.doFilter(request, response);
            return;
        }

        String token = authHeader.substring(7);
        String username = jwtUtils.getUsername(token);
        logger.debug("JWT token parsed. Username extracted: [{}]", username);

        if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
            CredentialDTO credentialDTOByUsername = authServiceProxy.getCredentialByUsername(
                    username,
                    "GET",
                    "/v1/creds/ids?user=" + username
            );

            if (jwtUtils.isTokenValid(token, credentialDTOByUsername)) {
                UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
                        credentialDTOByUsername,
                        null,
                        credentialDTOByUsername.getAuthorities()
                );
                authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                SecurityContextHolder.getContext().setAuthentication(authToken);
                logger.info("JWT authentication successful for user [{}] on [{}]", username, requestPath);
            }
        }
        filterChain.doFilter(request, response);
    }

    // Extracting 'Authorization' from request Headers
    private String getAuthHeader(HttpServletRequest request) {
        return request.getHeader(HttpHeaders.AUTHORIZATION);
    }
}

JwtAuthEntryPoint.java

@Component
@RequiredArgsConstructor(onConstructor_ = @__(@Autowired))
public class JwtAuthEntryPoint implements AuthenticationEntryPoint {

    // Final Constructor
    private final HandlerExceptionResolver handlerExceptionResolver;
    private final Logger logger = LoggerFactory.getLogger(JwtAuthEntryPoint.class);

    @Override
    public void commence(
            @NonNull HttpServletRequest request,
            @NonNull HttpServletResponse response,
            @NonNull AuthenticationException authException
    ) {
        logger.error("Error occurred during JWT Auth: {}", authException.getMessage());
        handlerExceptionResolver.resolveException(
                request,
                response,
                null,
                new UnauthorizedAccessAttempt(authException.getMessage())
        );
    }
}

CredentialAuthenticationProvider.java

@Component
@RequiredArgsConstructor(onConstructor_ = @__(@Autowired))
public class CredentialAuthenticationProvider implements AuthenticationProvider {

    // Final Constructors
    private final AuthServiceProxy authServiceProxy;
    private final PasswordEncoder passwordEncoder;
    private final Logger logger = LoggerFactory.getLogger(CredentialAuthenticationProvider.class);

    @Value("${EMP_CONFIG_USERNAME}")
    private String adminUsername;

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {

        // Username & Password (Plaintext)
        String username = authentication.getName();

        // Skip in-memory admin
        if (username.equals(adminUsername)) {
            logger.debug("Skipping CredentialAuthenticationProvider for in-memory user [{}]", username);
            return null;
        }

        String password = authentication.getCredentials().toString();

        CredentialDTO credentialDTOByUsername = authServiceProxy.getCredentialByUsername(
                username,
                "GET",
                "/v1/cred/ids?user=" + username
        );
        if(credentialDTOByUsername == null || !passwordEncoder.matches(password, credentialDTOByUsername.password())){
            logger.error("Username or Password aren't valid - Please check again for JWT generation");
            throw new InvalidCredentialException(username);
        }
        logger.info("Username & Password valid!");
        return new UsernamePasswordAuthenticationToken(credentialDTOByUsername, null, credentialDTOByUsername.getAuthorities());
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
    }
}

恳请各位提供帮助,谢谢!

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 10:54:55