SUPPORT-9023: move socket connection to listener; fix NPE; add retryable annotation
This commit is contained in:
parent
b7e59faa06
commit
8350cde23b
7 changed files with 76 additions and 24 deletions
|
|
@ -224,6 +224,14 @@
|
|||
<groupId>org.hibernate.validator</groupId>
|
||||
<artifactId>hibernate-validator</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.retry</groupId>
|
||||
<artifactId>spring-retry</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-aspects</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<finalName>${project.parent.artifactId}</finalName>
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import org.springframework.context.annotation.Configuration;
|
|||
import org.springframework.context.annotation.EnableAspectJAutoProxy;
|
||||
import org.springframework.context.annotation.FilterType;
|
||||
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
|
||||
import org.springframework.retry.annotation.EnableRetry;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
|
@ -47,6 +48,7 @@ import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
|||
@EnableAspectJAutoProxy(proxyTargetClass = true)
|
||||
@EnableWebMvc
|
||||
@EnableScheduling
|
||||
@EnableRetry
|
||||
public class AppConfig {
|
||||
|
||||
@Bean
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import java.lang.invoke.MethodHandles;
|
|||
import java.net.URLDecoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.Executors;
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
|
@ -16,7 +15,6 @@ import org.slf4j.LoggerFactory;
|
|||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
|
@ -44,13 +42,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
|||
try {
|
||||
Authentication authentication = attemptAuthentication(request);
|
||||
if (authentication != null) {
|
||||
SecurityContext context = SecurityContextHolder.getContext();
|
||||
context.setAuthentication(authentication);
|
||||
//TODO SUPPORT-9009 connection by duty user
|
||||
new Thread(() -> {
|
||||
SecurityContextHolder.setContext(context);
|
||||
webSocketService.connectToSocket();
|
||||
}).start();
|
||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||
}
|
||||
}
|
||||
catch (AuthenticationException e) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,34 @@
|
|||
package ru.micord.ervu.account_applications.security.listener;
|
||||
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.security.authentication.event.AuthenticationSuccessEvent;
|
||||
import org.springframework.stereotype.Component;
|
||||
import ru.micord.ervu.account_applications.security.model.jwt.UserSession;
|
||||
import ru.micord.ervu.account_applications.security.model.jwt.authentication.JwtTokenAuthentication;
|
||||
import ru.micord.ervu.account_applications.websocket.service.WebSocketService;
|
||||
|
||||
/**
|
||||
* @author gulnaz
|
||||
*/
|
||||
@Component
|
||||
public class SuccessfulAuthListener implements ApplicationListener<AuthenticationSuccessEvent> {
|
||||
private static final String ADMIN_ROLE = "security_administrator";
|
||||
|
||||
private final WebSocketService webSocketService;
|
||||
|
||||
public SuccessfulAuthListener(WebSocketService webSocketService) {
|
||||
this.webSocketService = webSocketService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(AuthenticationSuccessEvent event) {
|
||||
JwtTokenAuthentication authentication = (JwtTokenAuthentication) event.getAuthentication();
|
||||
UserSession userSession = authentication.getUserSession();
|
||||
boolean isAdmin = userSession.roles().stream()
|
||||
.anyMatch(ervuRoleAuthority -> ervuRoleAuthority.getAuthority().equals(ADMIN_ROLE));
|
||||
|
||||
if (isAdmin) {
|
||||
webSocketService.connectToSocket(userSession.userId(), authentication.getCredentials().toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -15,7 +15,6 @@ import org.springframework.web.socket.CloseStatus;
|
|||
import org.springframework.web.socket.WebSocketMessage;
|
||||
import org.springframework.web.socket.WebSocketSession;
|
||||
import org.springframework.web.socket.handler.TextWebSocketHandler;
|
||||
import ru.micord.ervu.account_applications.security.context.SecurityContext;
|
||||
import ru.micord.ervu.account_applications.security.service.EncryptionService;
|
||||
import ru.micord.ervu.account_applications.service.UserApplicationListService;
|
||||
import ru.micord.ervu.account_applications.websocket.dto.ProcessResponseDto;
|
||||
|
|
@ -28,23 +27,21 @@ import ru.micord.ervu.account_applications.websocket.service.WebSocketService;
|
|||
public class ClientSocketHandler extends TextWebSocketHandler {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(TextWebSocketHandler.class);
|
||||
private static final Map<String, WebSocketSession> sessionByUserId = new ConcurrentHashMap<>();
|
||||
private static final Map<String, UserData> userDataBySessionId = new ConcurrentHashMap<>();
|
||||
|
||||
private final ObjectMapper objectMapper;
|
||||
private final UserApplicationListService applicationService;
|
||||
private final EncryptionService encryptionService;
|
||||
private final WebSocketService webSocketService;
|
||||
private final SecurityContext securityContext;
|
||||
|
||||
public ClientSocketHandler(ObjectMapper objectMapper,
|
||||
UserApplicationListService applicationService,
|
||||
EncryptionService encryptionService,
|
||||
@Lazy WebSocketService webSocketService,
|
||||
SecurityContext securityContext) {
|
||||
@Lazy WebSocketService webSocketService) {
|
||||
this.objectMapper = objectMapper;
|
||||
this.applicationService = applicationService;
|
||||
this.encryptionService = encryptionService;
|
||||
this.webSocketService = webSocketService;
|
||||
this.securityContext = securityContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -104,7 +101,10 @@ public class ClientSocketHandler extends TextWebSocketHandler {
|
|||
LOGGER.error("Failed to close session on afterConnectionClosed ", e);
|
||||
}
|
||||
}
|
||||
webSocketService.connectToSocket();
|
||||
String sessionId = session.getId();
|
||||
UserData userData = userDataBySessionId.get(sessionId);
|
||||
userDataBySessionId.remove(sessionId);
|
||||
webSocketService.connectToSocket(userData.userId(), userData.token());
|
||||
}
|
||||
|
||||
public boolean isSessionOpen(String userId) {
|
||||
|
|
@ -114,4 +114,10 @@ public class ClientSocketHandler extends TextWebSocketHandler {
|
|||
public void putSession(String userId, WebSocketSession session) {
|
||||
sessionByUserId.put(userId, session);
|
||||
}
|
||||
|
||||
public void putUserData(String sessionId, String userId, String token) {
|
||||
userDataBySessionId.put(sessionId, new UserData(userId, token));
|
||||
}
|
||||
|
||||
private record UserData(String userId, String token) {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,12 +9,12 @@ import java.util.concurrent.TimeoutException;
|
|||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.retry.annotation.Retryable;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.socket.WebSocketHandler;
|
||||
import org.springframework.web.socket.WebSocketHttpHeaders;
|
||||
import org.springframework.web.socket.WebSocketSession;
|
||||
import org.springframework.web.socket.client.WebSocketClient;
|
||||
import ru.micord.ervu.account_applications.security.context.SecurityContext;
|
||||
import ru.micord.ervu.account_applications.websocket.handler.ClientSocketHandler;
|
||||
|
||||
/**
|
||||
|
|
@ -26,7 +26,6 @@ public class WebSocketService {
|
|||
|
||||
private final WebSocketClient webSocketClient;
|
||||
private final WebSocketHandler webSocketHandler;
|
||||
private final SecurityContext securityContext;
|
||||
|
||||
@Value("${ervu.url}")
|
||||
private String ervuUrl;
|
||||
|
|
@ -35,22 +34,22 @@ public class WebSocketService {
|
|||
@Value("${ervu.socket.connection_timeout:30}")
|
||||
private long timeout;
|
||||
|
||||
public WebSocketService(WebSocketClient webSocketClient, WebSocketHandler webSocketHandler,
|
||||
SecurityContext securityContext) {
|
||||
public WebSocketService(WebSocketClient webSocketClient, WebSocketHandler webSocketHandler) {
|
||||
this.webSocketClient = webSocketClient;
|
||||
this.webSocketHandler = webSocketHandler;
|
||||
this.securityContext = securityContext;
|
||||
}
|
||||
|
||||
public void connectToSocket() {
|
||||
@Retryable(
|
||||
retryFor = {ExecutionException.class, TimeoutException.class},
|
||||
maxAttemptsExpression = "${socket.connect.max_attempts:3}")
|
||||
public void connectToSocket(String userId, String token) {
|
||||
ClientSocketHandler socketHandler = (ClientSocketHandler) this.webSocketHandler;
|
||||
|
||||
if (socketHandler.isSessionOpen(securityContext.getUserId())) {
|
||||
if (socketHandler.isSessionOpen(userId)) {
|
||||
return;
|
||||
}
|
||||
WebSocketHttpHeaders headers = new WebSocketHttpHeaders();
|
||||
headers.set("Content-Type", "application/json");
|
||||
String token = securityContext.getToken();
|
||||
headers.add("Authorization", "Bearer " + token);
|
||||
headers.add("Cookie", "JWT=" + token); // to listen private messages
|
||||
|
||||
|
|
@ -58,7 +57,8 @@ public class WebSocketService {
|
|||
String host = new URI(ervuUrl).getHost();
|
||||
WebSocketSession session = webSocketClient.doHandshake(this.webSocketHandler, headers,
|
||||
URI.create("wss://" + host + socketQueue)).get(timeout, TimeUnit.SECONDS);
|
||||
socketHandler.putSession(securityContext.getUserId(), session);
|
||||
socketHandler.putSession(userId, session);
|
||||
socketHandler.putUserData(session.getId(), userId, token);
|
||||
}
|
||||
catch (InterruptedException | ExecutionException | URISyntaxException | TimeoutException e) {
|
||||
LOGGER.error("Failed to connect socket", e);
|
||||
|
|
|
|||
10
pom.xml
10
pom.xml
|
|
@ -307,6 +307,16 @@
|
|||
<artifactId>hibernate-validator</artifactId>
|
||||
<version>9.0.0.CR1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.retry</groupId>
|
||||
<artifactId>spring-retry</artifactId>
|
||||
<version>2.0.11</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-aspects</artifactId>
|
||||
<version>6.2.5</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
<repositories>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue