From f1b50d4c7f3e74ecd6b67d98041e520af4587046 Mon Sep 17 00:00:00 2001 From: Eduard Tihomirov Date: Tue, 1 Oct 2024 10:54:30 +0300 Subject: [PATCH 01/26] SUPPORT-8579: Fix --- .../EmployeeInfoFileUploadController.java | 8 +--- .../EmployeeInfoFileUploadService.java | 20 +++++---- .../ru/micord/ervu/security/esia/Tokens.java | 36 ++++++++++++++++ .../esia/service/EsiaAuthService.java | 41 ++++++++----------- .../esia/service/EsiaDataService.java | 27 ++++++++++-- 5 files changed, 92 insertions(+), 40 deletions(-) create mode 100644 backend/src/main/java/ru/micord/ervu/security/esia/Tokens.java diff --git a/backend/src/main/java/ervu/controller/EmployeeInfoFileUploadController.java b/backend/src/main/java/ervu/controller/EmployeeInfoFileUploadController.java index d9e8b0c4..80d08618 100644 --- a/backend/src/main/java/ervu/controller/EmployeeInfoFileUploadController.java +++ b/backend/src/main/java/ervu/controller/EmployeeInfoFileUploadController.java @@ -22,20 +22,16 @@ public class EmployeeInfoFileUploadController { @RequestMapping(value = "/employee/document", method = RequestMethod.POST) public ResponseEntity saveEmployeeInformationFile(@RequestParam("file") MultipartFile multipartFile, @RequestHeader("X-Employee-Info-File-Form-Type") String formType, HttpServletRequest request) { - String accessToken = null; String authToken = null; Cookie[] cookies = request.getCookies(); if (cookies != null) { for (Cookie cookie : cookies) { - if (cookie.getName().equals("access_token")) { - accessToken = cookie.getValue(); - } - else if (cookie.getName().equals("auth_token")) { + if (cookie.getName().equals("auth_token")) { authToken = cookie.getValue(); } } } - if (accessToken != null && this.fileUploadService.saveEmployeeInformationFile(multipartFile, formType, accessToken, authToken)) { + if (authToken != null && this.fileUploadService.saveEmployeeInformationFile(multipartFile, formType, authToken)) { return ResponseEntity.ok("File successfully uploaded."); } else { diff --git a/backend/src/main/java/ervu/service/fileupload/EmployeeInfoFileUploadService.java b/backend/src/main/java/ervu/service/fileupload/EmployeeInfoFileUploadService.java index a566eddd..c11e08a4 100644 --- a/backend/src/main/java/ervu/service/fileupload/EmployeeInfoFileUploadService.java +++ b/backend/src/main/java/ervu/service/fileupload/EmployeeInfoFileUploadService.java @@ -18,11 +18,15 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.kafka.core.KafkaTemplate; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; +import ru.micord.ervu.security.esia.Tokens; import ru.micord.ervu.security.esia.model.EmployeeModel; import ru.micord.ervu.security.esia.model.PersonModel; import ru.micord.ervu.security.esia.service.UlDataService; +import ru.micord.ervu.security.webbpm.jwt.JwtAuthentication; import ru.micord.ervu.security.webbpm.jwt.model.Token; import ru.micord.ervu.security.webbpm.jwt.service.JwtTokenService; import ru.micord.ervu.service.InteractionService; @@ -64,8 +68,7 @@ public class EmployeeInfoFileUploadService { this.jwtTokenService = jwtTokenService; } - public boolean saveEmployeeInformationFile(MultipartFile multipartFile, String formType, - String accessToken, String authToken) { + public boolean saveEmployeeInformationFile(MultipartFile multipartFile, String formType, String authToken) { String fileUploadUrl = this.url + "/" + getNewFilename(multipartFile.getOriginalFilename()); LocalDateTime now = LocalDateTime.now(); String departureDateTime = now.format(DateTimeFormatter.ofPattern(FORMAT));; @@ -79,10 +82,13 @@ public class EmployeeInfoFileUploadService { String fileId = UUID.randomUUID().toString(); String fileName = multipartFile.getOriginalFilename(); EmployeeInfoFileFormType employeeInfoFileFormType = EmployeeInfoFileFormType.valueOf(formType); - EmployeeModel employeeModel = ulDataService.getEmployeeModel(accessToken); - PersonModel personModel = employeeModel.getPerson(); Token token = jwtTokenService.getToken(authToken); String[] ids = token.getUserAccountId().split(":"); + String userId = ids[0]; + String ervuId = ids[1]; + String accessToken = Tokens.getAccessToken(ids[0]); + EmployeeModel employeeModel = ulDataService.getEmployeeModel(accessToken); + PersonModel personModel = employeeModel.getPerson(); String jsonMessage = getJsonKafkaMessage( employeeInfoKafkaMessageService.getKafkaMessage( fileId, @@ -93,14 +99,14 @@ public class EmployeeInfoFileUploadService { accessToken, timeZone, fileStatus, - ids[1], - ids[0], + ervuId, + userId, personModel ) ); interactionService.setStatus(fileId, fileStatus.getStatus(), fileName, employeeInfoFileFormType.getFilePatternName(), Timestamp.valueOf(now), personModel.getLastName() + " " + personModel.getFirstName().charAt(0) + ". " + personModel.getMiddleName().charAt(0) + ".", (int) multipartFile.getSize(), - ids[1]); + ervuId); return sendMessage(jsonMessage); } else { diff --git a/backend/src/main/java/ru/micord/ervu/security/esia/Tokens.java b/backend/src/main/java/ru/micord/ervu/security/esia/Tokens.java new file mode 100644 index 00000000..87e40792 --- /dev/null +++ b/backend/src/main/java/ru/micord/ervu/security/esia/Tokens.java @@ -0,0 +1,36 @@ +package ru.micord.ervu.security.esia; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author Eduard Tihomirov + */ +public class Tokens { + private static final Map accessTokensMap = new HashMap(); + private static final Map refreshTokensMap = new HashMap(); + + public static void addAccessToken(String prnOid, String token) { + accessTokensMap.put(prnOid, token); + } + + public static String getAccessToken(String prnOid) { + return accessTokensMap.get(prnOid); + } + + public static void removeAccessToken(String prnOid) { + accessTokensMap.remove(prnOid); + } + + public static void addRefreshToken(String prnOid, String token) { + refreshTokensMap.put(prnOid, token); + } + + public static String getRefreshToken(String prnOid) { + return refreshTokensMap.get(prnOid); + } + + public static void removeRefreshToken(String prnOid) { + refreshTokensMap.remove(prnOid); + } +} diff --git a/backend/src/main/java/ru/micord/ervu/security/esia/service/EsiaAuthService.java b/backend/src/main/java/ru/micord/ervu/security/esia/service/EsiaAuthService.java index 2ae17bb2..1df692eb 100644 --- a/backend/src/main/java/ru/micord/ervu/security/esia/service/EsiaAuthService.java +++ b/backend/src/main/java/ru/micord/ervu/security/esia/service/EsiaAuthService.java @@ -21,6 +21,8 @@ import javax.servlet.http.HttpServletResponse; import com.fasterxml.jackson.databind.ObjectMapper; import ervu.service.okopf.OkopfService; +import org.springframework.security.core.Authentication; +import ru.micord.ervu.security.esia.Tokens; import org.springframework.security.core.context.SecurityContext; import org.springframework.util.StringUtils; import ru.micord.ervu.security.esia.config.EsiaConfig; @@ -215,19 +217,13 @@ public class EsiaAuthService { else { cookiePath = request.getContextPath(); } - Cookie cookie = new Cookie("access_token", accessToken); - cookie.setHttpOnly(true); - cookie.setPath(cookiePath); - response.addCookie(cookie); - - String refreshToken = tokenResponse.getRefresh_token(); - Cookie cookieRefresh = new Cookie("refresh_token", refreshToken); - cookieRefresh.setHttpOnly(true); - cookieRefresh.setPath(cookiePath); - response.addCookie(cookieRefresh); EsiaAccessToken esiaAccessToken = ulDataService.readToken(accessToken); - String ervuId = getErvuId(accessToken, esiaAccessToken.getSbj_id()); + String refreshToken = tokenResponse.getRefresh_token(); + String prnOid = esiaAccessToken.getSbj_id(); + String ervuId = getErvuId(accessToken, prnOid); + Tokens.addAccessToken(prnOid, accessToken); + Tokens.addRefreshToken(prnOid, refreshToken); Token token = jwtTokenService.createAccessToken(esiaAccessToken.getSbj_id(), tokenResponse.getExpires_in(), ervuId); Cookie authToken = new Cookie("auth_token", token.getValue()); authToken.setPath(cookiePath); @@ -254,15 +250,19 @@ public class EsiaAuthService { public void getEsiaTokensByRefreshToken(HttpServletRequest request, HttpServletResponse response) { try { - String refreshToken = null; + String authTokenCookie = null; Cookie[] cookies = request.getCookies(); if (cookies != null) { for (Cookie cookie : cookies) { - if (cookie.getName().equals("refresh_token")) { - refreshToken = cookie.getValue(); + if (cookie.getName().equals("auth_token")) { + authTokenCookie = cookie.getValue(); } } } + Token authJwtToken = jwtTokenService.getToken(authTokenCookie); + String[] split = authJwtToken.getUserAccountId().split(":"); + String userId = split[0]; + String refreshToken = Tokens.getRefreshToken(userId); String clientId = esiaConfig.getClientId(); DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy.MM.dd HH:mm:ss xx"); ZonedDateTime dt = ZonedDateTime.now(); @@ -311,8 +311,6 @@ public class EsiaAuthService { throw new RuntimeException(tokenResponse.getError_description()); } String accessToken = tokenResponse.getAccess_token(); - Cookie cookie = new Cookie("access_token", accessToken); - cookie.setHttpOnly(true); String cookiePath = null; if (path != null) { cookiePath = path; @@ -320,16 +318,13 @@ public class EsiaAuthService { else { cookiePath = request.getContextPath(); } - cookie.setPath(cookiePath); - response.addCookie(cookie); String newRefreshToken = tokenResponse.getRefresh_token(); - Cookie cookieRefresh = new Cookie("refresh_token", newRefreshToken); - cookieRefresh.setHttpOnly(true); - cookieRefresh.setPath(cookiePath); - response.addCookie(cookieRefresh); EsiaAccessToken esiaAccessToken = ulDataService.readToken(accessToken); - String ervuId = getErvuId(accessToken, esiaAccessToken.getSbj_id()); + String prnOid = esiaAccessToken.getSbj_id(); + Tokens.addAccessToken(prnOid, accessToken); + Tokens.addRefreshToken(prnOid, newRefreshToken); + String ervuId = getErvuId(accessToken, prnOid); Token token = jwtTokenService.createAccessToken(esiaAccessToken.getSbj_id(), tokenResponse.getExpires_in(), ervuId); Cookie authToken = new Cookie("auth_token", token.getValue()); authToken.setPath(cookiePath); diff --git a/backend/src/main/java/ru/micord/ervu/security/esia/service/EsiaDataService.java b/backend/src/main/java/ru/micord/ervu/security/esia/service/EsiaDataService.java index 8eac1efc..eb30d418 100644 --- a/backend/src/main/java/ru/micord/ervu/security/esia/service/EsiaDataService.java +++ b/backend/src/main/java/ru/micord/ervu/security/esia/service/EsiaDataService.java @@ -6,7 +6,15 @@ import javax.servlet.http.HttpServletRequest; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; -import ru.micord.ervu.security.esia.model.*; +import ru.micord.ervu.security.esia.Tokens; +import ru.micord.ervu.security.esia.model.Addresses; +import ru.micord.ervu.security.esia.model.Contacts; +import ru.micord.ervu.security.esia.model.EmployeeModel; +import ru.micord.ervu.security.esia.model.EsiaAccessToken; +import ru.micord.ervu.security.esia.model.OrgInfoModel; +import ru.micord.ervu.security.esia.model.OrganizationModel; +import ru.micord.ervu.security.esia.model.PersonModel; +import ru.micord.ervu.security.webbpm.jwt.service.JwtTokenService; /** * @author Eduard Tihomirov @@ -17,6 +25,9 @@ public class EsiaDataService { @Autowired private UlDataService ulDataService; + @Autowired + private JwtTokenService jwtTokenService; + public OrgInfoModel getOrgInfo(HttpServletRequest request) { String accessToken = getAccessToken(request); if (accessToken == null) { @@ -87,13 +98,21 @@ public class EsiaDataService { private String getAccessToken(HttpServletRequest request) { Cookie[] cookies = request.getCookies(); + String authToken = null; if (cookies != null) { for (Cookie cookie : cookies) { - if (cookie.getName().equals("access_token")) { - return cookie.getValue(); + if (cookie.getName().equals("auth_token")) { + authToken = cookie.getValue(); } } } - return null; + if (authToken != null) { + String[] ids = jwtTokenService.getToken(authToken).getUserAccountId().split(":"); + String userId = ids[0]; + return Tokens.getAccessToken(userId); + } + else { + throw new RuntimeException("Failed to get auth data. User unauthorized."); + } } } From aebf5bb7cc9b1cd5fc14eb633102e3a5649b8941 Mon Sep 17 00:00:00 2001 From: Eduard Tihomirov Date: Wed, 2 Oct 2024 09:35:49 +0300 Subject: [PATCH 02/26] SUPPORT-8579: Fix --- .../esia/service/EsiaAuthService.java | 45 ++++++++++++------- 1 file changed, 29 insertions(+), 16 deletions(-) diff --git a/backend/src/main/java/ru/micord/ervu/security/esia/service/EsiaAuthService.java b/backend/src/main/java/ru/micord/ervu/security/esia/service/EsiaAuthService.java index 1df692eb..afb26a6e 100644 --- a/backend/src/main/java/ru/micord/ervu/security/esia/service/EsiaAuthService.java +++ b/backend/src/main/java/ru/micord/ervu/security/esia/service/EsiaAuthService.java @@ -383,6 +383,7 @@ public class EsiaAuthService { public String logout(HttpServletRequest request, HttpServletResponse response) { try { Cookie[] cookies = request.getCookies(); + String authToken = null; if (cookies != null) for (Cookie cookie : cookies) { if (cookie.getName().equals("webbpm.ervu-lkrp-ul")) { @@ -391,7 +392,14 @@ public class EsiaAuthService { cookie.setMaxAge(0); response.addCookie(cookie); } - else if (cookie.getName().equals("auth_token") || cookie.getName().equals("refresh_token") + else if (cookie.getName().equals("auth_token")) { + authToken = cookie.getValue(); + cookie.setValue(""); + cookie.setPath(cookie.getPath()); + cookie.setMaxAge(0); + response.addCookie(cookie); + } + else if (cookie.getName().equals("refresh_token") || cookie.getName().equals("access_token")) { cookie.setValue(""); cookie.setPath(cookie.getPath()); @@ -399,6 +407,10 @@ public class EsiaAuthService { response.addCookie(cookie); } } + Token token = jwtTokenService.getToken(authToken); + String[] ids = token.getUserAccountId().split(":"); + String userId = ids[0]; + Tokens.removeAccessToken(userId); String logoutUrl = esiaConfig.getEsiaBaseUri() + esiaConfig.getEsiaLogoutUrl(); String redirectUrl = esiaConfig.getRedirectUrl(); URL url = new URL(logoutUrl); @@ -414,21 +426,22 @@ public class EsiaAuthService { public String getErvuId(String accessToken, String prnOid) { try { - OrganizationModel organizationModel = ulDataService.getOrganizationModel(accessToken); - EmployeeModel employeeModel = ulDataService.getEmployeeModel(accessToken); - EmployeeModel chiefModel = ulDataService.getChiefEmployeeModel(accessToken); - OrgInfo orgInfo = copyToOrgInfo(organizationModel, employeeModel, chiefModel); - orgInfo.setOrgTypeName(okopfService.findTitleByLeg(orgInfo.getOrgTypeLeg())); - String kafkaResponse = replyingKafkaService.sendMessageAndGetReply(requestTopic, - requestReplyTopic, objectMapper.writeValueAsString(orgInfo) - ); - ErvuOrgResponse ervuOrgResponse = objectMapper.readValue(kafkaResponse, ErvuOrgResponse.class); - String ervuId = ervuOrgResponse.getData().getOrgId_ERVU(); - - if (!StringUtils.hasText(ervuId)) { - throw new RuntimeException("No ervuId for prnOid = " + prnOid); - } - return ervuId; +// OrganizationModel organizationModel = ulDataService.getOrganizationModel(accessToken); +// EmployeeModel employeeModel = ulDataService.getEmployeeModel(accessToken); +// EmployeeModel chiefModel = ulDataService.getChiefEmployeeModel(accessToken); +// OrgInfo orgInfo = copyToOrgInfo(organizationModel, employeeModel, chiefModel); +// orgInfo.setOrgTypeName(okopfService.findTitleByLeg(orgInfo.getOrgTypeLeg())); +// String kafkaResponse = replyingKafkaService.sendMessageAndGetReply(requestTopic, +// requestReplyTopic, objectMapper.writeValueAsString(orgInfo) +// ); +// ErvuOrgResponse ervuOrgResponse = objectMapper.readValue(kafkaResponse, ErvuOrgResponse.class); +// String ervuId = ervuOrgResponse.getData().getOrgId_ERVU(); +// +// if (!StringUtils.hasText(ervuId)) { +// throw new RuntimeException("No ervuId for prnOid = " + prnOid); +// } +// return ervuId; + return "111111"; } catch (Exception e) { throw new RuntimeException(e); From 0f5b9e284607dfd6d11d1903e92d526fa407304c Mon Sep 17 00:00:00 2001 From: Eduard Tihomirov Date: Wed, 2 Oct 2024 09:54:30 +0300 Subject: [PATCH 03/26] SUPPORT-8579: Fix --- .../esia/service/EsiaAuthService.java | 38 ++++++++----------- 1 file changed, 15 insertions(+), 23 deletions(-) diff --git a/backend/src/main/java/ru/micord/ervu/security/esia/service/EsiaAuthService.java b/backend/src/main/java/ru/micord/ervu/security/esia/service/EsiaAuthService.java index afb26a6e..5b64271a 100644 --- a/backend/src/main/java/ru/micord/ervu/security/esia/service/EsiaAuthService.java +++ b/backend/src/main/java/ru/micord/ervu/security/esia/service/EsiaAuthService.java @@ -399,13 +399,6 @@ public class EsiaAuthService { cookie.setMaxAge(0); response.addCookie(cookie); } - else if (cookie.getName().equals("refresh_token") - || cookie.getName().equals("access_token")) { - cookie.setValue(""); - cookie.setPath(cookie.getPath()); - cookie.setMaxAge(0); - response.addCookie(cookie); - } } Token token = jwtTokenService.getToken(authToken); String[] ids = token.getUserAccountId().split(":"); @@ -426,22 +419,21 @@ public class EsiaAuthService { public String getErvuId(String accessToken, String prnOid) { try { -// OrganizationModel organizationModel = ulDataService.getOrganizationModel(accessToken); -// EmployeeModel employeeModel = ulDataService.getEmployeeModel(accessToken); -// EmployeeModel chiefModel = ulDataService.getChiefEmployeeModel(accessToken); -// OrgInfo orgInfo = copyToOrgInfo(organizationModel, employeeModel, chiefModel); -// orgInfo.setOrgTypeName(okopfService.findTitleByLeg(orgInfo.getOrgTypeLeg())); -// String kafkaResponse = replyingKafkaService.sendMessageAndGetReply(requestTopic, -// requestReplyTopic, objectMapper.writeValueAsString(orgInfo) -// ); -// ErvuOrgResponse ervuOrgResponse = objectMapper.readValue(kafkaResponse, ErvuOrgResponse.class); -// String ervuId = ervuOrgResponse.getData().getOrgId_ERVU(); -// -// if (!StringUtils.hasText(ervuId)) { -// throw new RuntimeException("No ervuId for prnOid = " + prnOid); -// } -// return ervuId; - return "111111"; + OrganizationModel organizationModel = ulDataService.getOrganizationModel(accessToken); + EmployeeModel employeeModel = ulDataService.getEmployeeModel(accessToken); + EmployeeModel chiefModel = ulDataService.getChiefEmployeeModel(accessToken); + OrgInfo orgInfo = copyToOrgInfo(organizationModel, employeeModel, chiefModel); + orgInfo.setOrgTypeName(okopfService.findTitleByLeg(orgInfo.getOrgTypeLeg())); + String kafkaResponse = replyingKafkaService.sendMessageAndGetReply(requestTopic, + requestReplyTopic, objectMapper.writeValueAsString(orgInfo) + ); + ErvuOrgResponse ervuOrgResponse = objectMapper.readValue(kafkaResponse, ErvuOrgResponse.class); + String ervuId = ervuOrgResponse.getData().getOrgId_ERVU(); + + if (!StringUtils.hasText(ervuId)) { + throw new RuntimeException("No ervuId for prnOid = " + prnOid); + } + return ervuId; } catch (Exception e) { throw new RuntimeException(e); From 593bea1d1b738e29ebec63ceb6ffcf6f8884874f Mon Sep 17 00:00:00 2001 From: Eduard Tihomirov Date: Wed, 2 Oct 2024 10:00:53 +0300 Subject: [PATCH 04/26] SUPPORT-8579: Fix --- .../ru/micord/ervu/security/esia/service/EsiaAuthService.java | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/src/main/java/ru/micord/ervu/security/esia/service/EsiaAuthService.java b/backend/src/main/java/ru/micord/ervu/security/esia/service/EsiaAuthService.java index 5b64271a..12842746 100644 --- a/backend/src/main/java/ru/micord/ervu/security/esia/service/EsiaAuthService.java +++ b/backend/src/main/java/ru/micord/ervu/security/esia/service/EsiaAuthService.java @@ -404,6 +404,7 @@ public class EsiaAuthService { String[] ids = token.getUserAccountId().split(":"); String userId = ids[0]; Tokens.removeAccessToken(userId); + Tokens.removeRefreshToken(userId); String logoutUrl = esiaConfig.getEsiaBaseUri() + esiaConfig.getEsiaLogoutUrl(); String redirectUrl = esiaConfig.getRedirectUrl(); URL url = new URL(logoutUrl); From 16f99f1993c3ab311df29901a01901caed1a98ff Mon Sep 17 00:00:00 2001 From: Eduard Tihomirov Date: Thu, 3 Oct 2024 10:11:52 +0300 Subject: [PATCH 05/26] SUPPORT-8579: Fix --- .../ru/micord/ervu/security/esia/Tokens.java | 6 ++--- .../esia/service/EsiaDataService.java | 26 +++---------------- .../webbpm/jwt/service/JwtTokenService.java | 23 ++++++++++++++++ 3 files changed, 29 insertions(+), 26 deletions(-) diff --git a/backend/src/main/java/ru/micord/ervu/security/esia/Tokens.java b/backend/src/main/java/ru/micord/ervu/security/esia/Tokens.java index 87e40792..5d4d76d3 100644 --- a/backend/src/main/java/ru/micord/ervu/security/esia/Tokens.java +++ b/backend/src/main/java/ru/micord/ervu/security/esia/Tokens.java @@ -1,14 +1,14 @@ package ru.micord.ervu.security.esia; -import java.util.HashMap; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; /** * @author Eduard Tihomirov */ public class Tokens { - private static final Map accessTokensMap = new HashMap(); - private static final Map refreshTokensMap = new HashMap(); + private static final Map accessTokensMap = new ConcurrentHashMap<>(); + private static final Map refreshTokensMap = new ConcurrentHashMap<>(); public static void addAccessToken(String prnOid, String token) { accessTokensMap.put(prnOid, token); diff --git a/backend/src/main/java/ru/micord/ervu/security/esia/service/EsiaDataService.java b/backend/src/main/java/ru/micord/ervu/security/esia/service/EsiaDataService.java index eb30d418..565d6dac 100644 --- a/backend/src/main/java/ru/micord/ervu/security/esia/service/EsiaDataService.java +++ b/backend/src/main/java/ru/micord/ervu/security/esia/service/EsiaDataService.java @@ -29,7 +29,7 @@ public class EsiaDataService { private JwtTokenService jwtTokenService; public OrgInfoModel getOrgInfo(HttpServletRequest request) { - String accessToken = getAccessToken(request); + String accessToken = jwtTokenService.getAccessToken(request); if (accessToken == null) { return null; } @@ -78,7 +78,7 @@ public class EsiaDataService { } public String getOrgUnitName(HttpServletRequest request) { - String accessToken = getAccessToken(request); + String accessToken = jwtTokenService.getAccessToken(request); if (accessToken == null) { return null; } @@ -87,7 +87,7 @@ public class EsiaDataService { } public String getUserFullname(HttpServletRequest request) { - String accessToken = getAccessToken(request); + String accessToken = jwtTokenService.getAccessToken(request); if (accessToken == null) { return null; } @@ -95,24 +95,4 @@ public class EsiaDataService { PersonModel personModel = ulDataService.getPersonData(esiaAccessToken.getSbj_id(), accessToken); return personModel.getLastName() + " " + personModel.getFirstName().charAt(0) + ". " + personModel.getMiddleName().charAt(0) + "."; } - - private String getAccessToken(HttpServletRequest request) { - Cookie[] cookies = request.getCookies(); - String authToken = null; - if (cookies != null) { - for (Cookie cookie : cookies) { - if (cookie.getName().equals("auth_token")) { - authToken = cookie.getValue(); - } - } - } - if (authToken != null) { - String[] ids = jwtTokenService.getToken(authToken).getUserAccountId().split(":"); - String userId = ids[0]; - return Tokens.getAccessToken(userId); - } - else { - throw new RuntimeException("Failed to get auth data. User unauthorized."); - } - } } diff --git a/backend/src/main/java/ru/micord/ervu/security/webbpm/jwt/service/JwtTokenService.java b/backend/src/main/java/ru/micord/ervu/security/webbpm/jwt/service/JwtTokenService.java index 645b582d..e7d219bf 100644 --- a/backend/src/main/java/ru/micord/ervu/security/webbpm/jwt/service/JwtTokenService.java +++ b/backend/src/main/java/ru/micord/ervu/security/webbpm/jwt/service/JwtTokenService.java @@ -4,6 +4,8 @@ import java.lang.invoke.MethodHandles; import java.util.Base64; import java.util.Date; import javax.crypto.SecretKey; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; @@ -13,6 +15,7 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; +import ru.micord.ervu.security.esia.Tokens; import ru.micord.ervu.security.webbpm.jwt.model.Token; import ru.cg.webbpm.modules.resources.api.ResourceMetadataUtils; @@ -72,4 +75,24 @@ public class JwtTokenService { return new Token(claims.getSubject(), claims.getIssuer(), claims.getExpiration(), token); } + + public String getAccessToken(HttpServletRequest request) { + Cookie[] cookies = request.getCookies(); + String authToken = null; + if (cookies != null) { + for (Cookie cookie : cookies) { + if (cookie.getName().equals("auth_token")) { + authToken = cookie.getValue(); + } + } + } + if (authToken != null) { + String[] ids = getToken(authToken).getUserAccountId().split(":"); + String userId = ids[0]; + return Tokens.getAccessToken(userId); + } + else { + throw new RuntimeException("Failed to get auth data. User unauthorized."); + } + } } From 353cf2040e912cde19838ed74bbfc09b17bde6c0 Mon Sep 17 00:00:00 2001 From: Eduard Tihomirov Date: Thu, 3 Oct 2024 10:48:57 +0300 Subject: [PATCH 06/26] SUPPORT-8579: Fix --- .../main/java/ru/micord/ervu/security/esia/Tokens.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/backend/src/main/java/ru/micord/ervu/security/esia/Tokens.java b/backend/src/main/java/ru/micord/ervu/security/esia/Tokens.java index 5d4d76d3..f639b95f 100644 --- a/backend/src/main/java/ru/micord/ervu/security/esia/Tokens.java +++ b/backend/src/main/java/ru/micord/ervu/security/esia/Tokens.java @@ -11,7 +11,9 @@ public class Tokens { private static final Map refreshTokensMap = new ConcurrentHashMap<>(); public static void addAccessToken(String prnOid, String token) { - accessTokensMap.put(prnOid, token); + if (token != null) { + accessTokensMap.put(prnOid, token); + } } public static String getAccessToken(String prnOid) { @@ -23,7 +25,9 @@ public class Tokens { } public static void addRefreshToken(String prnOid, String token) { - refreshTokensMap.put(prnOid, token); + if (token != null) { + refreshTokensMap.put(prnOid, token); + } } public static String getRefreshToken(String prnOid) { From 2c2ff8680af484572075c186478afd65201fc76d Mon Sep 17 00:00:00 2001 From: Eduard Tihomirov Date: Thu, 3 Oct 2024 16:26:46 +0300 Subject: [PATCH 07/26] SUPPORT-8579: Fix --- .../EmployeeInfoFileUploadService.java | 7 +-- .../ru/micord/ervu/security/esia/Tokens.java | 40 ------------ .../esia/service/EsiaAuthService.java | 35 ++++------- .../esia/service/EsiaDataService.java | 2 - .../security/esia/token/ExpiringToken.java | 34 +++++++++++ .../token/TokensClearShedulerService.java | 20 ++++++ .../ervu/security/esia/token/TokensStore.java | 61 +++++++++++++++++++ .../webbpm/jwt/service/JwtTokenService.java | 15 +++-- config/micord.env | 2 + config/standalone/dev/standalone.xml | 1 + 10 files changed, 143 insertions(+), 74 deletions(-) delete mode 100644 backend/src/main/java/ru/micord/ervu/security/esia/Tokens.java create mode 100644 backend/src/main/java/ru/micord/ervu/security/esia/token/ExpiringToken.java create mode 100644 backend/src/main/java/ru/micord/ervu/security/esia/token/TokensClearShedulerService.java create mode 100644 backend/src/main/java/ru/micord/ervu/security/esia/token/TokensStore.java diff --git a/backend/src/main/java/ervu/service/fileupload/EmployeeInfoFileUploadService.java b/backend/src/main/java/ervu/service/fileupload/EmployeeInfoFileUploadService.java index c11e08a4..07e547cf 100644 --- a/backend/src/main/java/ervu/service/fileupload/EmployeeInfoFileUploadService.java +++ b/backend/src/main/java/ervu/service/fileupload/EmployeeInfoFileUploadService.java @@ -18,15 +18,12 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.kafka.core.KafkaTemplate; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; -import ru.micord.ervu.security.esia.Tokens; +import ru.micord.ervu.security.esia.token.TokensStore; import ru.micord.ervu.security.esia.model.EmployeeModel; import ru.micord.ervu.security.esia.model.PersonModel; import ru.micord.ervu.security.esia.service.UlDataService; -import ru.micord.ervu.security.webbpm.jwt.JwtAuthentication; import ru.micord.ervu.security.webbpm.jwt.model.Token; import ru.micord.ervu.security.webbpm.jwt.service.JwtTokenService; import ru.micord.ervu.service.InteractionService; @@ -86,7 +83,7 @@ public class EmployeeInfoFileUploadService { String[] ids = token.getUserAccountId().split(":"); String userId = ids[0]; String ervuId = ids[1]; - String accessToken = Tokens.getAccessToken(ids[0]); + String accessToken = TokensStore.getAccessToken(ids[0]); EmployeeModel employeeModel = ulDataService.getEmployeeModel(accessToken); PersonModel personModel = employeeModel.getPerson(); String jsonMessage = getJsonKafkaMessage( diff --git a/backend/src/main/java/ru/micord/ervu/security/esia/Tokens.java b/backend/src/main/java/ru/micord/ervu/security/esia/Tokens.java deleted file mode 100644 index f639b95f..00000000 --- a/backend/src/main/java/ru/micord/ervu/security/esia/Tokens.java +++ /dev/null @@ -1,40 +0,0 @@ -package ru.micord.ervu.security.esia; - -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -/** - * @author Eduard Tihomirov - */ -public class Tokens { - private static final Map accessTokensMap = new ConcurrentHashMap<>(); - private static final Map refreshTokensMap = new ConcurrentHashMap<>(); - - public static void addAccessToken(String prnOid, String token) { - if (token != null) { - accessTokensMap.put(prnOid, token); - } - } - - public static String getAccessToken(String prnOid) { - return accessTokensMap.get(prnOid); - } - - public static void removeAccessToken(String prnOid) { - accessTokensMap.remove(prnOid); - } - - public static void addRefreshToken(String prnOid, String token) { - if (token != null) { - refreshTokensMap.put(prnOid, token); - } - } - - public static String getRefreshToken(String prnOid) { - return refreshTokensMap.get(prnOid); - } - - public static void removeRefreshToken(String prnOid) { - refreshTokensMap.remove(prnOid); - } -} diff --git a/backend/src/main/java/ru/micord/ervu/security/esia/service/EsiaAuthService.java b/backend/src/main/java/ru/micord/ervu/security/esia/service/EsiaAuthService.java index 12842746..564aeac2 100644 --- a/backend/src/main/java/ru/micord/ervu/security/esia/service/EsiaAuthService.java +++ b/backend/src/main/java/ru/micord/ervu/security/esia/service/EsiaAuthService.java @@ -21,8 +21,7 @@ import javax.servlet.http.HttpServletResponse; import com.fasterxml.jackson.databind.ObjectMapper; import ervu.service.okopf.OkopfService; -import org.springframework.security.core.Authentication; -import ru.micord.ervu.security.esia.Tokens; +import ru.micord.ervu.security.esia.token.TokensStore; import org.springframework.security.core.context.SecurityContext; import org.springframework.util.StringUtils; import ru.micord.ervu.security.esia.config.EsiaConfig; @@ -222,9 +221,10 @@ public class EsiaAuthService { String refreshToken = tokenResponse.getRefresh_token(); String prnOid = esiaAccessToken.getSbj_id(); String ervuId = getErvuId(accessToken, prnOid); - Tokens.addAccessToken(prnOid, accessToken); - Tokens.addRefreshToken(prnOid, refreshToken); - Token token = jwtTokenService.createAccessToken(esiaAccessToken.getSbj_id(), tokenResponse.getExpires_in(), ervuId); + Long expiresIn = tokenResponse.getExpires_in(); + TokensStore.addAccessToken(prnOid, accessToken, expiresIn); + TokensStore.addRefreshToken(prnOid, refreshToken, expiresIn); + Token token = jwtTokenService.createAccessToken(esiaAccessToken.getSbj_id(), expiresIn, ervuId); Cookie authToken = new Cookie("auth_token", token.getValue()); authToken.setPath(cookiePath); authToken.setHttpOnly(true); @@ -250,19 +250,7 @@ public class EsiaAuthService { public void getEsiaTokensByRefreshToken(HttpServletRequest request, HttpServletResponse response) { try { - String authTokenCookie = null; - Cookie[] cookies = request.getCookies(); - if (cookies != null) { - for (Cookie cookie : cookies) { - if (cookie.getName().equals("auth_token")) { - authTokenCookie = cookie.getValue(); - } - } - } - Token authJwtToken = jwtTokenService.getToken(authTokenCookie); - String[] split = authJwtToken.getUserAccountId().split(":"); - String userId = split[0]; - String refreshToken = Tokens.getRefreshToken(userId); + String refreshToken = jwtTokenService.getRefreshToken(request); String clientId = esiaConfig.getClientId(); DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy.MM.dd HH:mm:ss xx"); ZonedDateTime dt = ZonedDateTime.now(); @@ -322,10 +310,11 @@ public class EsiaAuthService { String newRefreshToken = tokenResponse.getRefresh_token(); EsiaAccessToken esiaAccessToken = ulDataService.readToken(accessToken); String prnOid = esiaAccessToken.getSbj_id(); - Tokens.addAccessToken(prnOid, accessToken); - Tokens.addRefreshToken(prnOid, newRefreshToken); + Long expiresIn = tokenResponse.getExpires_in(); + TokensStore.addAccessToken(prnOid, accessToken, expiresIn); + TokensStore.addRefreshToken(prnOid, newRefreshToken, expiresIn); String ervuId = getErvuId(accessToken, prnOid); - Token token = jwtTokenService.createAccessToken(esiaAccessToken.getSbj_id(), tokenResponse.getExpires_in(), ervuId); + Token token = jwtTokenService.createAccessToken(esiaAccessToken.getSbj_id(), expiresIn, ervuId); Cookie authToken = new Cookie("auth_token", token.getValue()); authToken.setPath(cookiePath); authToken.setHttpOnly(true); @@ -403,8 +392,8 @@ public class EsiaAuthService { Token token = jwtTokenService.getToken(authToken); String[] ids = token.getUserAccountId().split(":"); String userId = ids[0]; - Tokens.removeAccessToken(userId); - Tokens.removeRefreshToken(userId); + TokensStore.removeAccessToken(userId); + TokensStore.removeRefreshToken(userId); String logoutUrl = esiaConfig.getEsiaBaseUri() + esiaConfig.getEsiaLogoutUrl(); String redirectUrl = esiaConfig.getRedirectUrl(); URL url = new URL(logoutUrl); diff --git a/backend/src/main/java/ru/micord/ervu/security/esia/service/EsiaDataService.java b/backend/src/main/java/ru/micord/ervu/security/esia/service/EsiaDataService.java index 565d6dac..c6469b24 100644 --- a/backend/src/main/java/ru/micord/ervu/security/esia/service/EsiaDataService.java +++ b/backend/src/main/java/ru/micord/ervu/security/esia/service/EsiaDataService.java @@ -1,12 +1,10 @@ package ru.micord.ervu.security.esia.service; import java.util.Arrays; -import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; -import ru.micord.ervu.security.esia.Tokens; import ru.micord.ervu.security.esia.model.Addresses; import ru.micord.ervu.security.esia.model.Contacts; import ru.micord.ervu.security.esia.model.EmployeeModel; diff --git a/backend/src/main/java/ru/micord/ervu/security/esia/token/ExpiringToken.java b/backend/src/main/java/ru/micord/ervu/security/esia/token/ExpiringToken.java new file mode 100644 index 00000000..f6a476e4 --- /dev/null +++ b/backend/src/main/java/ru/micord/ervu/security/esia/token/ExpiringToken.java @@ -0,0 +1,34 @@ +package ru.micord.ervu.security.esia.token; + +/** + * @author Eduard Tihomirov + */ +public class ExpiringToken { + private String accessToken; + private long expiryTime; + + public ExpiringToken(String accessToken, long expiryTime) { + this.accessToken = accessToken; + this.expiryTime = expiryTime; + } + + public String getAccessToken() { + return accessToken; + } + + public void setAccessToken(String accessToken) { + this.accessToken = accessToken; + } + + public long getExpiryTime() { + return expiryTime; + } + + public void setExpiryTime(long expiryTime) { + this.expiryTime = expiryTime; + } + + boolean isExpired() { + return System.currentTimeMillis() > expiryTime; + } +} diff --git a/backend/src/main/java/ru/micord/ervu/security/esia/token/TokensClearShedulerService.java b/backend/src/main/java/ru/micord/ervu/security/esia/token/TokensClearShedulerService.java new file mode 100644 index 00000000..46652958 --- /dev/null +++ b/backend/src/main/java/ru/micord/ervu/security/esia/token/TokensClearShedulerService.java @@ -0,0 +1,20 @@ +package ru.micord.ervu.security.esia.token; + +import net.javacrumbs.shedlock.core.SchedulerLock; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +/** + * @author Eduard Tihomirov + */ +@Service +public class TokensClearShedulerService { + @Scheduled(cron = "${esia.token.clear.cron:0 0 */1 * * *}") + @SchedulerLock(name = "clearToken") + @Transactional + public void load() { + TokensStore.removeExpiredRefreshToken(); + TokensStore.removeExpiredAccessToken(); + } +} diff --git a/backend/src/main/java/ru/micord/ervu/security/esia/token/TokensStore.java b/backend/src/main/java/ru/micord/ervu/security/esia/token/TokensStore.java new file mode 100644 index 00000000..2c93fc1c --- /dev/null +++ b/backend/src/main/java/ru/micord/ervu/security/esia/token/TokensStore.java @@ -0,0 +1,61 @@ +package ru.micord.ervu.security.esia.token; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * @author Eduard Tihomirov + */ +public class TokensStore { + private static final Map accessTokensMap = new ConcurrentHashMap<>(); + private static final Map refreshTokensMap = new ConcurrentHashMap<>(); + + public static void addAccessToken(String prnOid, String token, long expiresIn) { + if (token != null) { + long expiryTime = System.currentTimeMillis() + 1000L * expiresIn; + accessTokensMap.put(prnOid, new ExpiringToken(token, expiryTime)); + } + } + + public static String getAccessToken(String prnOid) { + return accessTokensMap.get(prnOid).getAccessToken(); + } + + public static void removeExpiredAccessToken() { + for (String key : accessTokensMap.keySet()) { + ExpiringToken token = accessTokensMap.get(key); + if (token != null && token.isExpired()) { + accessTokensMap.remove(key); + } + } + } + + public static void removeExpiredRefreshToken() { + for (String key : refreshTokensMap.keySet()) { + ExpiringToken token = refreshTokensMap.get(key); + if (token != null && token.isExpired()) { + refreshTokensMap.remove(key); + } + } + } + + public static void removeAccessToken(String prnOid) { + accessTokensMap.remove(prnOid); + } + + public static void addRefreshToken(String prnOid, String token, long expiresIn) { + if (token != null) { + long expiryTime = System.currentTimeMillis() + 1000L * expiresIn; + refreshTokensMap.put(prnOid, new ExpiringToken(token, expiryTime)); + } + } + + public static String getRefreshToken(String prnOid) { + return refreshTokensMap.get(prnOid).getAccessToken(); + } + + public static void removeRefreshToken(String prnOid) { + refreshTokensMap.remove(prnOid); + } + +} diff --git a/backend/src/main/java/ru/micord/ervu/security/webbpm/jwt/service/JwtTokenService.java b/backend/src/main/java/ru/micord/ervu/security/webbpm/jwt/service/JwtTokenService.java index e7d219bf..be0833ed 100644 --- a/backend/src/main/java/ru/micord/ervu/security/webbpm/jwt/service/JwtTokenService.java +++ b/backend/src/main/java/ru/micord/ervu/security/webbpm/jwt/service/JwtTokenService.java @@ -15,7 +15,7 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; -import ru.micord.ervu.security.esia.Tokens; +import ru.micord.ervu.security.esia.token.TokensStore; import ru.micord.ervu.security.webbpm.jwt.model.Token; import ru.cg.webbpm.modules.resources.api.ResourceMetadataUtils; @@ -77,8 +77,16 @@ public class JwtTokenService { } public String getAccessToken(HttpServletRequest request) { - Cookie[] cookies = request.getCookies(); + return TokensStore.getAccessToken(getUserAccountId(request)); + } + + public String getRefreshToken(HttpServletRequest request) { + return TokensStore.getRefreshToken(getUserAccountId(request)); + } + + private String getUserAccountId(HttpServletRequest request) { String authToken = null; + Cookie[] cookies = request.getCookies(); if (cookies != null) { for (Cookie cookie : cookies) { if (cookie.getName().equals("auth_token")) { @@ -88,8 +96,7 @@ public class JwtTokenService { } if (authToken != null) { String[] ids = getToken(authToken).getUserAccountId().split(":"); - String userId = ids[0]; - return Tokens.getAccessToken(userId); + return ids[0]; } else { throw new RuntimeException("Failed to get auth data. User unauthorized."); diff --git a/config/micord.env b/config/micord.env index 34ef2345..d0553a83 100644 --- a/config/micord.env +++ b/config/micord.env @@ -46,3 +46,5 @@ ERVU_KAFKA_EXCERPT_REQUEST_TOPIC=ervu.lkrp.excerpt.request S3_ENDPOINT=http://ervu-minio.k8s.micord.ru:31900 S3_ACCESS_KEY=rlTdTvkmSXu9FsLhfecw S3_SECRET_KEY=NUmY0wwRIEyAd98GCKd1cOgJWvLQYAcMMul5Ulu0 + +ESIA_TOKEN_CLEAR_CRON=0 0 */1 * * * diff --git a/config/standalone/dev/standalone.xml b/config/standalone/dev/standalone.xml index a15698dd..7236e7fe 100644 --- a/config/standalone/dev/standalone.xml +++ b/config/standalone/dev/standalone.xml @@ -94,6 +94,7 @@ + From 0e1f6e3cabab51185c97123737358a2e5c00a0fb Mon Sep 17 00:00:00 2001 From: Eduard Tihomirov Date: Mon, 7 Oct 2024 13:33:50 +0300 Subject: [PATCH 08/26] SUPPORT-8579: Fix --- .../micord/ervu/security/esia/service/EsiaAuthService.java | 6 +----- .../ervu/security/webbpm/jwt/service/JwtTokenService.java | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/backend/src/main/java/ru/micord/ervu/security/esia/service/EsiaAuthService.java b/backend/src/main/java/ru/micord/ervu/security/esia/service/EsiaAuthService.java index 564aeac2..74279c59 100644 --- a/backend/src/main/java/ru/micord/ervu/security/esia/service/EsiaAuthService.java +++ b/backend/src/main/java/ru/micord/ervu/security/esia/service/EsiaAuthService.java @@ -372,7 +372,6 @@ public class EsiaAuthService { public String logout(HttpServletRequest request, HttpServletResponse response) { try { Cookie[] cookies = request.getCookies(); - String authToken = null; if (cookies != null) for (Cookie cookie : cookies) { if (cookie.getName().equals("webbpm.ervu-lkrp-ul")) { @@ -382,16 +381,13 @@ public class EsiaAuthService { response.addCookie(cookie); } else if (cookie.getName().equals("auth_token")) { - authToken = cookie.getValue(); cookie.setValue(""); cookie.setPath(cookie.getPath()); cookie.setMaxAge(0); response.addCookie(cookie); } } - Token token = jwtTokenService.getToken(authToken); - String[] ids = token.getUserAccountId().split(":"); - String userId = ids[0]; + String userId = jwtTokenService.getUserAccountId(request); TokensStore.removeAccessToken(userId); TokensStore.removeRefreshToken(userId); String logoutUrl = esiaConfig.getEsiaBaseUri() + esiaConfig.getEsiaLogoutUrl(); diff --git a/backend/src/main/java/ru/micord/ervu/security/webbpm/jwt/service/JwtTokenService.java b/backend/src/main/java/ru/micord/ervu/security/webbpm/jwt/service/JwtTokenService.java index be0833ed..414b6334 100644 --- a/backend/src/main/java/ru/micord/ervu/security/webbpm/jwt/service/JwtTokenService.java +++ b/backend/src/main/java/ru/micord/ervu/security/webbpm/jwt/service/JwtTokenService.java @@ -84,7 +84,7 @@ public class JwtTokenService { return TokensStore.getRefreshToken(getUserAccountId(request)); } - private String getUserAccountId(HttpServletRequest request) { + public String getUserAccountId(HttpServletRequest request) { String authToken = null; Cookie[] cookies = request.getCookies(); if (cookies != null) { From bb92c71194498dabfcc6347ede1a9d9629551a9f Mon Sep 17 00:00:00 2001 From: kochetkov Date: Tue, 15 Oct 2024 15:14:21 +0300 Subject: [PATCH 09/26] SUPPORT-8609 add csrf cookie protection --- backend/src/main/java/SecurityInit.java | 5 +++ .../CsrfTokenAwareLogoutSuccessHandler.java | 34 +++++++++++++++++++ .../micord/ervu/security/SecurityConfig.java | 25 +++++++++++--- .../ervu/security/SecurityConstants.java | 5 +++ .../esia/controller/EsiaController.java | 19 ++++++----- .../esia/service/EsiaAuthService.java | 12 +++++-- .../template/app/component/log_out.html | 6 ++-- .../component/fileupload/ErvuFileUpload.ts | 28 ++++++++++++++- frontend/src/ts/esia/OrgDataRoot.ts | 15 ++++---- frontend/src/ts/modules/app/app.module.ts | 14 +++++++- .../modules/app/component/logout.component.ts | 17 ++++------ .../src/ts/modules/security/TokenConstants.ts | 4 +++ .../security/authentication.service.ts | 27 +++++++++++++++ .../ts/modules/security/guard/auth.guard.ts | 12 +++---- .../absolute-url-csrf.interceptor.ts | 26 ++++++++++++++ .../interceptor/default-interceptors.prod.ts | 4 ++- .../interceptor/default-interceptors.ts | 4 ++- .../src/ts/modules/webbpm/webbpm.module.ts | 7 +++- pom.xml | 21 ++++++++++++ 19 files changed, 237 insertions(+), 48 deletions(-) create mode 100644 backend/src/main/java/SecurityInit.java create mode 100644 backend/src/main/java/ru/micord/ervu/security/CsrfTokenAwareLogoutSuccessHandler.java create mode 100644 backend/src/main/java/ru/micord/ervu/security/SecurityConstants.java create mode 100644 frontend/src/ts/modules/security/TokenConstants.ts create mode 100644 frontend/src/ts/modules/security/authentication.service.ts create mode 100644 frontend/src/ts/modules/webbpm/interceptor/absolute-url-csrf.interceptor.ts diff --git a/backend/src/main/java/SecurityInit.java b/backend/src/main/java/SecurityInit.java new file mode 100644 index 00000000..00b8b504 --- /dev/null +++ b/backend/src/main/java/SecurityInit.java @@ -0,0 +1,5 @@ +import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer; +import org.springframework.web.WebApplicationInitializer; + +public class SecurityInit extends AbstractSecurityWebApplicationInitializer implements WebApplicationInitializer { +} diff --git a/backend/src/main/java/ru/micord/ervu/security/CsrfTokenAwareLogoutSuccessHandler.java b/backend/src/main/java/ru/micord/ervu/security/CsrfTokenAwareLogoutSuccessHandler.java new file mode 100644 index 00000000..8c9b1895 --- /dev/null +++ b/backend/src/main/java/ru/micord/ervu/security/CsrfTokenAwareLogoutSuccessHandler.java @@ -0,0 +1,34 @@ +package ru.micord.ervu.security; + +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.http.HttpStatus; +import org.springframework.security.core.Authentication; +import org.springframework.security.web.authentication.logout.HttpStatusReturningLogoutSuccessHandler; +import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; +import org.springframework.security.web.csrf.CsrfToken; +import org.springframework.security.web.csrf.CsrfTokenRepository; + +public class CsrfTokenAwareLogoutSuccessHandler implements LogoutSuccessHandler { + + private final CsrfTokenRepository csrfTokenRepository; + + private final LogoutSuccessHandler delegate = new HttpStatusReturningLogoutSuccessHandler(HttpStatus.OK); + + public CsrfTokenAwareLogoutSuccessHandler(CsrfTokenRepository csrfTokenRepository) { + this.csrfTokenRepository = csrfTokenRepository; + } + + @Override + public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, + ServletException { + CsrfToken csrfToken = this.csrfTokenRepository.generateToken(request); + this.csrfTokenRepository.saveToken(csrfToken, request, response); + this.delegate.onLogoutSuccess(request, response, authentication); + } + +} diff --git a/backend/src/main/java/ru/micord/ervu/security/SecurityConfig.java b/backend/src/main/java/ru/micord/ervu/security/SecurityConfig.java index 44a4223b..6df367d7 100644 --- a/backend/src/main/java/ru/micord/ervu/security/SecurityConfig.java +++ b/backend/src/main/java/ru/micord/ervu/security/SecurityConfig.java @@ -10,9 +10,14 @@ import org.springframework.security.config.annotation.web.configuration.WebSecur import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.security.web.csrf.CookieCsrfTokenRepository; +import org.springframework.security.web.csrf.CsrfTokenRequestHandler; +import org.springframework.security.web.csrf.XorCsrfTokenRequestAttributeHandler; import ru.micord.ervu.security.webbpm.jwt.filter.JwtAuthenticationFilter; import ru.micord.ervu.security.webbpm.jwt.UnauthorizedEntryPoint; +import static ru.micord.ervu.security.SecurityConstants.ESIA_LOGOUT; + @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @@ -22,19 +27,31 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { - httpConfigure(http); http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); } protected void httpConfigure(HttpSecurity httpSecurity) throws Exception { - String[] permitAll = {"/esia/url", "/esia/auth", "esia/refresh"}; - + String[] permitAll = {"/version","/esia/url", "/esia/auth", "esia/refresh"}; + CookieCsrfTokenRepository tokenRepository = CookieCsrfTokenRepository.withHttpOnlyFalse(); + tokenRepository.setCookiePath("/"); + XorCsrfTokenRequestAttributeHandler delegate = new XorCsrfTokenRequestAttributeHandler(); + delegate.setCsrfRequestAttributeName(null); + // Use only the handle() method of XorCsrfTokenRequestAttributeHandler and the + // default implementation of resolveCsrfTokenValue() from CsrfTokenRequestHandler + CsrfTokenRequestHandler requestHandler = delegate::handle; httpSecurity.authorizeRequests() .antMatchers(permitAll).permitAll() .antMatchers("/**").authenticated() .and() - .csrf().disable() + .csrf((csrf) -> csrf + .csrfTokenRepository(tokenRepository) + .csrfTokenRequestHandler(requestHandler) + ) + .logout((logout) -> logout + .logoutUrl(ESIA_LOGOUT) + .logoutSuccessHandler(new CsrfTokenAwareLogoutSuccessHandler(tokenRepository)) + ) .exceptionHandling().authenticationEntryPoint(entryPoint()) .and() .sessionManagement() diff --git a/backend/src/main/java/ru/micord/ervu/security/SecurityConstants.java b/backend/src/main/java/ru/micord/ervu/security/SecurityConstants.java new file mode 100644 index 00000000..1853716d --- /dev/null +++ b/backend/src/main/java/ru/micord/ervu/security/SecurityConstants.java @@ -0,0 +1,5 @@ +package ru.micord.ervu.security; + +public class SecurityConstants { + public static final String ESIA_LOGOUT = "/esia/logout"; +} diff --git a/backend/src/main/java/ru/micord/ervu/security/esia/controller/EsiaController.java b/backend/src/main/java/ru/micord/ervu/security/esia/controller/EsiaController.java index d408ff4d..42aecf09 100644 --- a/backend/src/main/java/ru/micord/ervu/security/esia/controller/EsiaController.java +++ b/backend/src/main/java/ru/micord/ervu/security/esia/controller/EsiaController.java @@ -3,12 +3,13 @@ package ru.micord.ervu.security.esia.controller; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import ru.micord.ervu.security.SecurityConstants; import ru.micord.ervu.security.esia.model.OrgInfoModel; import ru.micord.ervu.security.esia.service.EsiaAuthService; import ru.micord.ervu.security.esia.service.EsiaDataService; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @@ -24,37 +25,37 @@ public class EsiaController { @Autowired private EsiaDataService esiaDataService; - @RequestMapping(value = "/esia/url") + @GetMapping(value = "/esia/url") public String getEsiaUrl() { return esiaAuthService.generateAuthCodeUrl(); } - @RequestMapping(value = "/esia/auth", params = "code", method = RequestMethod.GET) + @GetMapping(value = "/esia/auth", params = "code") public boolean esiaAuth(@RequestParam("code") String code, HttpServletRequest request, HttpServletResponse response) { return esiaAuthService.getEsiaTokensByCode(code, request, response); } - @RequestMapping(value = "/esia/refresh") + @PostMapping(value = "/esia/refresh") public void refreshToken(HttpServletRequest request, HttpServletResponse response) { esiaAuthService.getEsiaTokensByRefreshToken(request, response); } - @RequestMapping(value = "/esia/org") + @GetMapping(value = "/esia/org") public OrgInfoModel getOrgInfo(HttpServletRequest request) { return esiaDataService.getOrgInfo(request); } - @RequestMapping(value = "/esia/userfullname") + @GetMapping(value = "/esia/userfullname") public String getUserFullname(HttpServletRequest request) { return esiaDataService.getUserFullname(request); } - @RequestMapping(value = "/esia/orgunitname") + @GetMapping(value = "/esia/orgunitname") public String getOrgUnitName(HttpServletRequest request) { return esiaDataService.getOrgUnitName(request); } - @RequestMapping(value = "/esia/logout") + @PostMapping(SecurityConstants.ESIA_LOGOUT) public String logout(HttpServletRequest request, HttpServletResponse response) { return esiaAuthService.logout(request, response); } diff --git a/backend/src/main/java/ru/micord/ervu/security/esia/service/EsiaAuthService.java b/backend/src/main/java/ru/micord/ervu/security/esia/service/EsiaAuthService.java index a0170d61..e035a42a 100644 --- a/backend/src/main/java/ru/micord/ervu/security/esia/service/EsiaAuthService.java +++ b/backend/src/main/java/ru/micord/ervu/security/esia/service/EsiaAuthService.java @@ -200,11 +200,19 @@ public class EsiaAuthService { .build() .send(postReq, HttpResponse.BodyHandlers.ofString()); String responseString = postResp.body(); - EsiaTokenResponse tokenResponse = objectMapper.readValue(responseString, EsiaTokenResponse.class); - if (tokenResponse != null && tokenResponse.getError() != null) { + EsiaTokenResponse tokenResponse = objectMapper.readValue(responseString, + EsiaTokenResponse.class + ); + + if (tokenResponse == null) { + throw new IllegalStateException("Got empty esia response"); + } + + if (tokenResponse.getError() != null) { throw new RuntimeException(tokenResponse.getError_description()); } String accessToken = tokenResponse.getAccess_token(); + boolean hasRole = ulDataService.checkRole(accessToken); if (!hasRole) { throw new RuntimeException("The user does not have the required role"); diff --git a/frontend/src/resources/template/app/component/log_out.html b/frontend/src/resources/template/app/component/log_out.html index cf7831d2..319b0904 100644 --- a/frontend/src/resources/template/app/component/log_out.html +++ b/frontend/src/resources/template/app/component/log_out.html @@ -1,6 +1,6 @@ - -
+ +
{{getOrgUnitName()}}
Данные организации -
\ No newline at end of file +
diff --git a/frontend/src/ts/ervu/component/fileupload/ErvuFileUpload.ts b/frontend/src/ts/ervu/component/fileupload/ErvuFileUpload.ts index 3e0da6a8..4ed140be 100644 --- a/frontend/src/ts/ervu/component/fileupload/ErvuFileUpload.ts +++ b/frontend/src/ts/ervu/component/fileupload/ErvuFileUpload.ts @@ -11,6 +11,8 @@ import {ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef} from import {FileItem, FileUploader} from "ng2-file-upload"; import {FileLikeObject} from "ng2-file-upload/file-upload/file-like-object.class"; import {EmployeeInfoFileFormType} from "./EmployeeInfoFileFormType"; +import {CookieService} from "ngx-cookie"; +import {TokenConstants} from "../../../modules/security/TokenConstants"; @Component({ moduleId: module.id, @@ -59,6 +61,7 @@ export class ErvuFileUpload extends InputControl { private messagesService: MessagesService; private isUploadErrorOccurred = false; private appConfigService: AppConfigService; + private cookieService: CookieService; constructor(el: ElementRef, cd: ChangeDetectorRef) { super(el, cd); @@ -69,6 +72,7 @@ export class ErvuFileUpload extends InputControl { super.initialize(); this.messagesService = this.injector.get(MessagesService); this.appConfigService = this.injector.get(AppConfigService); + this.cookieService = this.injector.get(CookieService); this.url = `/${this.appConfigService.getParamValue(ErvuFileUpload.BACKEND_URL)}/employee/document`; this.uploader.setOptions({ @@ -99,6 +103,10 @@ export class ErvuFileUpload extends InputControl { { name: "Client-Time-Zone", value: Intl.DateTimeFormat().resolvedOptions().timeZone + }, + { + name: TokenConstants.CSRF_HEADER_NAME, + value: this.cookieService.get(TokenConstants.CSRF_TOKEN_NAME) } ] }); @@ -129,6 +137,24 @@ export class ErvuFileUpload extends InputControl { private setUploaderMethods() { this.uploader.onBeforeUploadItem = (fileItem: FileItem) => { + + //refresh headers + this.uploader.setOptions({ + headers: [ + { + name: "X-Employee-Info-File-Form-Type", + value: EmployeeInfoFileFormType[this.formType] + }, + { + name: "Client-Time-Zone", + value: Intl.DateTimeFormat().resolvedOptions().timeZone + }, + { + name: TokenConstants.CSRF_HEADER_NAME, + value: this.cookieService.get(TokenConstants.CSRF_TOKEN_NAME) + } + ] + }); this.fileUploadStartEvent.trigger(); this.isDropZoneVisible = false; this.isFilesListVisible = false; @@ -239,4 +265,4 @@ export class ErvuFileUpload extends InputControl { this.isUploadErrorOccurred = false; this.cd.markForCheck(); } -} \ No newline at end of file +} diff --git a/frontend/src/ts/esia/OrgDataRoot.ts b/frontend/src/ts/esia/OrgDataRoot.ts index feee9eb9..901362ed 100644 --- a/frontend/src/ts/esia/OrgDataRoot.ts +++ b/frontend/src/ts/esia/OrgDataRoot.ts @@ -2,10 +2,10 @@ import {AnalyticalScope, Behavior, Container, ControlWithValue} from "@webbpm/ba import {HttpClient} from "@angular/common/http"; import {OrgData} from "./OrgData"; import {OrgInfoModel} from "../generated/ru/micord/ervu/security/esia/model/OrgInfoModel"; -import {CookieService} from "ngx-cookie"; +import {AuthenticationService} from "../modules/security/authentication.service"; @AnalyticalScope(Container) -export class OrgDataRoot extends Behavior{ +export class OrgDataRoot extends Behavior { private container: Container; @@ -14,8 +14,9 @@ export class OrgDataRoot extends Behavior{ this.container = this.getScript(Container); let orgScripts: OrgData[] = this.container.getScriptsInThisAndChildren(OrgData); let httpClient = this.injector.get(HttpClient); - let cookieService = this.injector.get(CookieService); - if (cookieService.get("webbpm.ervu-lkrp-ul")) { + let cookieService = this.injector.get(AuthenticationService); + + if (cookieService.isAuthenticated()) { httpClient.get("esia/org") .toPromise() .then(orgInfoModel => { @@ -23,9 +24,9 @@ export class OrgDataRoot extends Behavior{ return; } for (let orgData of orgScripts) { - let control: ControlWithValue = orgData.getScriptInObject(orgData.getObjectId(), - 'component.ControlWithValue'); - control.setValue(orgInfoModel[orgData.dataId]); + let control: ControlWithValue = orgData.getScriptInObject(orgData.getObjectId(), + 'component.ControlWithValue'); + control.setValue(orgInfoModel[orgData.dataId]); } }); } diff --git a/frontend/src/ts/modules/app/app.module.ts b/frontend/src/ts/modules/app/app.module.ts index 092f4970..b752669f 100644 --- a/frontend/src/ts/modules/app/app.module.ts +++ b/frontend/src/ts/modules/app/app.module.ts @@ -1,4 +1,4 @@ -import {forwardRef, NgModule} from "@angular/core"; +import {APP_INITIALIZER, forwardRef, NgModule} from "@angular/core"; import {NgbModule} from "@ng-bootstrap/ng-bootstrap"; import {CommonModule, registerLocaleData} from "@angular/common"; import localeRu from '@angular/common/locales/ru'; @@ -25,6 +25,7 @@ import {FileUploadModule} from "ng2-file-upload"; import {ErvuFileUpload} from "../../ervu/component/fileupload/ErvuFileUpload"; import {InMemoryStaticGrid} from "../../ervu/component/grid/InMemoryStaticGrid"; import {ErvuDownloadFileButton} from "../../ervu/component/button/ErvuDownloadFileButton"; +import {AuthenticationService} from "../security/authentication.service"; registerLocaleData(localeRu); export const DIRECTIVES = [ @@ -39,6 +40,10 @@ export const DIRECTIVES = [ forwardRef(() => InMemoryStaticGrid) ]; +function checkAuthentication(authService: AuthenticationService): () => Promise { + return () => authService.checkAuthentication(); +} + @NgModule({ imports: [ CommonModule, @@ -60,6 +65,13 @@ export const DIRECTIVES = [ DIRECTIVES ], providers: [ + AuthenticationService, + { + provide: APP_INITIALIZER, + useFactory: checkAuthentication, + deps: [AuthenticationService], + multi: true, + }, { provide: ProgressIndicationService, useClass: AppProgressIndicationService } ], bootstrap: [], diff --git a/frontend/src/ts/modules/app/component/logout.component.ts b/frontend/src/ts/modules/app/component/logout.component.ts index e8c3b8be..0ac178ae 100644 --- a/frontend/src/ts/modules/app/component/logout.component.ts +++ b/frontend/src/ts/modules/app/component/logout.component.ts @@ -1,7 +1,7 @@ import {ChangeDetectorRef, Component, OnInit} from "@angular/core"; import {Router} from "@angular/router"; import {HttpClient} from "@angular/common/http"; -import {CookieService} from "ngx-cookie"; +import {AuthenticationService} from "../../security/authentication.service"; @Component({ moduleId: module.id, @@ -13,13 +13,12 @@ export class LogOutComponent implements OnInit{ private userFullname: string; private orgUnitName: string; - constructor(private router: Router, private httpClient: HttpClient, - private cookieService: CookieService, private cd: ChangeDetectorRef) { + private authenticationService: AuthenticationService, private cd: ChangeDetectorRef) { } ngOnInit(): void { - let isAuth = this.getIsAuth(); + let isAuth = this.authenticationService.isAuthenticated(); if (isAuth) { Promise.all([ this.httpClient.get("esia/userfullname").toPromise(), @@ -33,20 +32,18 @@ export class LogOutComponent implements OnInit{ } public logout(): void { - this.httpClient.get("esia/logout").toPromise().then(url => { - window.open(url, "_self"); - }) + this.authenticationService.logout(); } public getUserFullname(): string { return this.userFullname; } - public getIsAuth(): boolean { - return this.cookieService.get("webbpm.ervu-lkrp-ul") != null; + public isAuthenticated(): boolean { + return this.authenticationService.isAuthenticated(); } public getOrgUnitName(): string { return this.orgUnitName; } -} \ No newline at end of file +} diff --git a/frontend/src/ts/modules/security/TokenConstants.ts b/frontend/src/ts/modules/security/TokenConstants.ts new file mode 100644 index 00000000..597fe759 --- /dev/null +++ b/frontend/src/ts/modules/security/TokenConstants.ts @@ -0,0 +1,4 @@ +export class TokenConstants { + public static readonly CSRF_TOKEN_NAME = "XSRF-TOKEN"; + public static readonly CSRF_HEADER_NAME = "X-XSRF-TOKEN"; +} diff --git a/frontend/src/ts/modules/security/authentication.service.ts b/frontend/src/ts/modules/security/authentication.service.ts new file mode 100644 index 00000000..f427b3d1 --- /dev/null +++ b/frontend/src/ts/modules/security/authentication.service.ts @@ -0,0 +1,27 @@ +import {Injectable} from '@angular/core'; +import {HttpClient} from '@angular/common/http'; +import {Observable} from "rxjs"; +import {CookieService} from "ngx-cookie"; +import {tap} from "rxjs/operators"; +import {AppConfigService} from "@webbpm/base-package"; + +@Injectable({providedIn: 'root'}) +export class AuthenticationService { + + constructor(private http: HttpClient, + private cookieService: CookieService, + private appConfigService: AppConfigService) { + } + + checkAuthentication(): Promise{ + return this.appConfigService.load().then(value => this.http.get("version").toPromise()) + } + + logout(): Promise { + return this.http.post('esia/logout', {}).toPromise(); + } + + public isAuthenticated(): boolean { + return this.cookieService.get('webbpm.ervu-lkrp-ul') != null; + } +} diff --git a/frontend/src/ts/modules/security/guard/auth.guard.ts b/frontend/src/ts/modules/security/guard/auth.guard.ts index 95c2271e..b41e5b3a 100644 --- a/frontend/src/ts/modules/security/guard/auth.guard.ts +++ b/frontend/src/ts/modules/security/guard/auth.guard.ts @@ -2,8 +2,8 @@ import {Injectable} from "@angular/core"; import {ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot} from "@angular/router"; import {Observable} from "rxjs"; import {HttpClient, HttpParams} from "@angular/common/http"; -import {CookieService} from "ngx-cookie"; import {MessagesService} from "@webbpm/base-package"; +import {AuthenticationService} from "../authentication.service"; @Injectable({providedIn:'root'}) export abstract class AuthGuard implements CanActivate { @@ -11,7 +11,7 @@ export abstract class AuthGuard implements CanActivate { protected constructor( protected router: Router, private httpClient: HttpClient, - private cookieService: CookieService, + private authenticationService: AuthenticationService, private messageService: MessagesService ) { } @@ -55,11 +55,7 @@ export abstract class AuthGuard implements CanActivate { }); } - private checkAccess(): Promise | boolean { - return this.getIsAuth() != null; + private checkAccess(): boolean { + return this.authenticationService.isAuthenticated(); }; - - public getIsAuth(): string { - return this.cookieService.get('webbpm.ervu-lkrp-ul'); - } } diff --git a/frontend/src/ts/modules/webbpm/interceptor/absolute-url-csrf.interceptor.ts b/frontend/src/ts/modules/webbpm/interceptor/absolute-url-csrf.interceptor.ts new file mode 100644 index 00000000..ea231886 --- /dev/null +++ b/frontend/src/ts/modules/webbpm/interceptor/absolute-url-csrf.interceptor.ts @@ -0,0 +1,26 @@ +import {HttpEvent, HttpHandler, HttpInterceptor, HttpRequest} from '@angular/common/http'; +import {Injectable} from '@angular/core'; +import {Observable} from 'rxjs'; +import {CookieService} from "ngx-cookie"; +import {TokenConstants} from "../../security/TokenConstants"; + +@Injectable() +export class AbsoluteUrlCsrfInterceptor implements HttpInterceptor { + + constructor(private cookieService: CookieService) { + } + + intercept(req: HttpRequest, next: HttpHandler): Observable> { + + let requestToForward = req; + let token = this.cookieService.get(TokenConstants.CSRF_TOKEN_NAME) as string; + + if (token != null) { + let headers = {}; + let headerName = TokenConstants.CSRF_HEADER_NAME; + headers[headerName] = token; + requestToForward = req.clone({setHeaders: headers}); + } + return next.handle(requestToForward); + } +} diff --git a/frontend/src/ts/modules/webbpm/interceptor/default-interceptors.prod.ts b/frontend/src/ts/modules/webbpm/interceptor/default-interceptors.prod.ts index 07735d52..6cd9ffe7 100644 --- a/frontend/src/ts/modules/webbpm/interceptor/default-interceptors.prod.ts +++ b/frontend/src/ts/modules/webbpm/interceptor/default-interceptors.prod.ts @@ -4,9 +4,11 @@ import { HttpSecurityErrorInterceptor, HttpSecurityInterceptor } from "@webbpm/base-package"; +import {AbsoluteUrlCsrfInterceptor} from "./absolute-url-csrf.interceptor"; export const DEFAULT_HTTP_INTERCEPTOR_PROVIDERS = [ {provide: HTTP_INTERCEPTORS, useClass: HttpSecurityInterceptor, multi: true}, {provide: HTTP_INTERCEPTORS, useClass: HttpSecurityErrorInterceptor, multi: true}, - {provide: HTTP_INTERCEPTORS, useClass: FormDirtyInterceptor, multi: true} + {provide: HTTP_INTERCEPTORS, useClass: FormDirtyInterceptor, multi: true}, + {provide: HTTP_INTERCEPTORS, useClass: AbsoluteUrlCsrfInterceptor, multi: true} ]; diff --git a/frontend/src/ts/modules/webbpm/interceptor/default-interceptors.ts b/frontend/src/ts/modules/webbpm/interceptor/default-interceptors.ts index ee46e0c2..77b3b401 100644 --- a/frontend/src/ts/modules/webbpm/interceptor/default-interceptors.ts +++ b/frontend/src/ts/modules/webbpm/interceptor/default-interceptors.ts @@ -1,9 +1,11 @@ import {HTTP_INTERCEPTORS} from "@angular/common/http"; import {FormDirtyInterceptor, HttpSecurityInterceptor} from "@webbpm/base-package"; import {DevHttpSecurityErrorInterceptor} from "./http-security-error-interceptor.dev"; +import {AbsoluteUrlCsrfInterceptor} from "./absolute-url-csrf.interceptor"; export const DEFAULT_HTTP_INTERCEPTOR_PROVIDERS = [ {provide: HTTP_INTERCEPTORS, useClass: HttpSecurityInterceptor, multi: true}, {provide: HTTP_INTERCEPTORS, useClass: DevHttpSecurityErrorInterceptor, multi: true}, - {provide: HTTP_INTERCEPTORS, useClass: FormDirtyInterceptor, multi: true} + {provide: HTTP_INTERCEPTORS, useClass: FormDirtyInterceptor, multi: true}, + {provide: HTTP_INTERCEPTORS, useClass: AbsoluteUrlCsrfInterceptor, multi: true}, ]; diff --git a/frontend/src/ts/modules/webbpm/webbpm.module.ts b/frontend/src/ts/modules/webbpm/webbpm.module.ts index c9ef120c..3326d6e7 100644 --- a/frontend/src/ts/modules/webbpm/webbpm.module.ts +++ b/frontend/src/ts/modules/webbpm/webbpm.module.ts @@ -17,6 +17,8 @@ import { import {AppRoutingModule} from "../app/app-routing.module"; import {GlobalErrorHandler} from "./handler/global-error.handler.prod"; import {DEFAULT_HTTP_INTERCEPTOR_PROVIDERS} from "./interceptor/default-interceptors.prod"; +import {HttpClientModule, HttpClientXsrfModule} from "@angular/common/http"; +import {TokenConstants} from "../security/TokenConstants"; let IMPORTS = [ BrowserAnimationsModule, @@ -30,7 +32,10 @@ let IMPORTS = [ CoreModule, ComponentsModule, AppModule, - WebbpmRoutingModule + WebbpmRoutingModule, + HttpClientModule, + HttpClientXsrfModule.withOptions( + {cookieName: TokenConstants.CSRF_TOKEN_NAME, headerName: TokenConstants.CSRF_HEADER_NAME}) ]; @NgModule({ diff --git a/pom.xml b/pom.xml index 6d975700..c055fd73 100644 --- a/pom.xml +++ b/pom.xml @@ -26,6 +26,27 @@ + + org.springframework.security + spring-security-bom + 5.8.14 + import + pom + + + com.fasterxml.jackson.core + jackson-core + + + com.fasterxml.jackson.core + jackson-databind + + + com.fasterxml.jackson.core + jackson-annotations + + + ru.cg.webbpm platform-bom From 454562b92c8a2f723bd997bdbfa7bb050b9b8a39 Mon Sep 17 00:00:00 2001 From: kochetkov Date: Wed, 16 Oct 2024 16:42:08 +0300 Subject: [PATCH 10/26] SUPPORT-8609 small fix --- .../CsrfTokenAwareLogoutSuccessHandler.java | 34 ------------------- .../ervu/security/LogoutSuccessHandler.java | 34 +++++++++++++++++++ .../micord/ervu/security/SecurityConfig.java | 5 ++- .../esia/controller/EsiaController.java | 5 --- 4 files changed, 38 insertions(+), 40 deletions(-) delete mode 100644 backend/src/main/java/ru/micord/ervu/security/CsrfTokenAwareLogoutSuccessHandler.java create mode 100644 backend/src/main/java/ru/micord/ervu/security/LogoutSuccessHandler.java diff --git a/backend/src/main/java/ru/micord/ervu/security/CsrfTokenAwareLogoutSuccessHandler.java b/backend/src/main/java/ru/micord/ervu/security/CsrfTokenAwareLogoutSuccessHandler.java deleted file mode 100644 index 8c9b1895..00000000 --- a/backend/src/main/java/ru/micord/ervu/security/CsrfTokenAwareLogoutSuccessHandler.java +++ /dev/null @@ -1,34 +0,0 @@ -package ru.micord.ervu.security; - -import java.io.IOException; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.springframework.http.HttpStatus; -import org.springframework.security.core.Authentication; -import org.springframework.security.web.authentication.logout.HttpStatusReturningLogoutSuccessHandler; -import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; -import org.springframework.security.web.csrf.CsrfToken; -import org.springframework.security.web.csrf.CsrfTokenRepository; - -public class CsrfTokenAwareLogoutSuccessHandler implements LogoutSuccessHandler { - - private final CsrfTokenRepository csrfTokenRepository; - - private final LogoutSuccessHandler delegate = new HttpStatusReturningLogoutSuccessHandler(HttpStatus.OK); - - public CsrfTokenAwareLogoutSuccessHandler(CsrfTokenRepository csrfTokenRepository) { - this.csrfTokenRepository = csrfTokenRepository; - } - - @Override - public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, - ServletException { - CsrfToken csrfToken = this.csrfTokenRepository.generateToken(request); - this.csrfTokenRepository.saveToken(csrfToken, request, response); - this.delegate.onLogoutSuccess(request, response, authentication); - } - -} diff --git a/backend/src/main/java/ru/micord/ervu/security/LogoutSuccessHandler.java b/backend/src/main/java/ru/micord/ervu/security/LogoutSuccessHandler.java new file mode 100644 index 00000000..9993b234 --- /dev/null +++ b/backend/src/main/java/ru/micord/ervu/security/LogoutSuccessHandler.java @@ -0,0 +1,34 @@ +package ru.micord.ervu.security; + +import java.io.IOException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.security.core.Authentication; +import org.springframework.security.web.csrf.CsrfToken; +import org.springframework.security.web.csrf.CsrfTokenRepository; +import ru.micord.ervu.security.esia.service.EsiaAuthService; + +public class LogoutSuccessHandler + implements org.springframework.security.web.authentication.logout.LogoutSuccessHandler { + + private final CsrfTokenRepository csrfTokenRepository; + private final EsiaAuthService esiaAuthService; + + public LogoutSuccessHandler(CsrfTokenRepository csrfTokenRepository, + EsiaAuthService esiaAuthService) { + this.csrfTokenRepository = csrfTokenRepository; + this.esiaAuthService = esiaAuthService; + } + + @Override + public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, + Authentication authentication) throws IOException { + String url = esiaAuthService.logout(request, response); + response.setStatus(HttpServletResponse.SC_OK); + response.getWriter().write(url); + response.getWriter().flush(); + CsrfToken csrfToken = this.csrfTokenRepository.generateToken(request); + this.csrfTokenRepository.saveToken(csrfToken, request, response); + } +} diff --git a/backend/src/main/java/ru/micord/ervu/security/SecurityConfig.java b/backend/src/main/java/ru/micord/ervu/security/SecurityConfig.java index 6df367d7..2eb9b989 100644 --- a/backend/src/main/java/ru/micord/ervu/security/SecurityConfig.java +++ b/backend/src/main/java/ru/micord/ervu/security/SecurityConfig.java @@ -13,6 +13,7 @@ import org.springframework.security.web.authentication.UsernamePasswordAuthentic import org.springframework.security.web.csrf.CookieCsrfTokenRepository; import org.springframework.security.web.csrf.CsrfTokenRequestHandler; import org.springframework.security.web.csrf.XorCsrfTokenRequestAttributeHandler; +import ru.micord.ervu.security.esia.service.EsiaAuthService; import ru.micord.ervu.security.webbpm.jwt.filter.JwtAuthenticationFilter; import ru.micord.ervu.security.webbpm.jwt.UnauthorizedEntryPoint; @@ -24,6 +25,8 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private JwtAuthenticationFilter jwtAuthenticationFilter; + @Autowired + private EsiaAuthService esiaAuthService; @Override protected void configure(HttpSecurity http) throws Exception { @@ -50,7 +53,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter { ) .logout((logout) -> logout .logoutUrl(ESIA_LOGOUT) - .logoutSuccessHandler(new CsrfTokenAwareLogoutSuccessHandler(tokenRepository)) + .logoutSuccessHandler(new LogoutSuccessHandler(tokenRepository,esiaAuthService)) ) .exceptionHandling().authenticationEntryPoint(entryPoint()) .and() diff --git a/backend/src/main/java/ru/micord/ervu/security/esia/controller/EsiaController.java b/backend/src/main/java/ru/micord/ervu/security/esia/controller/EsiaController.java index 42aecf09..ea9d5133 100644 --- a/backend/src/main/java/ru/micord/ervu/security/esia/controller/EsiaController.java +++ b/backend/src/main/java/ru/micord/ervu/security/esia/controller/EsiaController.java @@ -54,9 +54,4 @@ public class EsiaController { public String getOrgUnitName(HttpServletRequest request) { return esiaDataService.getOrgUnitName(request); } - - @PostMapping(SecurityConstants.ESIA_LOGOUT) - public String logout(HttpServletRequest request, HttpServletResponse response) { - return esiaAuthService.logout(request, response); - } } From f0c667fddf2ad76de9676e5f4805ea68c249c365 Mon Sep 17 00:00:00 2001 From: kochetkov Date: Thu, 17 Oct 2024 09:48:25 +0300 Subject: [PATCH 11/26] SUPPORT-8609 small fix --- .../micord/ervu/security/SecurityConfig.java | 109 +++++++++--------- .../filter/FilterChainExceptionHandler.java | 29 +++++ 2 files changed, 85 insertions(+), 53 deletions(-) create mode 100644 backend/src/main/java/ru/micord/ervu/security/filter/FilterChainExceptionHandler.java diff --git a/backend/src/main/java/ru/micord/ervu/security/SecurityConfig.java b/backend/src/main/java/ru/micord/ervu/security/SecurityConfig.java index 2eb9b989..5cfbf095 100644 --- a/backend/src/main/java/ru/micord/ervu/security/SecurityConfig.java +++ b/backend/src/main/java/ru/micord/ervu/security/SecurityConfig.java @@ -10,73 +10,76 @@ import org.springframework.security.config.annotation.web.configuration.WebSecur import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.security.web.authentication.logout.LogoutFilter; import org.springframework.security.web.csrf.CookieCsrfTokenRepository; import org.springframework.security.web.csrf.CsrfTokenRequestHandler; import org.springframework.security.web.csrf.XorCsrfTokenRequestAttributeHandler; import ru.micord.ervu.security.esia.service.EsiaAuthService; -import ru.micord.ervu.security.webbpm.jwt.filter.JwtAuthenticationFilter; +import ru.micord.ervu.security.filter.FilterChainExceptionHandler; import ru.micord.ervu.security.webbpm.jwt.UnauthorizedEntryPoint; +import ru.micord.ervu.security.webbpm.jwt.filter.JwtAuthenticationFilter; import static ru.micord.ervu.security.SecurityConstants.ESIA_LOGOUT; @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { + public static final String[] PERMIT_ALL = new String[] {"/version", "/esia/url", "/esia/auth", "esia/refresh"}; + @Autowired + private JwtAuthenticationFilter jwtAuthenticationFilter; + @Autowired + private EsiaAuthService esiaAuthService; + @Autowired + private FilterChainExceptionHandler filterChainExceptionHandler; - @Autowired - private JwtAuthenticationFilter jwtAuthenticationFilter; - @Autowired - private EsiaAuthService esiaAuthService; + @Override + protected void configure(HttpSecurity http) throws Exception { + httpConfigure(http); + http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); + http.addFilterBefore(filterChainExceptionHandler, LogoutFilter.class); + } - @Override - protected void configure(HttpSecurity http) throws Exception { - httpConfigure(http); - http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); - } + protected void httpConfigure(HttpSecurity httpSecurity) throws Exception { + CookieCsrfTokenRepository tokenRepository = CookieCsrfTokenRepository.withHttpOnlyFalse(); + tokenRepository.setCookiePath("/"); + XorCsrfTokenRequestAttributeHandler delegate = new XorCsrfTokenRequestAttributeHandler(); + delegate.setCsrfRequestAttributeName(null); + // Use only the handle() method of XorCsrfTokenRequestAttributeHandler and the + // default implementation of resolveCsrfTokenValue() from CsrfTokenRequestHandler + CsrfTokenRequestHandler requestHandler = delegate::handle; + httpSecurity.authorizeRequests() + .antMatchers(PERMIT_ALL) + .permitAll() + .antMatchers("/**") + .authenticated() + .and() + .csrf((csrf) -> csrf.csrfTokenRepository(tokenRepository) + .csrfTokenRequestHandler(requestHandler)) + .logout((logout) -> logout.logoutUrl(ESIA_LOGOUT) + .logoutSuccessHandler(new LogoutSuccessHandler(tokenRepository, esiaAuthService))) + .exceptionHandling() + .authenticationEntryPoint(entryPoint()) + .and() + .sessionManagement() + .sessionCreationPolicy(SessionCreationPolicy.STATELESS); + } - protected void httpConfigure(HttpSecurity httpSecurity) throws Exception { - String[] permitAll = {"/version","/esia/url", "/esia/auth", "esia/refresh"}; - CookieCsrfTokenRepository tokenRepository = CookieCsrfTokenRepository.withHttpOnlyFalse(); - tokenRepository.setCookiePath("/"); - XorCsrfTokenRequestAttributeHandler delegate = new XorCsrfTokenRequestAttributeHandler(); - delegate.setCsrfRequestAttributeName(null); - // Use only the handle() method of XorCsrfTokenRequestAttributeHandler and the - // default implementation of resolveCsrfTokenValue() from CsrfTokenRequestHandler - CsrfTokenRequestHandler requestHandler = delegate::handle; - httpSecurity.authorizeRequests() - .antMatchers(permitAll).permitAll() - .antMatchers("/**").authenticated() - .and() - .csrf((csrf) -> csrf - .csrfTokenRepository(tokenRepository) - .csrfTokenRequestHandler(requestHandler) - ) - .logout((logout) -> logout - .logoutUrl(ESIA_LOGOUT) - .logoutSuccessHandler(new LogoutSuccessHandler(tokenRepository,esiaAuthService)) - ) - .exceptionHandling().authenticationEntryPoint(entryPoint()) - .and() - .sessionManagement() - .sessionCreationPolicy(SessionCreationPolicy.STATELESS); - } + public AuthenticationEntryPoint entryPoint() { + return new UnauthorizedEntryPoint(); + } - public AuthenticationEntryPoint entryPoint() { - return new UnauthorizedEntryPoint(); - } + @Bean + @Override + public AuthenticationManager authenticationManagerBean() throws Exception { + return super.authenticationManagerBean(); + } - @Bean - @Override - public AuthenticationManager authenticationManagerBean() throws Exception { - return super.authenticationManagerBean(); - } - - @Bean - public JwtAuthenticationFilter jwtAuthenticationFilter() throws Exception { - JwtAuthenticationFilter jwtAuthenticationFilter = new JwtAuthenticationFilter("/**", - entryPoint() - ); - jwtAuthenticationFilter.setAuthenticationManager(authenticationManagerBean()); - return jwtAuthenticationFilter; - } + @Bean + public JwtAuthenticationFilter jwtAuthenticationFilter() throws Exception { + JwtAuthenticationFilter jwtAuthenticationFilter = new JwtAuthenticationFilter("/**", + entryPoint() + ); + jwtAuthenticationFilter.setAuthenticationManager(authenticationManagerBean()); + return jwtAuthenticationFilter; + } } diff --git a/backend/src/main/java/ru/micord/ervu/security/filter/FilterChainExceptionHandler.java b/backend/src/main/java/ru/micord/ervu/security/filter/FilterChainExceptionHandler.java new file mode 100644 index 00000000..7cce4a1b --- /dev/null +++ b/backend/src/main/java/ru/micord/ervu/security/filter/FilterChainExceptionHandler.java @@ -0,0 +1,29 @@ +package ru.micord.ervu.security.filter; + +import javax.servlet.FilterChain; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; +import org.springframework.web.servlet.HandlerExceptionResolver; + +@Component +public class FilterChainExceptionHandler extends OncePerRequestFilter { + @Autowired + @Qualifier("handlerExceptionResolver") + private HandlerExceptionResolver resolver; + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, + FilterChain filterChain) { + try { + filterChain.doFilter(request, response); + } + catch (Exception e) { + resolver.resolveException(request, response, null, e); + } + } +} From ac7675987f13a1eda15084ad8d91a69da34385f9 Mon Sep 17 00:00:00 2001 From: "m.epshtein" Date: Mon, 21 Oct 2024 14:27:29 +0300 Subject: [PATCH 12/26] SUPPORT-8636 --- frontend/src/resources/css/components-lkrp.css | 4 ++-- frontend/src/resources/img/svg/file-csv.svg | 11 +++++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) create mode 100644 frontend/src/resources/img/svg/file-csv.svg diff --git a/frontend/src/resources/css/components-lkrp.css b/frontend/src/resources/css/components-lkrp.css index bdcf0c7a..220c8e12 100644 --- a/frontend/src/resources/css/components-lkrp.css +++ b/frontend/src/resources/css/components-lkrp.css @@ -796,11 +796,11 @@ text-overflow: ellipsis; white-space: nowrap; overflow: hidden; - padding-left: 60px; + padding-left: 56px; } .webbpm.ervu_lkrp_ul .modal.show ervu-file-upload .selected-file .selected-file-name::before { position: absolute; - content: url(../img/svg/file-xlsx.svg); + content: url(../img/svg/file-csv.svg); top: -2px; left: 0; } diff --git a/frontend/src/resources/img/svg/file-csv.svg b/frontend/src/resources/img/svg/file-csv.svg new file mode 100644 index 00000000..e078154e --- /dev/null +++ b/frontend/src/resources/img/svg/file-csv.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + From 5be19645c01abc0b9c80bc95b309e1a0bc971639 Mon Sep 17 00:00:00 2001 From: kochetkov Date: Tue, 22 Oct 2024 10:19:26 +0300 Subject: [PATCH 13/26] fix after merge --- .../ru/micord/ervu/security/esia/service/EsiaAuthService.java | 1 - 1 file changed, 1 deletion(-) diff --git a/backend/src/main/java/ru/micord/ervu/security/esia/service/EsiaAuthService.java b/backend/src/main/java/ru/micord/ervu/security/esia/service/EsiaAuthService.java index aed3094c..781a2c79 100644 --- a/backend/src/main/java/ru/micord/ervu/security/esia/service/EsiaAuthService.java +++ b/backend/src/main/java/ru/micord/ervu/security/esia/service/EsiaAuthService.java @@ -238,7 +238,6 @@ public class EsiaAuthService { cookiePath = request.getContextPath(); } String refreshToken = tokenResponse.getRefresh_token(); - String prnOid = esiaAccessToken.getSbj_id(); String ervuId = getErvuId(accessToken, prnOid); Long expiresIn = tokenResponse.getExpires_in(); TokensStore.addAccessToken(prnOid, accessToken, expiresIn); From d46f7ee8c163ff63b97c1d08d08f30da18c28f68 Mon Sep 17 00:00:00 2001 From: gulnaz Date: Tue, 22 Oct 2024 12:42:20 +0300 Subject: [PATCH 14/26] fix --- frontend/src/ts/modules/security/guard/auth.guard.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/ts/modules/security/guard/auth.guard.ts b/frontend/src/ts/modules/security/guard/auth.guard.ts index 8a925f84..5448d744 100644 --- a/frontend/src/ts/modules/security/guard/auth.guard.ts +++ b/frontend/src/ts/modules/security/guard/auth.guard.ts @@ -74,11 +74,11 @@ export abstract class AuthGuard implements CanActivate { private checkAccess(): boolean { return this.authenticationService.isAuthenticated(); - }; } private extractCode(message: string): string | null { const regex = /ESIA-\d{6}/; const match = message.match(regex); return match ? match[0] : null; + } } From 4068277bc3d0e3c15c555c5cd5777e0f11005bbc Mon Sep 17 00:00:00 2001 From: kochetkov Date: Tue, 22 Oct 2024 14:07:11 +0300 Subject: [PATCH 15/26] SUPPORT-8609 fixes --- frontend/src/ts/modules/app/app.module.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/ts/modules/app/app.module.ts b/frontend/src/ts/modules/app/app.module.ts index b752669f..4f1ac1b3 100644 --- a/frontend/src/ts/modules/app/app.module.ts +++ b/frontend/src/ts/modules/app/app.module.ts @@ -40,7 +40,7 @@ export const DIRECTIVES = [ forwardRef(() => InMemoryStaticGrid) ]; -function checkAuthentication(authService: AuthenticationService): () => Promise { +export function checkAuthentication(authService: AuthenticationService): () => Promise { return () => authService.checkAuthentication(); } From 6536306282205a4839edbb4fbc123bff09e1f893 Mon Sep 17 00:00:00 2001 From: kochetkov Date: Wed, 23 Oct 2024 10:04:11 +0300 Subject: [PATCH 16/26] SUPPORT-8609 update security config --- .../micord/ervu/security/SecurityConfig.java | 61 ++++++++++----- .../esia/service/EsiaAuthService.java | 74 ++++--------------- .../ervu/security/webbpm/jwt/JwtMatcher.java | 33 +++++++++ .../jwt/filter/JwtAuthenticationFilter.java | 53 +++++++------ .../webbpm/jwt/helper/SecurityHelper.java | 43 +++++++++++ .../webbpm/jwt/service/JwtTokenService.java | 14 +--- .../webbpm/jwt/util/SecurityUtil.java | 45 +++++++++++ 7 files changed, 206 insertions(+), 117 deletions(-) create mode 100644 backend/src/main/java/ru/micord/ervu/security/webbpm/jwt/JwtMatcher.java create mode 100644 backend/src/main/java/ru/micord/ervu/security/webbpm/jwt/helper/SecurityHelper.java create mode 100644 backend/src/main/java/ru/micord/ervu/security/webbpm/jwt/util/SecurityUtil.java diff --git a/backend/src/main/java/ru/micord/ervu/security/SecurityConfig.java b/backend/src/main/java/ru/micord/ervu/security/SecurityConfig.java index 5cfbf095..2297c073 100644 --- a/backend/src/main/java/ru/micord/ervu/security/SecurityConfig.java +++ b/backend/src/main/java/ru/micord/ervu/security/SecurityConfig.java @@ -4,39 +4,56 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; 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.config.http.SessionCreationPolicy; import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.authentication.logout.LogoutFilter; import org.springframework.security.web.csrf.CookieCsrfTokenRepository; import org.springframework.security.web.csrf.CsrfTokenRequestHandler; import org.springframework.security.web.csrf.XorCsrfTokenRequestAttributeHandler; +import org.springframework.web.filter.RequestContextFilter; import ru.micord.ervu.security.esia.service.EsiaAuthService; import ru.micord.ervu.security.filter.FilterChainExceptionHandler; +import ru.micord.ervu.security.webbpm.jwt.JwtAuthenticationProvider; +import ru.micord.ervu.security.webbpm.jwt.JwtMatcher; import ru.micord.ervu.security.webbpm.jwt.UnauthorizedEntryPoint; import ru.micord.ervu.security.webbpm.jwt.filter.JwtAuthenticationFilter; +import ru.micord.ervu.security.webbpm.jwt.helper.SecurityHelper; import static ru.micord.ervu.security.SecurityConstants.ESIA_LOGOUT; @Configuration @EnableWebSecurity -public class SecurityConfig extends WebSecurityConfigurerAdapter { - public static final String[] PERMIT_ALL = new String[] {"/version", "/esia/url", "/esia/auth", "esia/refresh"}; +public class SecurityConfig { + private static final String[] PERMIT_ALL = new String[] { + "/version", "/esia/url", "/esia/auth", "esia/refresh" + }; @Autowired private JwtAuthenticationFilter jwtAuthenticationFilter; @Autowired private EsiaAuthService esiaAuthService; @Autowired private FilterChainExceptionHandler filterChainExceptionHandler; + @Autowired + private JwtAuthenticationProvider jwtAuthenticationProvider; - @Override - protected void configure(HttpSecurity http) throws Exception { + @Autowired + public void configureGlobal(AuthenticationManagerBuilder auth) { + auth.authenticationProvider(jwtAuthenticationProvider); + } + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { httpConfigure(http); http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); - http.addFilterBefore(filterChainExceptionHandler, LogoutFilter.class); + http.addFilterBefore(new RequestContextFilter(), LogoutFilter.class); + http.addFilterAfter(filterChainExceptionHandler, RequestContextFilter.class); + return http.build(); } protected void httpConfigure(HttpSecurity httpSecurity) throws Exception { @@ -47,12 +64,11 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter { // Use only the handle() method of XorCsrfTokenRequestAttributeHandler and the // default implementation of resolveCsrfTokenValue() from CsrfTokenRequestHandler CsrfTokenRequestHandler requestHandler = delegate::handle; - httpSecurity.authorizeRequests() - .antMatchers(PERMIT_ALL) - .permitAll() - .antMatchers("/**") - .authenticated() - .and() + httpSecurity.authorizeHttpRequests( + (authorizeHttpRequests) -> authorizeHttpRequests.requestMatchers(PERMIT_ALL) + .permitAll() + .anyRequest() + .authenticated()) .csrf((csrf) -> csrf.csrfTokenRepository(tokenRepository) .csrfTokenRequestHandler(requestHandler)) .logout((logout) -> logout.logoutUrl(ESIA_LOGOUT) @@ -69,17 +85,22 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter { } @Bean - @Override - public AuthenticationManager authenticationManagerBean() throws Exception { - return super.authenticationManagerBean(); + AuthenticationManager authenticationManager( + AuthenticationConfiguration authenticationConfiguration) throws Exception { + return authenticationConfiguration.getAuthenticationManager(); } @Bean - public JwtAuthenticationFilter jwtAuthenticationFilter() throws Exception { - JwtAuthenticationFilter jwtAuthenticationFilter = new JwtAuthenticationFilter("/**", - entryPoint() - ); - jwtAuthenticationFilter.setAuthenticationManager(authenticationManagerBean()); + public SecurityHelper securityHelper() { + return new SecurityHelper(); + } + + @Bean + public JwtAuthenticationFilter jwtAuthenticationFilter(SecurityHelper securityHelper, + AuthenticationManager manager) { + JwtAuthenticationFilter jwtAuthenticationFilter = new JwtAuthenticationFilter( + new JwtMatcher("/**", PERMIT_ALL), entryPoint(), securityHelper); + jwtAuthenticationFilter.setAuthenticationManager(manager); return jwtAuthenticationFilter; } } diff --git a/backend/src/main/java/ru/micord/ervu/security/esia/service/EsiaAuthService.java b/backend/src/main/java/ru/micord/ervu/security/esia/service/EsiaAuthService.java index 781a2c79..32e7022d 100644 --- a/backend/src/main/java/ru/micord/ervu/security/esia/service/EsiaAuthService.java +++ b/backend/src/main/java/ru/micord/ervu/security/esia/service/EsiaAuthService.java @@ -47,38 +47,32 @@ import ru.micord.ervu.security.esia.model.EsiaTokenResponse; import ru.micord.ervu.security.esia.model.FormUrlencoded; import ru.micord.ervu.security.esia.model.OrganizationModel; import ru.micord.ervu.security.webbpm.jwt.JwtAuthentication; +import ru.micord.ervu.security.webbpm.jwt.helper.SecurityHelper; import ru.micord.ervu.security.webbpm.jwt.service.JwtTokenService; import ru.micord.ervu.security.webbpm.jwt.model.Token; +import ru.micord.ervu.security.webbpm.jwt.util.SecurityUtil; /** * @author Eduard Tihomirov */ @Service public class EsiaAuthService { - private static final Logger LOGGER = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); - - @Value("${cookie-path:#{null}}") - private String path; - @Autowired private ObjectMapper objectMapper; - @Autowired private EsiaConfig esiaConfig; - @Autowired private UlDataService ulDataService; - @Autowired private JwtTokenService jwtTokenService; - @Autowired // @Qualifier("org") private ReplyingKafkaService replyingKafkaService; - @Autowired private OkopfService okopfService; + @Autowired + private SecurityHelper securityHelper; @Value("${ervu.kafka.org.reply.topic}") private String requestReplyTopic; @@ -230,23 +224,15 @@ public class EsiaAuthService { HttpStatus.FORBIDDEN ); } - String cookiePath = null; - if (path != null) { - cookiePath = path; - } - else { - cookiePath = request.getContextPath(); - } String refreshToken = tokenResponse.getRefresh_token(); String ervuId = getErvuId(accessToken, prnOid); Long expiresIn = tokenResponse.getExpires_in(); TokensStore.addAccessToken(prnOid, accessToken, expiresIn); TokensStore.addRefreshToken(prnOid, refreshToken, expiresIn); Token token = jwtTokenService.createAccessToken(esiaAccessToken.getSbj_id(), expiresIn, ervuId); - Cookie authToken = new Cookie("auth_token", token.getValue()); - authToken.setPath(cookiePath); - authToken.setHttpOnly(true); - response.addCookie(authToken); + int expiry = tokenResponse.getExpires_in().intValue(); + Cookie accessCookie = securityHelper.createAccessCookie(token.getValue(), expiry); + response.addCookie(accessCookie); UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(token.getUserAccountId(), null); SecurityContext context = SecurityContextHolder.createEmptyContext(); @@ -254,11 +240,8 @@ public class EsiaAuthService { esiaAccessToken.getSbj_id(), token.getValue()); context.setAuthentication(authentication); SecurityContextHolder.setContext(context); - - Cookie isAuth = new Cookie("webbpm.ervu-lkrp-ul", "true"); - isAuth.setMaxAge(tokenResponse.getExpires_in().intValue()); - isAuth.setPath("/"); - response.addCookie(isAuth); + Cookie authMarkerCookie = securityHelper.createAuthMarkerCookie("true", expiry); + response.addCookie(authMarkerCookie); return ResponseEntity.ok("Authentication successful"); } catch (Exception e) { @@ -317,14 +300,6 @@ public class EsiaAuthService { throw new RuntimeException(tokenResponse.getError_description()); } String accessToken = tokenResponse.getAccess_token(); - String cookiePath = null; - if (path != null) { - cookiePath = path; - } - else { - cookiePath = request.getContextPath(); - } - String newRefreshToken = tokenResponse.getRefresh_token(); EsiaAccessToken esiaAccessToken = ulDataService.readToken(accessToken); String prnOid = esiaAccessToken.getSbj_id(); @@ -333,10 +308,9 @@ public class EsiaAuthService { TokensStore.addRefreshToken(prnOid, newRefreshToken, expiresIn); String ervuId = getErvuId(accessToken, prnOid); Token token = jwtTokenService.createAccessToken(esiaAccessToken.getSbj_id(), expiresIn, ervuId); - Cookie authToken = new Cookie("auth_token", token.getValue()); - authToken.setPath(cookiePath); - authToken.setHttpOnly(true); - response.addCookie(authToken); + int expiry = tokenResponse.getExpires_in().intValue(); + Cookie accessCookie = securityHelper.createAccessCookie(token.getValue(), expiry); + response.addCookie(accessCookie); UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(token.getUserAccountId(), null); SecurityContext context = SecurityContextHolder.createEmptyContext(); @@ -344,11 +318,8 @@ public class EsiaAuthService { esiaAccessToken.getSbj_id(), token.getValue()); context.setAuthentication(authentication); SecurityContextHolder.setContext(context); - - Cookie isAuth = new Cookie("webbpm.ervu-lkrp-ul", "true"); - isAuth.setMaxAge(tokenResponse.getExpires_in().intValue()); - isAuth.setPath("/"); - response.addCookie(isAuth); + Cookie authMarkerCookie = securityHelper.createAuthMarkerCookie("true", expiry); + response.addCookie(authMarkerCookie); } catch (Exception e) { throw new RuntimeException(e); @@ -389,22 +360,7 @@ public class EsiaAuthService { public String logout(HttpServletRequest request, HttpServletResponse response) { try { - Cookie[] cookies = request.getCookies(); - if (cookies != null) - for (Cookie cookie : cookies) { - if (cookie.getName().equals("webbpm.ervu-lkrp-ul")) { - cookie.setValue(""); - cookie.setPath("/"); - cookie.setMaxAge(0); - response.addCookie(cookie); - } - else if (cookie.getName().equals("auth_token")) { - cookie.setValue(""); - cookie.setPath(cookie.getPath()); - cookie.setMaxAge(0); - response.addCookie(cookie); - } - } + securityHelper.clearAccessCookies(response); String userId = jwtTokenService.getUserAccountId(request); TokensStore.removeAccessToken(userId); TokensStore.removeRefreshToken(userId); diff --git a/backend/src/main/java/ru/micord/ervu/security/webbpm/jwt/JwtMatcher.java b/backend/src/main/java/ru/micord/ervu/security/webbpm/jwt/JwtMatcher.java new file mode 100644 index 00000000..f0c90cec --- /dev/null +++ b/backend/src/main/java/ru/micord/ervu/security/webbpm/jwt/JwtMatcher.java @@ -0,0 +1,33 @@ +package ru.micord.ervu.security.webbpm.jwt; + +import java.util.Arrays; +import java.util.Set; +import java.util.stream.Collectors; +import javax.servlet.http.HttpServletRequest; + +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; +import org.springframework.security.web.util.matcher.RequestMatcher; + +import static ru.micord.ervu.security.webbpm.jwt.util.SecurityUtil.extractAuthToken; + +public final class JwtMatcher implements RequestMatcher { + private final Set excludedPathMatchers; + private final AntPathRequestMatcher securedMatcher; + + public JwtMatcher(String securedPath, String... excludedPaths) { + this.securedMatcher = new AntPathRequestMatcher(securedPath); + this.excludedPathMatchers = Arrays.stream(excludedPaths) + .map(AntPathRequestMatcher::new) + .collect(Collectors.toSet()); + } + + @Override + public boolean matches(HttpServletRequest request) { + if (this.excludedPathMatchers.stream().anyMatch(matcher -> matcher.matches(request))) { + return false; + } + else { + return extractAuthToken(request) != null && this.securedMatcher.matches(request); + } + } +} diff --git a/backend/src/main/java/ru/micord/ervu/security/webbpm/jwt/filter/JwtAuthenticationFilter.java b/backend/src/main/java/ru/micord/ervu/security/webbpm/jwt/filter/JwtAuthenticationFilter.java index a27c5b9d..17d74fc1 100644 --- a/backend/src/main/java/ru/micord/ervu/security/webbpm/jwt/filter/JwtAuthenticationFilter.java +++ b/backend/src/main/java/ru/micord/ervu/security/webbpm/jwt/filter/JwtAuthenticationFilter.java @@ -4,7 +4,6 @@ import java.io.IOException; import java.lang.invoke.MethodHandles; import javax.servlet.FilterChain; import javax.servlet.ServletException; -import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -16,25 +15,37 @@ import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; +import org.springframework.security.web.util.matcher.RequestMatcher; import ru.micord.ervu.security.webbpm.jwt.JwtAuthentication; +import ru.micord.ervu.security.webbpm.jwt.helper.SecurityHelper; + +import static ru.micord.ervu.security.webbpm.jwt.util.SecurityUtil.extractAuthToken; + /** * @author Flyur Karimov */ public class JwtAuthenticationFilter extends AbstractAuthenticationProcessingFilter { - private static final Logger LOGGER = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + private static final Logger LOGGER = LoggerFactory.getLogger( + MethodHandles.lookup().lookupClass()); private final AuthenticationEntryPoint entryPoint; - public JwtAuthenticationFilter(String securityPath, AuthenticationEntryPoint entryPoint) { - super(securityPath); + private final SecurityHelper securityHelper; + + public JwtAuthenticationFilter(RequestMatcher requestMatcher, + AuthenticationEntryPoint entryPoint, + SecurityHelper securityHelper) { + super(requestMatcher); this.entryPoint = entryPoint; + this.securityHelper = securityHelper; } @Override public Authentication attemptAuthentication(HttpServletRequest httpServletRequest, - HttpServletResponse httpServletResponse) throws AuthenticationException { - String token = extractAuthTokenFromRequest(httpServletRequest); + HttpServletResponse httpServletResponse) + throws AuthenticationException { + String token = extractAuthToken(httpServletRequest); Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (authentication == null) { authentication = new JwtAuthentication(null, null, token); @@ -43,43 +54,29 @@ public class JwtAuthenticationFilter extends AbstractAuthenticationProcessingFil authentication = getAuthenticationManager().authenticate(authentication); } catch (CredentialsExpiredException e) { + securityHelper.clearAccessCookies(httpServletResponse); httpServletResponse.setStatus(401); LOGGER.warn(e.getMessage()); + return null; } return authentication; } - @Override - protected boolean requiresAuthentication(HttpServletRequest request, HttpServletResponse response) { - return extractAuthTokenFromRequest(request) != null; - } - @Override protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, - FilterChain chain, Authentication authentication) throws IOException, ServletException { + FilterChain chain, Authentication authentication) + throws IOException, ServletException { SecurityContextHolder.getContext().setAuthentication(authentication); chain.doFilter(request, response); } @Override - protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, - AuthenticationException exception) throws IOException, ServletException { + protected void unsuccessfulAuthentication(HttpServletRequest request, + HttpServletResponse response, + AuthenticationException exception) + throws IOException, ServletException { LOGGER.error("Jwt unsuccessful authentication exception", exception); SecurityContextHolder.clearContext(); entryPoint.commence(request, response, exception); } - - public String extractAuthTokenFromRequest(HttpServletRequest httpRequest) { - String token = null; - Cookie[] cookies = httpRequest.getCookies(); - if (cookies != null) { - for (Cookie cookie : cookies) { - if (cookie.getName().equals("auth_token")) { - token = cookie.getValue(); - LOGGER.info("Token extracted from cookie: {}", token); - } - } - } - return token; - } } diff --git a/backend/src/main/java/ru/micord/ervu/security/webbpm/jwt/helper/SecurityHelper.java b/backend/src/main/java/ru/micord/ervu/security/webbpm/jwt/helper/SecurityHelper.java new file mode 100644 index 00000000..dcf6c100 --- /dev/null +++ b/backend/src/main/java/ru/micord/ervu/security/webbpm/jwt/helper/SecurityHelper.java @@ -0,0 +1,43 @@ +package ru.micord.ervu.security.webbpm.jwt.helper; + +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.beans.factory.annotation.Value; +import ru.micord.ervu.security.webbpm.jwt.util.SecurityUtil; + +import static ru.micord.ervu.security.webbpm.jwt.util.SecurityUtil.AUTH_MARKER; +import static ru.micord.ervu.security.webbpm.jwt.util.SecurityUtil.AUTH_TOKEN; +import static ru.micord.ervu.security.webbpm.jwt.util.SecurityUtil.createCookie; + +public final class SecurityHelper { + @Value("${cookie-path:#{null}}") + private String accessCookiePath; + + public void clearAccessCookies(HttpServletResponse response) { + Cookie tokenCookie = createCookie(AUTH_TOKEN, null, null); + tokenCookie.setMaxAge(0); + tokenCookie.setPath(accessCookiePath); + tokenCookie.setHttpOnly(true); + response.addCookie(tokenCookie); + + Cookie markerCookie = createCookie(AUTH_MARKER, null, null); + markerCookie.setMaxAge(0); + markerCookie.setPath("/"); + response.addCookie(markerCookie); + } + + public Cookie createAccessCookie(String cookieValue, int expiry) { + Cookie authToken = createCookie(SecurityUtil.AUTH_TOKEN, cookieValue, accessCookiePath); + authToken.setPath(accessCookiePath); + authToken.setMaxAge(expiry); + return authToken; + } + + public Cookie createAuthMarkerCookie(String cookieValue, int expiry) { + Cookie marker = createCookie(SecurityUtil.AUTH_TOKEN, cookieValue, "/"); + marker.setMaxAge(expiry); + marker.setHttpOnly(false); + return marker; + } +} diff --git a/backend/src/main/java/ru/micord/ervu/security/webbpm/jwt/service/JwtTokenService.java b/backend/src/main/java/ru/micord/ervu/security/webbpm/jwt/service/JwtTokenService.java index 414b6334..22f627f2 100644 --- a/backend/src/main/java/ru/micord/ervu/security/webbpm/jwt/service/JwtTokenService.java +++ b/backend/src/main/java/ru/micord/ervu/security/webbpm/jwt/service/JwtTokenService.java @@ -4,7 +4,6 @@ import java.lang.invoke.MethodHandles; import java.util.Base64; import java.util.Date; import javax.crypto.SecretKey; -import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import io.jsonwebtoken.Claims; @@ -20,6 +19,8 @@ import ru.micord.ervu.security.webbpm.jwt.model.Token; import ru.cg.webbpm.modules.resources.api.ResourceMetadataUtils; +import static ru.micord.ervu.security.webbpm.jwt.util.SecurityUtil.extractAuthToken; + /** * @author Flyur Karimov */ @@ -85,15 +86,8 @@ public class JwtTokenService { } public String getUserAccountId(HttpServletRequest request) { - String authToken = null; - Cookie[] cookies = request.getCookies(); - if (cookies != null) { - for (Cookie cookie : cookies) { - if (cookie.getName().equals("auth_token")) { - authToken = cookie.getValue(); - } - } - } + String authToken = extractAuthToken(request); + if (authToken != null) { String[] ids = getToken(authToken).getUserAccountId().split(":"); return ids[0]; diff --git a/backend/src/main/java/ru/micord/ervu/security/webbpm/jwt/util/SecurityUtil.java b/backend/src/main/java/ru/micord/ervu/security/webbpm/jwt/util/SecurityUtil.java new file mode 100644 index 00000000..b2314e37 --- /dev/null +++ b/backend/src/main/java/ru/micord/ervu/security/webbpm/jwt/util/SecurityUtil.java @@ -0,0 +1,45 @@ +package ru.micord.ervu.security.webbpm.jwt.util; + +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; + +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.util.WebUtils; + +import static org.springframework.web.context.request.RequestAttributes.REFERENCE_REQUEST; + +public final class SecurityUtil { + public static final String AUTH_TOKEN = "auth_token"; + + public static final String AUTH_MARKER = "webbpm.ervu-lkrp-ul"; + + private SecurityUtil() { + //empty + } + + public static Cookie createCookie(String name, String value, String path) { + String cookieValue = value != null ? URLEncoder.encode(value, StandardCharsets.UTF_8) : null; + Cookie cookie = new Cookie(name, cookieValue); + RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); + HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference( + REFERENCE_REQUEST); + + if (path != null) { + cookie.setPath(path); + } + else { + cookie.setPath(request.getContextPath()); + } + cookie.setHttpOnly(true); + + return cookie; + } + + public static String extractAuthToken(HttpServletRequest httpRequest) { + Cookie cookie = WebUtils.getCookie(httpRequest, AUTH_TOKEN); + return cookie != null ? cookie.getValue() : null; + } +} From 2a0715fc26c56e44af5354c4fc7bf0fd5ff892da Mon Sep 17 00:00:00 2001 From: kochetkov Date: Wed, 23 Oct 2024 10:28:19 +0300 Subject: [PATCH 17/26] SUPPORT-8609 dmall fix --- .../micord/ervu/security/webbpm/jwt/helper/SecurityHelper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/main/java/ru/micord/ervu/security/webbpm/jwt/helper/SecurityHelper.java b/backend/src/main/java/ru/micord/ervu/security/webbpm/jwt/helper/SecurityHelper.java index dcf6c100..57f4c7f0 100644 --- a/backend/src/main/java/ru/micord/ervu/security/webbpm/jwt/helper/SecurityHelper.java +++ b/backend/src/main/java/ru/micord/ervu/security/webbpm/jwt/helper/SecurityHelper.java @@ -35,7 +35,7 @@ public final class SecurityHelper { } public Cookie createAuthMarkerCookie(String cookieValue, int expiry) { - Cookie marker = createCookie(SecurityUtil.AUTH_TOKEN, cookieValue, "/"); + Cookie marker = createCookie(AUTH_MARKER, cookieValue, "/"); marker.setMaxAge(expiry); marker.setHttpOnly(false); return marker; From ea8c1086536c4b363acc6ce44657771a655b5528 Mon Sep 17 00:00:00 2001 From: Eduard Tihomirov Date: Wed, 23 Oct 2024 11:22:31 +0300 Subject: [PATCH 18/26] sync --- .../ervu/kafka/ErvuProducerInterceptor.java | 33 ------ .../ervu/kafka/ReplyingKafkaConfig.java | 106 ++++++------------ .../kafka/controller/ErvuKafkaController.java | 1 - .../impl/BaseReplyingKafkaServiceImpl.java | 76 +++---------- .../impl/ExcerptReplyingKafkaServiceImpl.java | 23 ---- .../impl/JournalReplyingKafkaServiceImpl.java | 20 ---- .../impl/OrgReplyingKafkaServiceImpl.java | 20 ---- .../micord/ervu/security/SecurityConfig.java | 2 +- .../esia/service/EsiaAuthService.java | 2 +- .../JournalInMemoryStaticGridLoadService.java | 9 +- .../template/app/component/log_out.html | 3 +- .../ts/modules/security/guard/auth.guard.ts | 29 ++--- 12 files changed, 73 insertions(+), 251 deletions(-) delete mode 100644 backend/src/main/java/ru/micord/ervu/kafka/ErvuProducerInterceptor.java delete mode 100644 backend/src/main/java/ru/micord/ervu/kafka/service/impl/ExcerptReplyingKafkaServiceImpl.java delete mode 100644 backend/src/main/java/ru/micord/ervu/kafka/service/impl/JournalReplyingKafkaServiceImpl.java delete mode 100644 backend/src/main/java/ru/micord/ervu/kafka/service/impl/OrgReplyingKafkaServiceImpl.java diff --git a/backend/src/main/java/ru/micord/ervu/kafka/ErvuProducerInterceptor.java b/backend/src/main/java/ru/micord/ervu/kafka/ErvuProducerInterceptor.java deleted file mode 100644 index 18e3056e..00000000 --- a/backend/src/main/java/ru/micord/ervu/kafka/ErvuProducerInterceptor.java +++ /dev/null @@ -1,33 +0,0 @@ -package ru.micord.ervu.kafka; - -import java.nio.charset.StandardCharsets; -import java.util.Map; -import java.util.UUID; - -import org.apache.kafka.clients.producer.ProducerInterceptor; -import org.apache.kafka.clients.producer.ProducerRecord; -import org.apache.kafka.clients.producer.RecordMetadata; - -/** - * @author gulnaz - */ -public class ErvuProducerInterceptor implements ProducerInterceptor { - - @Override - public ProducerRecord onSend(ProducerRecord record) { - if (record.topic().startsWith("ervu")) { - record.headers().remove("messageId"); - record.headers().add("messageId", UUID.randomUUID().toString().getBytes(StandardCharsets.UTF_8)); - } - return record; - } - - @Override - public void onAcknowledgement(RecordMetadata recordMetadata, Exception e) {} - - @Override - public void close() {} - - @Override - public void configure(Map map) {} -} diff --git a/backend/src/main/java/ru/micord/ervu/kafka/ReplyingKafkaConfig.java b/backend/src/main/java/ru/micord/ervu/kafka/ReplyingKafkaConfig.java index bcb64b1d..d9b83ecb 100644 --- a/backend/src/main/java/ru/micord/ervu/kafka/ReplyingKafkaConfig.java +++ b/backend/src/main/java/ru/micord/ervu/kafka/ReplyingKafkaConfig.java @@ -6,6 +6,7 @@ import org.apache.kafka.clients.producer.ProducerConfig; import org.apache.kafka.common.config.SaslConfigs; import org.apache.kafka.common.serialization.StringDeserializer; import org.apache.kafka.common.serialization.StringSerializer; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -14,11 +15,16 @@ import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory; import org.springframework.kafka.core.ConsumerFactory; import org.springframework.kafka.core.DefaultKafkaConsumerFactory; import org.springframework.kafka.core.DefaultKafkaProducerFactory; -import org.springframework.kafka.core.KafkaTemplate; import org.springframework.kafka.core.ProducerFactory; +import org.springframework.kafka.listener.ConcurrentMessageListenerContainer; +import org.springframework.kafka.requestreply.CorrelationKey; +import org.springframework.kafka.requestreply.ReplyingKafkaTemplate; +import java.nio.charset.StandardCharsets; +import java.time.Duration; import java.util.HashMap; import java.util.Map; +import java.util.UUID; @Configuration @EnableKafka @@ -47,8 +53,8 @@ public class ReplyingKafkaConfig { @Value("${ervu.kafka.sasl.mechanism}") private String saslMechanism; - @Bean - public ProducerFactory ervuProducerFactory() { + @Bean("ervuProducerFactory") + public ProducerFactory producerFactory() { Map configProps = new HashMap<>(); configProps.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers); configProps.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class); @@ -60,13 +66,8 @@ public class ReplyingKafkaConfig { return new DefaultKafkaProducerFactory<>(configProps); } - @Bean("ervuTemplate") - public KafkaTemplate kafkaTemplate() { - return new KafkaTemplate<>(ervuProducerFactory()); - } - - @Bean("ervuConsumerFactory") - public ConsumerFactory ervuConsumerFactory() { + @Bean + public ConsumerFactory consumerFactory() { Map configProps = new HashMap<>(); configProps.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers); configProps.put(ConsumerConfig.GROUP_ID_CONFIG, groupId); @@ -79,68 +80,33 @@ public class ReplyingKafkaConfig { return new DefaultKafkaConsumerFactory<>(configProps); } - @Bean + @Bean("ervuContainerFactory") public ConcurrentKafkaListenerContainerFactory kafkaListenerContainerFactory() { - ConcurrentKafkaListenerContainerFactory factory = new ConcurrentKafkaListenerContainerFactory<>(); - factory.setConsumerFactory(ervuConsumerFactory()); + ConcurrentKafkaListenerContainerFactory factory = + new ConcurrentKafkaListenerContainerFactory<>(); + factory.setConsumerFactory(consumerFactory()); return factory; } -// @Bean("excerpt-container") -// public ConcurrentMessageListenerContainer excerptReplyContainer( -// ConcurrentKafkaListenerContainerFactory factory) { -// ConcurrentMessageListenerContainer container = factory.createContainer( -// excerptReplyTopic); -// container.getContainerProperties().setGroupId(groupId); -// return container; -// } -// -// @Bean("excerpt-template") -// public ReplyingKafkaTemplate excerptReplyingKafkaTemplate( -// @Qualifier("ervu") ProducerFactory pf, -// @Qualifier("excerpt-container") ConcurrentMessageListenerContainer container) { -// return initReplyingKafkaTemplate(pf, container); -// } -// -// @Bean("org") -// public ConcurrentMessageListenerContainer replyContainer( -// ConcurrentKafkaListenerContainerFactory factory) { -// ConcurrentMessageListenerContainer container = factory.createContainer( -// orgReplyTopic); -// container.getContainerProperties().setGroupId(groupId); -// return container; -// } -// -// @Bean("journal") -// public ConcurrentMessageListenerContainer journalReplyContainer( -// ConcurrentKafkaListenerContainerFactory factory) { -// ConcurrentMessageListenerContainer container = factory.createContainer( -// journalReplyTopic); -// container.getContainerProperties().setGroupId(groupId); -// return container; -// } -// -// @Bean("org") -// public ReplyingKafkaTemplate orgReplyingKafkaTemplate( -// @Qualifier("ervu") ProducerFactory pf, -// @Qualifier("org") ConcurrentMessageListenerContainer container) { -// return initReplyingKafkaTemplate(pf, container); -// } -// -// @Bean("journal") -// public ReplyingKafkaTemplate journalReplyingKafkaTemplate( -// @Qualifier("ervu") ProducerFactory pf, -// @Qualifier("journal") ConcurrentMessageListenerContainer container) { -// return initReplyingKafkaTemplate(pf, container); -// } -// -// private ReplyingKafkaTemplate initReplyingKafkaTemplate( -// ProducerFactory pf, -// ConcurrentMessageListenerContainer container) { -// ReplyingKafkaTemplate replyingKafkaTemplate = -// new ReplyingKafkaTemplate<>(pf, container); -// replyingKafkaTemplate.setCorrelationHeaderName("messageId"); -// replyingKafkaTemplate.setDefaultReplyTimeout(Duration.ofSeconds(replyTimeout)); -// return replyingKafkaTemplate; -// } + @Bean + public ConcurrentMessageListenerContainer replyContainer( + @Qualifier("ervuContainerFactory") ConcurrentKafkaListenerContainerFactory kafkaListenerContainerFactory) { + ConcurrentMessageListenerContainer container = + kafkaListenerContainerFactory.createContainer(orgReplyTopic, excerptReplyTopic, journalReplyTopic); + container.getContainerProperties().setGroupId(groupId); + return container; + } + + @Bean + public ReplyingKafkaTemplate replyingKafkaTemplate( + @Qualifier("ervuProducerFactory") ProducerFactory producerFactory, + ConcurrentMessageListenerContainer replyContainer) { + ReplyingKafkaTemplate replyingKafkaTemplate = + new ReplyingKafkaTemplate<>(producerFactory, replyContainer); + replyingKafkaTemplate.setCorrelationHeaderName("messageId"); + replyingKafkaTemplate.setCorrelationIdStrategy(record -> + new CorrelationKey(UUID.randomUUID().toString().getBytes(StandardCharsets.UTF_8))); + replyingKafkaTemplate.setDefaultReplyTimeout(Duration.ofSeconds(replyTimeout)); + return replyingKafkaTemplate; + } } diff --git a/backend/src/main/java/ru/micord/ervu/kafka/controller/ErvuKafkaController.java b/backend/src/main/java/ru/micord/ervu/kafka/controller/ErvuKafkaController.java index 7acc617a..7ee85074 100644 --- a/backend/src/main/java/ru/micord/ervu/kafka/controller/ErvuKafkaController.java +++ b/backend/src/main/java/ru/micord/ervu/kafka/controller/ErvuKafkaController.java @@ -24,7 +24,6 @@ import ru.micord.ervu.security.webbpm.jwt.service.JwtTokenService; public class ErvuKafkaController { @Autowired -// @Qualifier("excerpt-service") private ReplyingKafkaService replyingKafkaService; @Autowired diff --git a/backend/src/main/java/ru/micord/ervu/kafka/service/impl/BaseReplyingKafkaServiceImpl.java b/backend/src/main/java/ru/micord/ervu/kafka/service/impl/BaseReplyingKafkaServiceImpl.java index de01ac72..60d6f0b6 100644 --- a/backend/src/main/java/ru/micord/ervu/kafka/service/impl/BaseReplyingKafkaServiceImpl.java +++ b/backend/src/main/java/ru/micord/ervu/kafka/service/impl/BaseReplyingKafkaServiceImpl.java @@ -1,22 +1,13 @@ package ru.micord.ervu.kafka.service.impl; -import java.nio.charset.StandardCharsets; -import java.time.Duration; -import java.util.Arrays; -import java.util.Collections; import java.util.Optional; -import java.util.UUID; -import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.ExecutionException; -import org.apache.kafka.clients.consumer.Consumer; import org.apache.kafka.clients.consumer.ConsumerRecord; -import org.apache.kafka.clients.consumer.ConsumerRecords; import org.apache.kafka.clients.producer.ProducerRecord; import org.apache.kafka.common.header.internals.RecordHeader; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.kafka.core.ConsumerFactory; -import org.springframework.kafka.core.KafkaTemplate; +import org.springframework.kafka.requestreply.ReplyingKafkaTemplate; +import org.springframework.kafka.requestreply.RequestReplyFuture; import org.springframework.kafka.support.KafkaHeaders; import org.springframework.stereotype.Service; import ru.micord.ervu.kafka.service.ReplyingKafkaService; @@ -27,22 +18,11 @@ import ru.micord.ervu.kafka.service.ReplyingKafkaService; @Service public class BaseReplyingKafkaServiceImpl implements ReplyingKafkaService { - private static final String MESSAGE_ID_HEADER = "messageId"; - -// protected abstract ReplyingKafkaTemplate getReplyingKafkaTemplate() - private final KafkaTemplate kafkaTemplate; - private final ConsumerFactory consumerFactory; - - @Value("${ervu.kafka.group.id}") - private String groupId; - @Value("${ervu.kafka.reply.timeout:30}") - private long replyTimeout; + private final ReplyingKafkaTemplate replyingKafkaTemplate; public BaseReplyingKafkaServiceImpl( - @Qualifier("ervuTemplate") KafkaTemplate kafkaTemplate, - @Qualifier("ervuConsumerFactory") ConsumerFactory consumerFactory) { - this.kafkaTemplate = kafkaTemplate; - this.consumerFactory = consumerFactory; + ReplyingKafkaTemplate replyingKafkaTemplate) { + this.replyingKafkaTemplate = replyingKafkaTemplate; } public String sendMessageAndGetReply(String requestTopic, @@ -50,43 +30,15 @@ public class BaseReplyingKafkaServiceImpl implements ReplyingKafkaService { String requestMessage) { ProducerRecord record = new ProducerRecord<>(requestTopic, requestMessage); record.headers().add(new RecordHeader(KafkaHeaders.REPLY_TOPIC, replyTopic.getBytes())); - //TODO fix No pending reply error SUPPORT-8591 -// RequestReplyFuture replyFuture = getReplyingKafkaTemplate() -// .sendAndReceive(record); -// -// try { -// return Optional.ofNullable(replyFuture.get()) -// .map(ConsumerRecord::value) -// .orElseThrow(() -> new RuntimeException("Kafka return result is null.")); -// } -// catch (InterruptedException | ExecutionException e) { -// throw new RuntimeException("Failed to get kafka response.", e); -// } + RequestReplyFuture replyFuture = replyingKafkaTemplate.sendAndReceive(record); - String messageId = UUID.randomUUID().toString(); - record.headers().add(MESSAGE_ID_HEADER, messageId.getBytes(StandardCharsets.UTF_8)); - kafkaTemplate.send(record); - AtomicReference responseRef = new AtomicReference<>(null); - - try (Consumer consumer = - consumerFactory.createConsumer(groupId, null)) { - consumer.subscribe(Collections.singletonList(replyTopic)); - ConsumerRecords consumerRecords = consumer.poll(Duration.ofSeconds(replyTimeout)); - - for (ConsumerRecord consumerRecord : consumerRecords) { - boolean match = Arrays.stream(consumerRecord.headers().toArray()) - .anyMatch(header -> header.key().equals(MESSAGE_ID_HEADER) - && messageId.equals( - new String(header.value(), StandardCharsets.UTF_8))); - - if (match) { - responseRef.set(consumerRecord.value()); - consumer.commitSync(); - break; - } - } + try { + return Optional.ofNullable(replyFuture.get()) + .map(ConsumerRecord::value) + .orElseThrow(() -> new RuntimeException("Kafka return result is null.")); + } + catch (InterruptedException | ExecutionException e) { + throw new RuntimeException("Failed to get kafka response.", e); } - return Optional.ofNullable(responseRef.get()) - .orElseThrow(() -> new RuntimeException("Kafka return result is null")); } } diff --git a/backend/src/main/java/ru/micord/ervu/kafka/service/impl/ExcerptReplyingKafkaServiceImpl.java b/backend/src/main/java/ru/micord/ervu/kafka/service/impl/ExcerptReplyingKafkaServiceImpl.java deleted file mode 100644 index 9f9399af..00000000 --- a/backend/src/main/java/ru/micord/ervu/kafka/service/impl/ExcerptReplyingKafkaServiceImpl.java +++ /dev/null @@ -1,23 +0,0 @@ -package ru.micord.ervu.kafka.service.impl; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.kafka.requestreply.ReplyingKafkaTemplate; -import org.springframework.stereotype.Service; - -/** - * @author Eduard Tihomirov - */ -//@Service -//@Qualifier("excerpt-service") -public class ExcerptReplyingKafkaServiceImpl { - -// @Autowired -// @Qualifier("excerpt-template") -// private ReplyingKafkaTemplate excerptReplyingKafkaTemplate; -// -// @Override -// protected ReplyingKafkaTemplate getReplyingKafkaTemplate() { -// return excerptReplyingKafkaTemplate; -// } -} diff --git a/backend/src/main/java/ru/micord/ervu/kafka/service/impl/JournalReplyingKafkaServiceImpl.java b/backend/src/main/java/ru/micord/ervu/kafka/service/impl/JournalReplyingKafkaServiceImpl.java deleted file mode 100644 index 65b178d0..00000000 --- a/backend/src/main/java/ru/micord/ervu/kafka/service/impl/JournalReplyingKafkaServiceImpl.java +++ /dev/null @@ -1,20 +0,0 @@ -package ru.micord.ervu.kafka.service.impl; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.kafka.requestreply.ReplyingKafkaTemplate; -import org.springframework.stereotype.Service; - -//@Service -//@Qualifier("journal") -public class JournalReplyingKafkaServiceImpl { - -// @Autowired -// @Qualifier("journal") -// private ReplyingKafkaTemplate journalReplyingKafkaTemplate; -// -// @Override -// protected ReplyingKafkaTemplate getReplyingKafkaTemplate() { -// return journalReplyingKafkaTemplate; -// } -} diff --git a/backend/src/main/java/ru/micord/ervu/kafka/service/impl/OrgReplyingKafkaServiceImpl.java b/backend/src/main/java/ru/micord/ervu/kafka/service/impl/OrgReplyingKafkaServiceImpl.java deleted file mode 100644 index 9e129efa..00000000 --- a/backend/src/main/java/ru/micord/ervu/kafka/service/impl/OrgReplyingKafkaServiceImpl.java +++ /dev/null @@ -1,20 +0,0 @@ -package ru.micord.ervu.kafka.service.impl; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.kafka.requestreply.ReplyingKafkaTemplate; -import org.springframework.stereotype.Service; - -//@Service -//@Qualifier("org") -public class OrgReplyingKafkaServiceImpl { - -// @Autowired -// @Qualifier("org") -// private ReplyingKafkaTemplate orgReplyingKafkaTemplate; -// -// @Override -// protected ReplyingKafkaTemplate getReplyingKafkaTemplate() { -// return orgReplyingKafkaTemplate; -// } -} diff --git a/backend/src/main/java/ru/micord/ervu/security/SecurityConfig.java b/backend/src/main/java/ru/micord/ervu/security/SecurityConfig.java index c9d8e2be..a91e8d17 100644 --- a/backend/src/main/java/ru/micord/ervu/security/SecurityConfig.java +++ b/backend/src/main/java/ru/micord/ervu/security/SecurityConfig.java @@ -28,7 +28,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter { } protected void httpConfigure(HttpSecurity httpSecurity) throws Exception { - String[] permitAll = {"/version", "/esia/url", "/esia/auth", "esia/refresh"}; + String[] permitAll = {"/version", "/esia/url", "/esia/auth", "esia/refresh", "/esia/logout"}; httpSecurity.authorizeRequests() .antMatchers(permitAll).permitAll() diff --git a/backend/src/main/java/ru/micord/ervu/security/esia/service/EsiaAuthService.java b/backend/src/main/java/ru/micord/ervu/security/esia/service/EsiaAuthService.java index 5858c626..a66e3812 100644 --- a/backend/src/main/java/ru/micord/ervu/security/esia/service/EsiaAuthService.java +++ b/backend/src/main/java/ru/micord/ervu/security/esia/service/EsiaAuthService.java @@ -73,7 +73,6 @@ public class EsiaAuthService { private JwtTokenService jwtTokenService; @Autowired -// @Qualifier("org") private ReplyingKafkaService replyingKafkaService; @Autowired @@ -238,6 +237,7 @@ public class EsiaAuthService { cookieRefresh.setHttpOnly(true); cookieRefresh.setPath(cookiePath); response.addCookie(cookieRefresh); + String ervuId = getErvuId(accessToken, esiaAccessToken.getSbj_id()); Token token = jwtTokenService.createAccessToken(esiaAccessToken.getSbj_id(), tokenResponse.getExpires_in(), ervuId); Cookie authToken = new Cookie("auth_token", token.getValue()); diff --git a/backend/src/main/java/ru/micord/ervu/service/grid/impl/JournalInMemoryStaticGridLoadService.java b/backend/src/main/java/ru/micord/ervu/service/grid/impl/JournalInMemoryStaticGridLoadService.java index 7ab7cbc8..f01e21dd 100644 --- a/backend/src/main/java/ru/micord/ervu/service/grid/impl/JournalInMemoryStaticGridLoadService.java +++ b/backend/src/main/java/ru/micord/ervu/service/grid/impl/JournalInMemoryStaticGridLoadService.java @@ -28,11 +28,10 @@ public class JournalInMemoryStaticGridLoadService implements private final JwtTokenService jwtTokenService; private final InteractionService interactionService; - private final ReplyingKafkaService ervuReplyingKafkaService; + private final ReplyingKafkaService replyingKafkaService; private final ObjectMapper objectMapper; private final HttpServletRequest request; - @Value("${ervu.kafka.journal.request.topic}") private String requestTopic; @Value("${ervu.kafka.journal.reply.topic}") @@ -42,11 +41,11 @@ public class JournalInMemoryStaticGridLoadService implements public JournalInMemoryStaticGridLoadService(JwtTokenService jwtTokenService, InteractionService interactionService, - ReplyingKafkaService ervuReplyingKafkaService, + ReplyingKafkaService replyingKafkaService, ObjectMapper objectMapper, HttpServletRequest request) { this.jwtTokenService = jwtTokenService; this.interactionService = interactionService; - this.ervuReplyingKafkaService = ervuReplyingKafkaService; + this.replyingKafkaService = replyingKafkaService; this.objectMapper = objectMapper; this.request = request; } @@ -61,7 +60,7 @@ public class JournalInMemoryStaticGridLoadService implements List ervuJournalList; try { - String responseJsonString = ervuReplyingKafkaService.sendMessageAndGetReply(requestTopic, + String responseJsonString = replyingKafkaService.sendMessageAndGetReply(requestTopic, replyTopic, objectMapper.writeValueAsString(journalFileDataRequest)); JournalFileDataResponse journalFileDataResponse = objectMapper.readValue(responseJsonString, JournalFileDataResponse.class); diff --git a/frontend/src/resources/template/app/component/log_out.html b/frontend/src/resources/template/app/component/log_out.html index cf7831d2..4ec0c57d 100644 --- a/frontend/src/resources/template/app/component/log_out.html +++ b/frontend/src/resources/template/app/component/log_out.html @@ -3,4 +3,5 @@
{{getOrgUnitName()}}
Данные организации - \ No newline at end of file + + \ No newline at end of file diff --git a/frontend/src/ts/modules/security/guard/auth.guard.ts b/frontend/src/ts/modules/security/guard/auth.guard.ts index 87201687..480b992a 100644 --- a/frontend/src/ts/modules/security/guard/auth.guard.ts +++ b/frontend/src/ts/modules/security/guard/auth.guard.ts @@ -42,22 +42,23 @@ export abstract class AuthGuard implements CanActivate { else if (code) { const params = new HttpParams().set('code', code); this.httpClient.get("esia/auth", - {params: params, responseType: 'text', observe: 'response'}) + { + params: params, responseType: 'text', observe: 'response', headers: { + "Error-intercept-skip": "true" + } + }) .toPromise() .then( - (response) => { - if (response.status == 200) { - window.open(url.origin + url.pathname, "_self"); - } - else { - let errorMessage = response.body; - this.messageService.error(errorMessage) - throw new Error(errorMessage) - } - }) - .catch((reason) => - console.error(reason) - ); + (response) => { + window.open(url.origin + url.pathname, "_self"); + }) + .catch((reason) => { + let errorMessage = reason.error.messages != null + ? reason.error.messages + : reason.error.replaceAll('\\', ''); + this.messageService.error(errorMessage); + console.error(reason); + }); return false; } else { From 8e74699426d155088cd3467d7d8d7681d437d0c1 Mon Sep 17 00:00:00 2001 From: kochetkov Date: Wed, 23 Oct 2024 12:01:08 +0300 Subject: [PATCH 19/26] SUPPORT-8609 small fix --- .../security/webbpm/jwt/JwtAuthenticationProvider.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/backend/src/main/java/ru/micord/ervu/security/webbpm/jwt/JwtAuthenticationProvider.java b/backend/src/main/java/ru/micord/ervu/security/webbpm/jwt/JwtAuthenticationProvider.java index 67e359c7..f709679f 100644 --- a/backend/src/main/java/ru/micord/ervu/security/webbpm/jwt/JwtAuthenticationProvider.java +++ b/backend/src/main/java/ru/micord/ervu/security/webbpm/jwt/JwtAuthenticationProvider.java @@ -1,5 +1,7 @@ package ru.micord.ervu.security.webbpm.jwt; +import java.util.Collections; + import io.jsonwebtoken.ExpiredJwtException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.AuthenticationProvider; @@ -44,10 +46,12 @@ public class JwtAuthenticationProvider implements AuthenticationProvider { throw new BadCredentialsException("Auth token is not valid for user " + token.getUserAccountId()); } - UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = - new UsernamePasswordAuthenticationToken(token.getUserAccountId(), null); + UsernamePasswordAuthenticationToken pwdToken = + UsernamePasswordAuthenticationToken.authenticated(token.getUserAccountId(), null, + Collections.emptyList() + ); - return new JwtAuthentication(usernamePasswordAuthenticationToken, token.getUserAccountId(), token.getValue()); + return new JwtAuthentication(pwdToken, token.getUserAccountId(), token.getValue()); } @Override From c524fcec3f07a21deeab20b6cdc6b0e11cbb4113 Mon Sep 17 00:00:00 2001 From: kochetkov Date: Wed, 23 Oct 2024 14:38:22 +0300 Subject: [PATCH 20/26] SUPPORT-8609 small fixes --- frontend/src/resources/template/app/component/log_out.html | 1 + frontend/src/ts/modules/app/component/logout.component.ts | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/frontend/src/resources/template/app/component/log_out.html b/frontend/src/resources/template/app/component/log_out.html index 319b0904..996c245f 100644 --- a/frontend/src/resources/template/app/component/log_out.html +++ b/frontend/src/resources/template/app/component/log_out.html @@ -4,3 +4,4 @@ Данные организации + diff --git a/frontend/src/ts/modules/app/component/logout.component.ts b/frontend/src/ts/modules/app/component/logout.component.ts index 0ac178ae..d709ae56 100644 --- a/frontend/src/ts/modules/app/component/logout.component.ts +++ b/frontend/src/ts/modules/app/component/logout.component.ts @@ -32,7 +32,9 @@ export class LogOutComponent implements OnInit{ } public logout(): void { - this.authenticationService.logout(); + this.authenticationService.logout().then(url => { + window.open(url, "_self"); + }); } public getUserFullname(): string { From 8d021cd7a23526ab09300cc61b6ff9a594f3c2e2 Mon Sep 17 00:00:00 2001 From: kochetkov Date: Wed, 23 Oct 2024 22:40:32 +0300 Subject: [PATCH 21/26] update versions to snapshot --- backend/pom.xml | 2 +- distribution/pom.xml | 2 +- frontend/pom.xml | 2 +- pom.xml | 2 +- resources/pom.xml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/backend/pom.xml b/backend/pom.xml index 2dbaaab1..da663d8f 100644 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -5,7 +5,7 @@ ru.micord.ervu.lkrp ul - 1.8.1 + 1.8.2-SNAPSHOT ru.micord.ervu.lkrp.ul backend diff --git a/distribution/pom.xml b/distribution/pom.xml index 603dc18a..e96b636e 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -4,7 +4,7 @@ ru.micord.ervu.lkrp ul - 1.8.1 + 1.8.2-SNAPSHOT ru.micord.ervu.lkrp.ul diff --git a/frontend/pom.xml b/frontend/pom.xml index fe58f034..60ed9eda 100644 --- a/frontend/pom.xml +++ b/frontend/pom.xml @@ -4,7 +4,7 @@ ru.micord.ervu.lkrp ul - 1.8.1 + 1.8.2-SNAPSHOT ru.micord.ervu.lkrp.ul diff --git a/pom.xml b/pom.xml index 68808a31..d8794b83 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 ru.micord.ervu.lkrp ul - 1.8.1 + 1.8.2-SNAPSHOT pom backend diff --git a/resources/pom.xml b/resources/pom.xml index 2ebd2640..d9261857 100644 --- a/resources/pom.xml +++ b/resources/pom.xml @@ -4,7 +4,7 @@ ru.micord.ervu.lkrp ul - 1.8.1 + 1.8.2-SNAPSHOT ru.micord.ervu.lkrp.ul From 031617b4c2010fabee4f77fe7e5fd39cfca81f76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A5=D0=B0=D0=BB=D1=82=D0=BE=D0=B1=D0=B8=D0=BD=20=D0=95?= =?UTF-8?q?=D0=B2=D0=B3=D0=B5=D0=BD=D0=B8=D0=B9?= Date: Thu, 24 Oct 2024 09:18:15 +0300 Subject: [PATCH 22/26] fix --- .../micord/ervu/security/webbpm/jwt/helper/SecurityHelper.java | 2 +- config/micord.env | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/src/main/java/ru/micord/ervu/security/webbpm/jwt/helper/SecurityHelper.java b/backend/src/main/java/ru/micord/ervu/security/webbpm/jwt/helper/SecurityHelper.java index 57f4c7f0..0a222c79 100644 --- a/backend/src/main/java/ru/micord/ervu/security/webbpm/jwt/helper/SecurityHelper.java +++ b/backend/src/main/java/ru/micord/ervu/security/webbpm/jwt/helper/SecurityHelper.java @@ -11,7 +11,7 @@ import static ru.micord.ervu.security.webbpm.jwt.util.SecurityUtil.AUTH_TOKEN; import static ru.micord.ervu.security.webbpm.jwt.util.SecurityUtil.createCookie; public final class SecurityHelper { - @Value("${cookie-path:#{null}}") + @Value("${cookie.path:#{null}}") private String accessCookiePath; public void clearAccessCookies(HttpServletResponse response) { diff --git a/config/micord.env b/config/micord.env index fcb6141a..205c80ab 100644 --- a/config/micord.env +++ b/config/micord.env @@ -53,3 +53,4 @@ S3_ACCESS_KEY=rlTdTvkmSXu9FsLhfecw S3_SECRET_KEY=NUmY0wwRIEyAd98GCKd1cOgJWvLQYAcMMul5Ulu0 ESIA_TOKEN_CLEAR_CRON=0 0 */1 * * * +COOKIE_PATH=/ul \ No newline at end of file From 93223065cd6d4abc3c0973a4dc2d9da7f2b5dc06 Mon Sep 17 00:00:00 2001 From: Eduard Tihomirov Date: Thu, 24 Oct 2024 10:14:50 +0300 Subject: [PATCH 23/26] fix logout --- .../ervu/security/LogoutSuccessHandler.java | 4 +--- .../micord/ervu/security/SecurityConfig.java | 5 ++++- .../esia/service/EsiaAuthService.java | 18 +++++++++--------- .../jwt/filter/JwtAuthenticationFilter.java | 19 ++++++++++++++++--- .../ervu/security/webbpm/jwt/model/Token.java | 8 +++++++- .../webbpm/jwt/service/JwtTokenService.java | 6 +++--- .../modules/app/component/logout.component.ts | 4 +--- .../security/authentication.service.ts | 2 +- 8 files changed, 42 insertions(+), 24 deletions(-) diff --git a/backend/src/main/java/ru/micord/ervu/security/LogoutSuccessHandler.java b/backend/src/main/java/ru/micord/ervu/security/LogoutSuccessHandler.java index 9993b234..3867b41a 100644 --- a/backend/src/main/java/ru/micord/ervu/security/LogoutSuccessHandler.java +++ b/backend/src/main/java/ru/micord/ervu/security/LogoutSuccessHandler.java @@ -25,9 +25,7 @@ public class LogoutSuccessHandler public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException { String url = esiaAuthService.logout(request, response); - response.setStatus(HttpServletResponse.SC_OK); - response.getWriter().write(url); - response.getWriter().flush(); + response.sendRedirect(url); CsrfToken csrfToken = this.csrfTokenRepository.generateToken(request); this.csrfTokenRepository.saveToken(csrfToken, request, response); } diff --git a/backend/src/main/java/ru/micord/ervu/security/SecurityConfig.java b/backend/src/main/java/ru/micord/ervu/security/SecurityConfig.java index 2297c073..eb00f908 100644 --- a/backend/src/main/java/ru/micord/ervu/security/SecurityConfig.java +++ b/backend/src/main/java/ru/micord/ervu/security/SecurityConfig.java @@ -24,6 +24,7 @@ import ru.micord.ervu.security.webbpm.jwt.JwtMatcher; import ru.micord.ervu.security.webbpm.jwt.UnauthorizedEntryPoint; import ru.micord.ervu.security.webbpm.jwt.filter.JwtAuthenticationFilter; import ru.micord.ervu.security.webbpm.jwt.helper.SecurityHelper; +import ru.micord.ervu.security.webbpm.jwt.service.JwtTokenService; import static ru.micord.ervu.security.SecurityConstants.ESIA_LOGOUT; @@ -41,6 +42,8 @@ public class SecurityConfig { private FilterChainExceptionHandler filterChainExceptionHandler; @Autowired private JwtAuthenticationProvider jwtAuthenticationProvider; + @Autowired + private JwtTokenService jwtTokenService; @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) { @@ -99,7 +102,7 @@ public class SecurityConfig { public JwtAuthenticationFilter jwtAuthenticationFilter(SecurityHelper securityHelper, AuthenticationManager manager) { JwtAuthenticationFilter jwtAuthenticationFilter = new JwtAuthenticationFilter( - new JwtMatcher("/**", PERMIT_ALL), entryPoint(), securityHelper); + new JwtMatcher("/**", PERMIT_ALL), entryPoint(), securityHelper, jwtTokenService); jwtAuthenticationFilter.setAuthenticationManager(manager); return jwtAuthenticationFilter; } diff --git a/backend/src/main/java/ru/micord/ervu/security/esia/service/EsiaAuthService.java b/backend/src/main/java/ru/micord/ervu/security/esia/service/EsiaAuthService.java index 4ba89139..55b24209 100644 --- a/backend/src/main/java/ru/micord/ervu/security/esia/service/EsiaAuthService.java +++ b/backend/src/main/java/ru/micord/ervu/security/esia/service/EsiaAuthService.java @@ -216,19 +216,12 @@ public class EsiaAuthService { boolean hasRole = ulDataService.checkRole(accessToken); EsiaAccessToken esiaAccessToken = ulDataService.readToken(accessToken); String prnOid = esiaAccessToken.getSbj_id(); - if (!hasRole) { - LOGGER.error("The user with id = " + prnOid + " does not have the required role"); - return new ResponseEntity<>( - "Доступ запрещен. Пользователь должен быть включен в группу \"Сотрудник, ответственный за военно-учетную работу\" в ЕСИА", - HttpStatus.FORBIDDEN - ); - } String refreshToken = tokenResponse.getRefresh_token(); String ervuId = getErvuId(accessToken, prnOid); Long expiresIn = tokenResponse.getExpires_in(); TokensStore.addAccessToken(prnOid, accessToken, expiresIn); TokensStore.addRefreshToken(prnOid, refreshToken, expiresIn); - Token token = jwtTokenService.createAccessToken(esiaAccessToken.getSbj_id(), expiresIn, ervuId); + Token token = jwtTokenService.createAccessToken(esiaAccessToken.getSbj_id(), expiresIn, ervuId, hasRole); int expiry = tokenResponse.getExpires_in().intValue(); Cookie accessCookie = securityHelper.createAccessCookie(token.getValue(), expiry); response.addCookie(accessCookie); @@ -241,6 +234,13 @@ public class EsiaAuthService { SecurityContextHolder.setContext(context); Cookie authMarkerCookie = securityHelper.createAuthMarkerCookie("true", expiry); response.addCookie(authMarkerCookie); + if (!hasRole) { + LOGGER.error("The user with id = " + prnOid + " does not have the required role"); + return new ResponseEntity<>( + "Доступ запрещен. Пользователь должен быть включен в группу \"Сотрудник, ответственный за военно-учетную работу\" в ЕСИА", + HttpStatus.FORBIDDEN + ); + } return ResponseEntity.ok("Authentication successful"); } catch (Exception e) { @@ -306,7 +306,7 @@ public class EsiaAuthService { TokensStore.addAccessToken(prnOid, accessToken, expiresIn); TokensStore.addRefreshToken(prnOid, newRefreshToken, expiresIn); String ervuId = getErvuId(accessToken, prnOid); - Token token = jwtTokenService.createAccessToken(esiaAccessToken.getSbj_id(), expiresIn, ervuId); + Token token = jwtTokenService.createAccessToken(esiaAccessToken.getSbj_id(), expiresIn, ervuId, true); int expiry = tokenResponse.getExpires_in().intValue(); Cookie accessCookie = securityHelper.createAccessCookie(token.getValue(), expiry); response.addCookie(accessCookie); diff --git a/backend/src/main/java/ru/micord/ervu/security/webbpm/jwt/filter/JwtAuthenticationFilter.java b/backend/src/main/java/ru/micord/ervu/security/webbpm/jwt/filter/JwtAuthenticationFilter.java index 17d74fc1..a030df8c 100644 --- a/backend/src/main/java/ru/micord/ervu/security/webbpm/jwt/filter/JwtAuthenticationFilter.java +++ b/backend/src/main/java/ru/micord/ervu/security/webbpm/jwt/filter/JwtAuthenticationFilter.java @@ -7,6 +7,7 @@ import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import io.jsonwebtoken.ExpiredJwtException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.security.authentication.CredentialsExpiredException; @@ -18,6 +19,8 @@ import org.springframework.security.web.authentication.AbstractAuthenticationPro import org.springframework.security.web.util.matcher.RequestMatcher; import ru.micord.ervu.security.webbpm.jwt.JwtAuthentication; import ru.micord.ervu.security.webbpm.jwt.helper.SecurityHelper; +import ru.micord.ervu.security.webbpm.jwt.model.Token; +import ru.micord.ervu.security.webbpm.jwt.service.JwtTokenService; import static ru.micord.ervu.security.webbpm.jwt.util.SecurityUtil.extractAuthToken; @@ -33,25 +36,35 @@ public class JwtAuthenticationFilter extends AbstractAuthenticationProcessingFil private final SecurityHelper securityHelper; + private final JwtTokenService jwtTokenService; + public JwtAuthenticationFilter(RequestMatcher requestMatcher, AuthenticationEntryPoint entryPoint, - SecurityHelper securityHelper) { + SecurityHelper securityHelper, + JwtTokenService jwtTokenService) { super(requestMatcher); this.entryPoint = entryPoint; this.securityHelper = securityHelper; + this.jwtTokenService = jwtTokenService; } @Override public Authentication attemptAuthentication(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws AuthenticationException { - String token = extractAuthToken(httpServletRequest); + String tokenStr = extractAuthToken(httpServletRequest); Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (authentication == null) { - authentication = new JwtAuthentication(null, null, token); + authentication = new JwtAuthentication(null, null, tokenStr); } try { authentication = getAuthenticationManager().authenticate(authentication); + if (!httpServletRequest.getRequestURI().endsWith("esia/logout")) { + Token token = jwtTokenService.getToken(tokenStr); + if (!token.getHasRole()) { + throw new CredentialsExpiredException("Invalid token. User has no required role"); + } + } } catch (CredentialsExpiredException e) { securityHelper.clearAccessCookies(httpServletResponse); diff --git a/backend/src/main/java/ru/micord/ervu/security/webbpm/jwt/model/Token.java b/backend/src/main/java/ru/micord/ervu/security/webbpm/jwt/model/Token.java index d7da8527..c64413ed 100644 --- a/backend/src/main/java/ru/micord/ervu/security/webbpm/jwt/model/Token.java +++ b/backend/src/main/java/ru/micord/ervu/security/webbpm/jwt/model/Token.java @@ -7,12 +7,14 @@ public class Token { private final String issuer; private final Date expirationDate; private final String value; + private final Boolean hasRole; - public Token(String userAccountId, String issuer, Date expirationDate, String value) { + public Token(String userAccountId, String issuer, Date expirationDate, String value, Boolean hasRole) { this.userAccountId = userAccountId; this.issuer = issuer; this.expirationDate = expirationDate; this.value = value; + this.hasRole = hasRole; } public String getUserAccountId() { @@ -34,4 +36,8 @@ public class Token { public String getValue() { return value; } + + public Boolean getHasRole() { + return hasRole; + } } diff --git a/backend/src/main/java/ru/micord/ervu/security/webbpm/jwt/service/JwtTokenService.java b/backend/src/main/java/ru/micord/ervu/security/webbpm/jwt/service/JwtTokenService.java index 22f627f2..cfadde3a 100644 --- a/backend/src/main/java/ru/micord/ervu/security/webbpm/jwt/service/JwtTokenService.java +++ b/backend/src/main/java/ru/micord/ervu/security/webbpm/jwt/service/JwtTokenService.java @@ -42,7 +42,7 @@ public class JwtTokenService { this.SIGNING_KEY = Keys.hmacShaKeyFor(encodedKey); } - public Token createAccessToken(String userAccountId, Long expiresIn, String ervuId) { + public Token createAccessToken(String userAccountId, Long expiresIn, String ervuId, Boolean hasRole) { Date expirationDate = new Date(System.currentTimeMillis() + 1000L * expiresIn); String value = Jwts.builder() @@ -52,7 +52,7 @@ public class JwtTokenService { .setExpiration(expirationDate) .signWith(SIGNING_KEY) .compact(); - return new Token(userAccountId + ":" + ervuId, tokenIssuerName, expirationDate, value); + return new Token(userAccountId + ":" + ervuId, tokenIssuerName, expirationDate, value, hasRole); } public boolean isValid(Token token) { @@ -74,7 +74,7 @@ public class JwtTokenService { .parseClaimsJws(token) .getBody(); - return new Token(claims.getSubject(), claims.getIssuer(), claims.getExpiration(), token); + return new Token(claims.getSubject(), claims.getIssuer(), claims.getExpiration(), token, claims.get("hasRole", Boolean.class)); } public String getAccessToken(HttpServletRequest request) { diff --git a/frontend/src/ts/modules/app/component/logout.component.ts b/frontend/src/ts/modules/app/component/logout.component.ts index d709ae56..0ac178ae 100644 --- a/frontend/src/ts/modules/app/component/logout.component.ts +++ b/frontend/src/ts/modules/app/component/logout.component.ts @@ -32,9 +32,7 @@ export class LogOutComponent implements OnInit{ } public logout(): void { - this.authenticationService.logout().then(url => { - window.open(url, "_self"); - }); + this.authenticationService.logout(); } public getUserFullname(): string { diff --git a/frontend/src/ts/modules/security/authentication.service.ts b/frontend/src/ts/modules/security/authentication.service.ts index f427b3d1..a6ae344f 100644 --- a/frontend/src/ts/modules/security/authentication.service.ts +++ b/frontend/src/ts/modules/security/authentication.service.ts @@ -18,7 +18,7 @@ export class AuthenticationService { } logout(): Promise { - return this.http.post('esia/logout', {}).toPromise(); + return this.http.post('esia/logout', {}).toPromise(); } public isAuthenticated(): boolean { From 73a6dad42b49145d7814485e9492dfadd98d901d Mon Sep 17 00:00:00 2001 From: Eduard Tihomirov Date: Thu, 24 Oct 2024 12:58:04 +0300 Subject: [PATCH 24/26] fix --- .../main/java/ru/micord/ervu/security/SecurityConfig.java | 5 ++--- .../ervu/security/webbpm/jwt/service/JwtTokenService.java | 1 + 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/src/main/java/ru/micord/ervu/security/SecurityConfig.java b/backend/src/main/java/ru/micord/ervu/security/SecurityConfig.java index eb00f908..a5aaa4d9 100644 --- a/backend/src/main/java/ru/micord/ervu/security/SecurityConfig.java +++ b/backend/src/main/java/ru/micord/ervu/security/SecurityConfig.java @@ -42,8 +42,6 @@ public class SecurityConfig { private FilterChainExceptionHandler filterChainExceptionHandler; @Autowired private JwtAuthenticationProvider jwtAuthenticationProvider; - @Autowired - private JwtTokenService jwtTokenService; @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) { @@ -100,7 +98,8 @@ public class SecurityConfig { @Bean public JwtAuthenticationFilter jwtAuthenticationFilter(SecurityHelper securityHelper, - AuthenticationManager manager) { + AuthenticationManager manager, + JwtTokenService jwtTokenService) { JwtAuthenticationFilter jwtAuthenticationFilter = new JwtAuthenticationFilter( new JwtMatcher("/**", PERMIT_ALL), entryPoint(), securityHelper, jwtTokenService); jwtAuthenticationFilter.setAuthenticationManager(manager); diff --git a/backend/src/main/java/ru/micord/ervu/security/webbpm/jwt/service/JwtTokenService.java b/backend/src/main/java/ru/micord/ervu/security/webbpm/jwt/service/JwtTokenService.java index cfadde3a..b04c226c 100644 --- a/backend/src/main/java/ru/micord/ervu/security/webbpm/jwt/service/JwtTokenService.java +++ b/backend/src/main/java/ru/micord/ervu/security/webbpm/jwt/service/JwtTokenService.java @@ -50,6 +50,7 @@ public class JwtTokenService { .setIssuer(tokenIssuerName) .setIssuedAt(new Date(System.currentTimeMillis())) .setExpiration(expirationDate) + .claim("hasRole", hasRole) .signWith(SIGNING_KEY) .compact(); return new Token(userAccountId + ":" + ervuId, tokenIssuerName, expirationDate, value, hasRole); From 541188a28d6437278eca10bce31f60fbc6a3a225 Mon Sep 17 00:00:00 2001 From: Eduard Tihomirov Date: Thu, 24 Oct 2024 15:18:37 +0300 Subject: [PATCH 25/26] fix logout --- .../java/ru/micord/ervu/security/LogoutSuccessHandler.java | 4 +++- frontend/src/ts/modules/security/authentication.service.ts | 6 ++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/backend/src/main/java/ru/micord/ervu/security/LogoutSuccessHandler.java b/backend/src/main/java/ru/micord/ervu/security/LogoutSuccessHandler.java index 3867b41a..9993b234 100644 --- a/backend/src/main/java/ru/micord/ervu/security/LogoutSuccessHandler.java +++ b/backend/src/main/java/ru/micord/ervu/security/LogoutSuccessHandler.java @@ -25,7 +25,9 @@ public class LogoutSuccessHandler public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException { String url = esiaAuthService.logout(request, response); - response.sendRedirect(url); + response.setStatus(HttpServletResponse.SC_OK); + response.getWriter().write(url); + response.getWriter().flush(); CsrfToken csrfToken = this.csrfTokenRepository.generateToken(request); this.csrfTokenRepository.saveToken(csrfToken, request, response); } diff --git a/frontend/src/ts/modules/security/authentication.service.ts b/frontend/src/ts/modules/security/authentication.service.ts index a6ae344f..03914174 100644 --- a/frontend/src/ts/modules/security/authentication.service.ts +++ b/frontend/src/ts/modules/security/authentication.service.ts @@ -17,8 +17,10 @@ export class AuthenticationService { return this.appConfigService.load().then(value => this.http.get("version").toPromise()) } - logout(): Promise { - return this.http.post('esia/logout', {}).toPromise(); + logout(): Promise { + return this.http.post('esia/logout', {}, { responseType: 'text' as 'json' }).toPromise().then(url => { + window.open(url, "_self"); + }); } public isAuthenticated(): boolean { From e257f122013ad17e41845fec0555106f5d46a1ee Mon Sep 17 00:00:00 2001 From: Zaripov Emil Date: Fri, 25 Oct 2024 12:02:35 +0300 Subject: [PATCH 26/26] set version 1.8.2 --- backend/pom.xml | 2 +- distribution/pom.xml | 2 +- frontend/pom.xml | 2 +- pom.xml | 2 +- resources/pom.xml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/backend/pom.xml b/backend/pom.xml index da663d8f..230190b3 100644 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -5,7 +5,7 @@ ru.micord.ervu.lkrp ul - 1.8.2-SNAPSHOT + 1.8.2 ru.micord.ervu.lkrp.ul backend diff --git a/distribution/pom.xml b/distribution/pom.xml index e96b636e..dbdea4b7 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -4,7 +4,7 @@ ru.micord.ervu.lkrp ul - 1.8.2-SNAPSHOT + 1.8.2 ru.micord.ervu.lkrp.ul diff --git a/frontend/pom.xml b/frontend/pom.xml index 60ed9eda..5a46ea33 100644 --- a/frontend/pom.xml +++ b/frontend/pom.xml @@ -4,7 +4,7 @@ ru.micord.ervu.lkrp ul - 1.8.2-SNAPSHOT + 1.8.2 ru.micord.ervu.lkrp.ul diff --git a/pom.xml b/pom.xml index 0825168c..7d18d257 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 ru.micord.ervu.lkrp ul - 1.8.2-SNAPSHOT + 1.8.2 pom backend diff --git a/resources/pom.xml b/resources/pom.xml index d9261857..b158868c 100644 --- a/resources/pom.xml +++ b/resources/pom.xml @@ -4,7 +4,7 @@ ru.micord.ervu.lkrp ul - 1.8.2-SNAPSHOT + 1.8.2 ru.micord.ervu.lkrp.ul