From d5082c3ce28f5c452493576cfac26fb713a78c59 Mon Sep 17 00:00:00 2001 From: Eduard Tihomirov Date: Thu, 3 Oct 2024 16:28:43 +0300 Subject: [PATCH] SUPPORT-8579: Fix --- .../ru/micord/ervu/security/esia/Tokens.java | 40 ------------- .../esia/service/EsiaAuthService.java | 34 ++++------- .../security/esia/token/ExpiringToken.java | 34 +++++++++++ .../token/TokensClearShedulerService.java | 20 +++++++ .../ervu/security/esia/token/TokensStore.java | 60 +++++++++++++++++++ .../webbpm/jwt/service/JwtTokenService.java | 26 +++++--- config/micord.env | 4 +- config/standalone/dev/standalone.xml | 1 + 8 files changed, 147 insertions(+), 72 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/ru/micord/ervu/security/esia/Tokens.java b/backend/src/main/java/ru/micord/ervu/security/esia/Tokens.java deleted file mode 100644 index f639b95..0000000 --- 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 80ae459..43f11c8 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 @@ -27,7 +27,7 @@ import ru.micord.ervu.kafka.model.Document; import ru.micord.ervu.kafka.model.Person; import ru.micord.ervu.kafka.model.Response; import ru.micord.ervu.kafka.service.ReplyingKafkaService; -import ru.micord.ervu.security.esia.Tokens; +import ru.micord.ervu.security.esia.token.TokensStore; import ru.micord.ervu.security.esia.config.EsiaConfig; import ru.micord.ervu.security.esia.model.FormUrlencoded; import ru.micord.ervu.security.esia.model.EsiaAccessToken; @@ -205,10 +205,11 @@ public class EsiaAuthService { String decodedString = new String(decodedBytes); EsiaAccessToken esiaAccessToken = objectMapper.readValue(decodedString, EsiaAccessToken.class); String prnOid = esiaAccessToken.getSbj_id(); - Tokens.addAccessToken(prnOid, accessToken); - Tokens.addRefreshToken(prnOid, refreshToken); + Long expiresIn = tokenResponse.getExpires_in(); + TokensStore.addAccessToken(prnOid, accessToken, expiresIn); + TokensStore.addRefreshToken(prnOid, refreshToken, expiresIn); String ervuId = getErvuId(accessToken); - 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); @@ -234,19 +235,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(); @@ -307,10 +296,11 @@ public class EsiaAuthService { String decodedString = new String(decodedBytes); EsiaAccessToken esiaAccessToken = objectMapper.readValue(decodedString, EsiaAccessToken.class); 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); - 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); @@ -388,8 +378,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/token/ExpiringToken.java b/backend/src/main/java/ru/micord/ervu/security/esia/token/ExpiringToken.java new file mode 100644 index 0000000..f6a476e --- /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 0000000..4665295 --- /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 0000000..9804b80 --- /dev/null +++ b/backend/src/main/java/ru/micord/ervu/security/esia/token/TokensStore.java @@ -0,0 +1,60 @@ +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 732e3ca..ff7d6c7 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 @@ -18,7 +18,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; 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.JwtAuthentication; import ru.micord.ervu.security.webbpm.jwt.model.Token; @@ -89,21 +89,29 @@ public class JwtTokenService { } public String getAccessToken(HttpServletRequest request) { - String authTokenStr = null; + 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")) { - authTokenStr = cookie.getValue(); + authToken = cookie.getValue(); } } } - if (authTokenStr == null) { - return null; + if (authToken != null) { + String[] ids = getToken(authToken).getUserAccountId().split(":"); + return ids[0]; + } + else { + throw new RuntimeException("Failed to get auth data. User unauthorized."); } - Token authToken = getToken(authTokenStr); - String[] split = authToken.getUserAccountId().split(":"); - String userId = split[0]; - return Tokens.getAccessToken(userId); } } diff --git a/config/micord.env b/config/micord.env index 25e9a7b..be879af 100644 --- a/config/micord.env +++ b/config/micord.env @@ -32,4 +32,6 @@ ERVU_KAFKA_SUBPOENA_EXTRACT_REPLY_TOPIC=ervu.subpoena.info.response ERVU_KAFKA_REGISTRY_EXTRACT_REQUEST_TOPIC=ervu.extract.info.request ERVU_KAFKA_REGISTRY_EXTRACT_REPLY_TOPIC=ervu.extract.info.response ERVU_KAFKA_EXTRACT_HEADER_CLASS=Request@urn://rostelekom.ru/ERVU-extractFromRegistryTR/1.0.3 -ERVU_KAFKA_DOC_LOGIN_MODULE=org.apache.kafka.common.security.scram.ScramLoginModule \ No newline at end of file +ERVU_KAFKA_DOC_LOGIN_MODULE=org.apache.kafka.common.security.scram.ScramLoginModule + +ESIA_TOKEN_CLEAR_CRON=0 0 */1 * * * \ No newline at end of file diff --git a/config/standalone/dev/standalone.xml b/config/standalone/dev/standalone.xml index 905c647..97d4563 100644 --- a/config/standalone/dev/standalone.xml +++ b/config/standalone/dev/standalone.xml @@ -80,6 +80,7 @@ +