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
相关产品推荐
相关产品推荐

