Ошибка аутентификации JWT весной с асинхронными запросамиJAVA

Программисты JAVA общаются здесь
Ответить
Anonymous
 Ошибка аутентификации JWT весной с асинхронными запросами

Сообщение Anonymous »

Привет всем, весной я написал бэкенд API и добавил аутентификацию jwt. Проблема в том, что я написал все асинхронно, и теперь аутентификация не работает с асинхронными методами. Токен правильный и работает с неасинхронными методами. Кажется, что есть два запроса, но он только один. Это логи:

Код: Выделить всё

2024-12-01T14:06:14.483+01:00 DEBUG 24364 --- [nio-8010-exec-1] o.s.security.web.FilterChainProxy        : Securing POST /api/videos/057aed00-6ac6-4353-aec7-be0faf5835c1/like
2024-12-01T14:06:14.490+01:00 DEBUG 24364 --- [nio-8010-exec-1] c.d.d.JwtAuthenticationFilter            : Start of doFilterInternal: Request URL: http://localhost:8010/api/videos/057aed00-6ac6-4353-aec7-be0faf5835c1/like
2024-12-01T14:06:14.565+01:00 DEBUG 24364 --- [nio-8010-exec-1] c.d.d.JwtAuthenticationFilter            : Extracted username from JWT: 1234
Hibernate: select u1_0.uuid,u1_0.banned,u1_0.beta_key,u1_0.daily_quest_id,u1_0.email,u1_0.enabled,u1_0.followers,u1_0.password,u1_0.profile_picture,u1_0.username,u1_0.verification_code,u1_0.verification_expiration from users u1_0 where u1_0.email=?
Hibernate: select q1_0.uuid,q1_0.description,q1_0.dislikes,q1_0.likes,q1_0.title from quest q1_0 where q1_0.uuid=?
2024-12-01T14:06:14.701+01:00  INFO 24364 --- [nio-8010-exec-1] c.d.d.JwtAuthenticationFilter            : Authentication successfully set for user: 1234
2024-12-01T14:06:14.704+01:00 DEBUG 24364 --- [nio-8010-exec-1] o.s.security.web.FilterChainProxy        : Secured POST /api/videos/057aed00-6ac6-4353-aec7-be0faf5835c1/like
2024-12-01T14:06:14.784+01:00 DEBUG 24364 --- [nio-8010-exec-1] c.d.d.JwtAuthenticationFilter            : End of doFilterInternal: Authentication: UsernamePasswordAuthenticationToken [Principal=com.dayquest.dayquestbackend.user.User@99881f6, Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=0:0:0:0:0:0:0:1, SessionId=null], Granted Authorities=[]]
Hibernate: select u1_0.uuid,u1_0.banned,u1_0.beta_key,dq1_0.uuid,dq1_0.description,dq1_0.dislikes,dq1_0.likes,dq1_0.title,u1_0.email,u1_0.enabled,u1_0.followers,u1_0.password,u1_0.profile_picture,u1_0.username,u1_0.verification_code,u1_0.verification_expiration from users u1_0 left join quest dq1_0 on dq1_0.uuid=u1_0.daily_quest_id where u1_0.uuid=?
Hibernate: select v1_0.uuid,v1_0.description,v1_0.down_votes,v1_0.file_path,v1_0.quest_uuid,v1_0.thumbnail,v1_0.title,v1_0.up_votes,v1_0.user_id from video v1_0 where v1_0.uuid=?
Hibernate: select lv1_0.user_id,lv1_0.video_id from liked_videos lv1_0 where lv1_0.user_id=?
2024-12-01T14:06:15.144+01:00 DEBUG 24364 --- [nio-8010-exec-3] o.s.security.web.FilterChainProxy        : Securing POST /api/videos/057aed00-6ac6-4353-aec7-be0faf5835c1/like
2024-12-01T14:06:15.144+01:00 DEBUG 24364 --- [nio-8010-exec-3] o.s.s.w.a.AnonymousAuthenticationFilter  : Set SecurityContextHolder to anonymous SecurityContext
и это код:

Код: Выделить всё

@PostMapping("/{uuid}/like")
@Async
public CompletableFuture likeVideo(
@PathVariable UUID uuid,
@RequestBody UuidDTO userUuid,
Principal principal) {

return CompletableFuture.supplyAsync(() -> {
try {
// Use the current authentication directly
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth == null || !auth.isAuthenticated()) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}

User currentUser = (User) auth.getPrincipal();
Optional user = userRepository.findById(UUID.fromString(userUuid.getUuid()));
Optional  video = videoRepository.findById(uuid);

// Verify the user matches the authenticated user
if (user.isEmpty() || !user.get().getEmail().equals(currentUser.getEmail())) {
return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
}

if (user.get().getLikedVideos().contains(uuid)) {
return ResponseEntity.status(HttpStatus.NOT_ACCEPTABLE).build();
}

if (user.get().getDislikedVideos().contains(uuid)) {
user.get().getDislikedVideos().remove(uuid);
video.get().setDownVotes(video.get().getDownVotes() - 1);
videoRepository.save(video.get());
}

user.get().getLikedVideos().add(uuid);
userRepository.save(user.get());
return videoService.likeVideo(uuid).join();
} catch (Exception e) {
System.out.println(e.getMessage());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}, delegatingSecurityContextAsyncTaskExecutor);
}
Моя асинхронная конфигурация:

Код: Выделить всё

package com.dayquest.dayquestbackend;

import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.security.concurrent.DelegatingSecurityContextExecutor;
import org.springframework.security.task.DelegatingSecurityContextAsyncTaskExecutor;

import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {

@Bean
public Executor asyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.setMaxPoolSize(20);
executor.setQueueCapacity(50);
executor.setThreadNamePrefix("async-task-");
executor.initialize();

return new DelegatingSecurityContextAsyncTaskExecutor(executor);
}

@Bean
public AsyncTaskExecutor delegatingSecurityContextAsyncTaskExecutor(ThreadPoolTaskExecutor threadPoolTaskExecutor) {
return new DelegatingSecurityContextAsyncTaskExecutor(threadPoolTaskExecutor);
}
}

Код: Выделить всё

package com.dayquest.dayquestbackend;

import com.dayquest.dayquestbackend.user.User;
import com.dayquest.dayquestbackend.user.UserRepository;
import jakarta.annotation.PostConstruct;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import java.util.Collections;

@Configuration
public class SecurityConfiguration {

private final JwtAuthenticationFilter jwtAuthenticationFilter;

public SecurityConfiguration(JwtAuthenticationFilter jwtAuthenticationFilter) {
this.jwtAuthenticationFilter = jwtAuthenticationFilter;
}

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth ->  auth
.requestMatchers(
"/auth/**",
"/api/users/auth",
"/api/users/login",
"/api/users/register",
"/swagger-ui/**",
"/v3/**",
"/api/users/verify",
"/api/users/resendcode"
).permitAll()
.anyRequest().authenticated()
)
.exceptionHandling(exceptionHandling ->
exceptionHandling
.authenticationEntryPoint((request, response, authException) -> {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write("Authentication failed: " + authException.getMessage());
})
.accessDeniedHandler((request, response, accessDeniedException) -> {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
response.getWriter().write("Access denied: " + accessDeniedException.getMessage());
})
)
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);

return http.build();
}

@Bean
public UserDetailsService userDetailsService(UserRepository userRepository) {
return username -> {
User user = userRepository.findByEmail(username);
if (user == null) {
throw new UsernameNotFoundException("User not found with email: "  + username);
}
return org.springframework.security.core.userdetails.User
.withUsername(user.getEmail())
.password(user.getPassword())
.authorities(Collections.emptyList())
.build();
};
}

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

@PostConstruct
public void init() {
SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
}
}

Мой фильтр:

Код: Выделить всё

package com.dayquest.dayquestbackend;

import com.dayquest.dayquestbackend.JwtService;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.lang.NonNull;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;

@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private static final Logger logger = LoggerFactory.getLogger(JwtAuthenticationFilter.class);

private final JwtService jwtService;
private final UserDetailsService userDetailsService;

@Autowired
public JwtAuthenticationFilter(JwtService jwtService, UserDetailsService userDetailsService) {
this.jwtService = jwtService;
this.userDetailsService = userDetailsService;
}

@Override
protected void doFilterInternal(
@NonNull HttpServletRequest request,
@NonNull HttpServletResponse response,
@NonNull FilterChain filterChain
) throws ServletException, IOException {
logger.debug("Start of doFilterInternal: Request URL: {}", request.getRequestURL());
final String authHeader = request.getHeader("Authorization");

if (authHeader == null || !authHeader.startsWith("Bearer ")) {
logger.debug("No valid Authorization header found.  Proceeding without authentication.");
filterChain.doFilter(request, response);
return;
}

try {
final String jwt = authHeader.substring(7);
final String username = jwtService.extractUsername(jwt);

logger.debug("Extracted username from JWT: {}", username);

// Proceed only if username is not null and no authentication is set
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = userDetailsService.loadUserByUsername(username);

if (jwtService.isTokenValid(jwt, userDetails)) {
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
userDetails,
null,
userDetails.getAuthorities()
);
authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));

// Set SecurityContext
SecurityContext securityContext = SecurityContextHolder.createEmptyContext();
securityContext.setAuthentication(authToken);
SecurityContextHolder.setContext(securityContext);

logger.info("Authentication successfully set for user: {}", username);
} else {
logger.warn("Invalid JWT token for user: {}", username);
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write("Invalid token");
return;
}
}

// Proceed with the filter chain
filterChain.doFilter(request, response);
} catch (Exception e) {
logger.error("Error occurred during JWT processing", e);
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write("Authentication failed: " + e.getMessage());
} finally {
logger.debug("End of doFilterInternal: Authentication: {}", SecurityContextHolder.getContext().getAuthentication());
}
}
}

Я пробовал использовать делегирующийSecurityContextAsyncTaskExecutor в AsyncConfig, но это не сработало.

Подробнее здесь: https://stackoverflow.com/questions/792 ... c-requests
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «JAVA»