Merge branch 'feature/SUPPORT-8942_fix' into hotfix/1.9.9

This commit is contained in:
Eduard Tihomirov 2025-02-26 16:57:51 +03:00
commit 4dd664ee53
17 changed files with 186 additions and 75 deletions

View file

@ -27,7 +27,7 @@ import ru.micord.ervu.kafka.dto.FullExtract;
import ru.micord.ervu.kafka.service.ReplyingKafkaService; import ru.micord.ervu.kafka.service.ReplyingKafkaService;
import ru.micord.ervu.security.esia.model.PersonModel; import ru.micord.ervu.security.esia.model.PersonModel;
import ru.micord.ervu.security.esia.service.PersonalDataService; import ru.micord.ervu.security.esia.service.PersonalDataService;
import ru.micord.ervu.security.esia.token.EsiaTokensStore; import ru.micord.ervu.security.esia.EsiaAuthInfoStore;
import ru.micord.ervu.security.webbpm.jwt.UserIdsPair; import ru.micord.ervu.security.webbpm.jwt.UserIdsPair;
import ru.micord.ervu.security.webbpm.jwt.util.SecurityUtil; import ru.micord.ervu.security.webbpm.jwt.util.SecurityUtil;
@ -80,7 +80,7 @@ public class ExtractController {
} }
else { else {
String esiaUserId = userIdsPair.getEsiaUserId(); // esiaUserId is not null here String esiaUserId = userIdsPair.getEsiaUserId(); // esiaUserId is not null here
String esiaAccessToken = EsiaTokensStore.getAccessToken(esiaUserId); String esiaAccessToken = EsiaAuthInfoStore.getAccessToken(esiaUserId);
PersonModel personModel = personalDataService.getPersonModel(esiaAccessToken); PersonModel personModel = personalDataService.getPersonModel(esiaAccessToken);
ExtractEmptyRequestDto emptyRequest = new ExtractEmptyRequestDto( ExtractEmptyRequestDto emptyRequest = new ExtractEmptyRequestDto(

View file

@ -1,4 +1,4 @@
package ru.micord.ervu.security.esia.token; package ru.micord.ervu.security.esia;
import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodHandles;
import java.util.Map; import java.util.Map;
@ -6,14 +6,17 @@ import java.util.concurrent.ConcurrentHashMap;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import ru.micord.ervu.security.esia.model.ExpiringState;
import ru.micord.ervu.security.esia.model.ExpiringToken;
/** /**
* @author Eduard Tihomirov * @author Eduard Tihomirov
*/ */
public class EsiaTokensStore { public class EsiaAuthInfoStore {
private static final Logger LOGGER = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); private static final Logger LOGGER = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
private static final Map<String, ExpiringToken> accessTokensMap = new ConcurrentHashMap<>(); private static final Map<String, ExpiringToken> accessTokensMap = new ConcurrentHashMap<>();
private static final Map<String, ExpiringToken> refreshTokensMap = new ConcurrentHashMap<>(); private static final Map<String, ExpiringToken> refreshTokensMap = new ConcurrentHashMap<>();
private static final Map<String, ExpiringState> prnsUuidStateMap = new ConcurrentHashMap<>();
public static void addAccessToken(String prnOid, String token, long expiresIn) { public static void addAccessToken(String prnOid, String token, long expiresIn) {
if (token != null) { if (token != null) {
@ -75,4 +78,26 @@ public class EsiaTokensStore {
public static void removeRefreshToken(String prnOid) { public static void removeRefreshToken(String prnOid) {
refreshTokensMap.remove(prnOid); refreshTokensMap.remove(prnOid);
} }
public static void addState(String prnsUUID, String state, long expiresIn) {
long expiryTime = System.currentTimeMillis() + expiresIn * 1000L;
prnsUuidStateMap.put(prnsUUID, new ExpiringState(state, expiryTime));
}
public static String getState(String prnsUUID) {
return prnsUuidStateMap.get(prnsUUID).getState();
}
public static void removeState(String prnsUUID) {
prnsUuidStateMap.remove(prnsUUID);
}
public static void removeExpiredState() {
for (String key : prnsUuidStateMap.keySet()) {
ExpiringState state = prnsUuidStateMap.get(key);
if (state != null && state.isExpired()) {
prnsUuidStateMap.remove(key);
}
}
}
} }

View file

@ -53,6 +53,13 @@ public class EsiaConfig {
@Value("${esia.issuer.url}") @Value("${esia.issuer.url}")
private String esiaIssuerUrl; private String esiaIssuerUrl;
@Value("${esia.marker.ver}")
private String esiaMarkerVer;
@Value("${esia.state.cookie.life.time:300}")
private long esiaStateCookieLifeTime;
public String getEsiaScopes() { public String getEsiaScopes() {
String[] scopeItems = esiaScopes.split(","); String[] scopeItems = esiaScopes.split(",");
return String.join(" ", Arrays.stream(scopeItems).map(String::trim).toArray(String[]::new)); return String.join(" ", Arrays.stream(scopeItems).map(String::trim).toArray(String[]::new));
@ -107,4 +114,13 @@ public class EsiaConfig {
public String getEsiaIssuerUrl() { public String getEsiaIssuerUrl() {
return esiaIssuerUrl; return esiaIssuerUrl;
} }
public String getEsiaMarkerVer() {
return esiaMarkerVer;
}
public long getEsiaStateCookieLifeTime() {
return esiaStateCookieLifeTime;
}
} }

View file

@ -5,10 +5,8 @@ import java.text.SimpleDateFormat;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping; 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.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import ru.micord.ervu.security.esia.model.PersonDataModel; import ru.micord.ervu.security.esia.model.PersonDataModel;
@ -34,14 +32,15 @@ public class EsiaController {
private JwtTokenService jwtTokenService; private JwtTokenService jwtTokenService;
@RequestMapping(value = "/esia/url") @RequestMapping(value = "/esia/url")
public String getEsiaUrl() { public String getEsiaUrl(HttpServletResponse response) {
return esiaAuthService.generateAuthCodeUrl(); return esiaAuthService.generateAuthCodeUrl(response);
} }
@GetMapping(value = "/esia/auth") @GetMapping(value = "/esia/auth")
public void esiaAuth(@RequestParam(value = "code", required = false) String code, public void esiaAuth(@RequestParam String code,
@RequestParam String state,
HttpServletResponse response, HttpServletRequest request) { HttpServletResponse response, HttpServletRequest request) {
esiaAuthService.authEsiaTokensByCode(code, response, request); esiaAuthService.authEsiaTokensByCode(code, state, response, request);
} }
@RequestMapping(value = "/esia/refresh") @RequestMapping(value = "/esia/refresh")

View file

@ -0,0 +1,34 @@
package ru.micord.ervu.security.esia.model;
/**
* @author Eduard Tihomirov
*/
public class ExpiringState {
private String state;
private long expiryTime;
public ExpiringState(String state, long expiryTime) {
this.state = state;
this.expiryTime = expiryTime;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
public long getExpiryTime() {
return expiryTime;
}
public void setExpiryTime(long expiryTime) {
this.expiryTime = expiryTime;
}
public boolean isExpired() {
return System.currentTimeMillis() > expiryTime;
}
}

View file

@ -1,4 +1,4 @@
package ru.micord.ervu.security.esia.token; package ru.micord.ervu.security.esia.model;
/** /**
* @author Eduard Tihomirov * @author Eduard Tihomirov
@ -28,7 +28,7 @@ public class ExpiringToken {
this.expiryTime = expiryTime; this.expiryTime = expiryTime;
} }
boolean isExpired() { public boolean isExpired() {
return System.currentTimeMillis() > expiryTime; return System.currentTimeMillis() > expiryTime;
} }
} }

View file

@ -0,0 +1,22 @@
package ru.micord.ervu.security.esia.service;
import net.javacrumbs.shedlock.core.SchedulerLock;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import ru.micord.ervu.security.esia.EsiaAuthInfoStore;
/**
* @author Eduard Tihomirov
*/
@Service
public class EsiaAuthInfoClearShedulerService {
@Scheduled(cron = "${esia.auth.info.clear.cron:0 0 */1 * * *}")
@SchedulerLock(name = "clearAuthInfo")
@Transactional
public void run() {
EsiaAuthInfoStore.removeExpiredRefreshToken();
EsiaAuthInfoStore.removeExpiredAccessToken();
EsiaAuthInfoStore.removeExpiredState();
}
}

View file

@ -9,12 +9,15 @@ import java.net.http.HttpRequest;
import java.net.http.HttpResponse; import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.time.Duration; import java.time.Duration;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.UUID; import java.util.UUID;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
@ -25,9 +28,11 @@ import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.support.MessageSourceAccessor; import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.http.ResponseCookie;
import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContext;
import ru.micord.ervu.audit.constants.AuditConstants; import ru.micord.ervu.audit.constants.AuditConstants;
import ru.micord.ervu.audit.service.AuditService; import ru.micord.ervu.audit.service.AuditService;
import org.springframework.web.util.WebUtils;
import ru.micord.ervu.kafka.model.Document; import ru.micord.ervu.kafka.model.Document;
import ru.micord.ervu.kafka.model.Person; import ru.micord.ervu.kafka.model.Person;
import ru.micord.ervu.kafka.model.Response; import ru.micord.ervu.kafka.model.Response;
@ -38,7 +43,7 @@ import ru.micord.ervu.security.esia.model.EsiaHeader;
import ru.micord.ervu.security.esia.model.EsiaTokenResponse; import ru.micord.ervu.security.esia.model.EsiaTokenResponse;
import ru.micord.ervu.security.esia.model.FormUrlencoded; import ru.micord.ervu.security.esia.model.FormUrlencoded;
import ru.micord.ervu.security.esia.model.PersonModel; import ru.micord.ervu.security.esia.model.PersonModel;
import ru.micord.ervu.security.esia.token.EsiaTokensStore; import ru.micord.ervu.security.esia.EsiaAuthInfoStore;
import ru.micord.ervu.security.esia.config.EsiaConfig; import ru.micord.ervu.security.esia.config.EsiaConfig;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
@ -50,7 +55,6 @@ 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.service.JwtTokenService;
import ru.micord.ervu.security.webbpm.jwt.model.Token; import ru.micord.ervu.security.webbpm.jwt.model.Token;
import static ru.micord.ervu.security.webbpm.jwt.util.SecurityUtil.getCurrentUserEsiaId;
import ru.cg.webbpm.modules.core.runtime.api.MessageBundleUtils; import ru.cg.webbpm.modules.core.runtime.api.MessageBundleUtils;
/** /**
@ -61,6 +65,7 @@ public class EsiaAuthService {
private static final Logger LOGGER = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); private static final Logger LOGGER = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
private static final MessageSourceAccessor MESSAGE_SOURCE = MessageBundleUtils.createAccessor( private static final MessageSourceAccessor MESSAGE_SOURCE = MessageBundleUtils.createAccessor(
"messages/common_errors_messages"); "messages/common_errors_messages");
private static final String PRNS_UUID = "prns_uuid";
@Autowired @Autowired
private ObjectMapper objectMapper; private ObjectMapper objectMapper;
@ -89,13 +94,14 @@ public class EsiaAuthService {
@Value("${ervu.kafka.request.topic}") @Value("${ervu.kafka.request.topic}")
private String requestTopic; private String requestTopic;
public String generateAuthCodeUrl() { public String generateAuthCodeUrl(HttpServletResponse response) {
try { try {
String clientId = esiaConfig.getClientId(); String clientId = esiaConfig.getClientId();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy.MM.dd HH:mm:ss xx"); DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy.MM.dd HH:mm:ss xx");
ZonedDateTime dt = ZonedDateTime.now(); ZonedDateTime dt = ZonedDateTime.now();
String timestamp = dt.format(formatter); String timestamp = dt.format(formatter);
String state = UUID.randomUUID().toString(); String state = UUID.randomUUID().toString();
String prnsUUID = UUID.randomUUID().toString();
String redirectUrl = esiaConfig.getRedirectUrl(); String redirectUrl = esiaConfig.getRedirectUrl();
String redirectUrlEncoded = redirectUrl.replaceAll(":", "%3A") String redirectUrlEncoded = redirectUrl.replaceAll(":", "%3A")
.replaceAll("/", "%2F"); .replaceAll("/", "%2F");
@ -109,6 +115,11 @@ public class EsiaAuthService {
parameters.put("redirect_uri", esiaConfig.getRedirectUrl()); parameters.put("redirect_uri", esiaConfig.getRedirectUrl());
String clientSecret = signMap(parameters); String clientSecret = signMap(parameters);
EsiaAuthInfoStore.addState(prnsUUID, state, esiaConfig.getEsiaStateCookieLifeTime());
ResponseCookie prnsCookie = securityHelper.createCookie(PRNS_UUID, prnsUUID, "/")
.maxAge(esiaConfig.getEsiaStateCookieLifeTime())
.build();
securityHelper.addResponseCookie(response, prnsCookie);
String responseType = "code"; String responseType = "code";
@ -157,17 +168,21 @@ public class EsiaAuthService {
return uriBuilder.toString(); return uriBuilder.toString();
} }
public void authEsiaTokensByCode(String esiaAuthCode, HttpServletResponse response, HttpServletRequest request) { public void authEsiaTokensByCode(String esiaAuthCode, String state, HttpServletResponse response, HttpServletRequest request) {
String esiaAccessTokenStr = null; String esiaAccessTokenStr = null;
String prnOid = null; String prnOid = null;
Long expiresIn = null; Long expiresIn = null;
long signSecret = 0, requestAccessToken = 0, verifySecret = 0; long signSecret = 0, requestAccessToken = 0, verifySecret = 0;
String verifyStateResult = verifyStateFromCookie(request, state, response);
if (verifyStateResult != null) {
throw new EsiaException(verifyStateResult);
}
try { try {
String clientId = esiaConfig.getClientId(); String clientId = esiaConfig.getClientId();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy.MM.dd HH:mm:ss xx"); DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy.MM.dd HH:mm:ss xx");
ZonedDateTime dt = ZonedDateTime.now(); ZonedDateTime dt = ZonedDateTime.now();
String timestamp = dt.format(formatter); String timestamp = dt.format(formatter);
String state = UUID.randomUUID().toString(); String newState = UUID.randomUUID().toString();
String redirectUrl = esiaConfig.getRedirectUrl(); String redirectUrl = esiaConfig.getRedirectUrl();
String scope = esiaConfig.getEsiaScopes(); String scope = esiaConfig.getEsiaScopes();
@ -175,7 +190,7 @@ public class EsiaAuthService {
parameters.put("client_id", clientId); parameters.put("client_id", clientId);
parameters.put("scope", scope); parameters.put("scope", scope);
parameters.put("timestamp", timestamp); parameters.put("timestamp", timestamp);
parameters.put("state", state); parameters.put("state", newState);
parameters.put("redirect_uri", redirectUrl); parameters.put("redirect_uri", redirectUrl);
parameters.put("code", esiaAuthCode); parameters.put("code", esiaAuthCode);
@ -188,7 +203,7 @@ public class EsiaAuthService {
.setParameter("code", esiaAuthCode) .setParameter("code", esiaAuthCode)
.setParameter("grant_type", "authorization_code") .setParameter("grant_type", "authorization_code")
.setParameter("client_secret", clientSecret) .setParameter("client_secret", clientSecret)
.setParameter("state", state) .setParameter("state", newState)
.setParameter("redirect_uri", redirectUrl) .setParameter("redirect_uri", redirectUrl)
.setParameter("scope", scope) .setParameter("scope", scope)
.setParameter("timestamp", timestamp) .setParameter("timestamp", timestamp)
@ -216,6 +231,9 @@ public class EsiaAuthService {
tokenResponse != null ? tokenResponse.getError_description() : "response is empty"; tokenResponse != null ? tokenResponse.getError_description() : "response is empty";
throw new IllegalStateException("Esia response error. " + errMsg); throw new IllegalStateException("Esia response error. " + errMsg);
} }
if (!tokenResponse.getState().equals(newState)) {
throw new EsiaException("Token invalid. State from request not equals with state from response.");
}
esiaAccessTokenStr = tokenResponse.getAccess_token(); esiaAccessTokenStr = tokenResponse.getAccess_token();
startTime = System.currentTimeMillis(); startTime = System.currentTimeMillis();
String verifyResult = verifyToken(esiaAccessTokenStr); String verifyResult = verifyToken(esiaAccessTokenStr);
@ -227,8 +245,8 @@ public class EsiaAuthService {
EsiaAccessToken esiaAccessToken = personalDataService.readToken(esiaAccessTokenStr); EsiaAccessToken esiaAccessToken = personalDataService.readToken(esiaAccessTokenStr);
prnOid = esiaAccessToken.getSbj_id(); prnOid = esiaAccessToken.getSbj_id();
expiresIn = tokenResponse.getExpires_in(); expiresIn = tokenResponse.getExpires_in();
EsiaTokensStore.addAccessToken(prnOid, esiaAccessTokenStr, expiresIn); EsiaAuthInfoStore.addAccessToken(prnOid, esiaAccessTokenStr, expiresIn);
EsiaTokensStore.addRefreshToken(prnOid, esiaRefreshTokenStr, expiresIn); EsiaAuthInfoStore.addRefreshToken(prnOid, esiaRefreshTokenStr, expiresIn);
} }
catch (Exception e) { catch (Exception e) {
throw new EsiaException(e); throw new EsiaException(e);
@ -318,8 +336,8 @@ public class EsiaAuthService {
EsiaAccessToken esiaAccessToken = personalDataService.readToken(esiaAccessTokenStr); EsiaAccessToken esiaAccessToken = personalDataService.readToken(esiaAccessTokenStr);
String prnOid = esiaAccessToken.getSbj_id(); String prnOid = esiaAccessToken.getSbj_id();
Long expiresIn = tokenResponse.getExpires_in(); Long expiresIn = tokenResponse.getExpires_in();
EsiaTokensStore.addAccessToken(prnOid, esiaAccessTokenStr, expiresIn); EsiaAuthInfoStore.addAccessToken(prnOid, esiaAccessTokenStr, expiresIn);
EsiaTokensStore.addRefreshToken(prnOid, esiaNewRefreshTokenStr, expiresIn); EsiaAuthInfoStore.addRefreshToken(prnOid, esiaNewRefreshTokenStr, expiresIn);
PersonModel personModel = personalDataService.getPersonModel(esiaAccessTokenStr); PersonModel personModel = personalDataService.getPersonModel(esiaAccessTokenStr);
Response ervuIdResponse = getErvuIdResponse(personModel); Response ervuIdResponse = getErvuIdResponse(personModel);
createTokenAndAddCookie(response, esiaAccessToken.getSbj_id(), ervuIdResponse.getErvuId(), expiresIn); createTokenAndAddCookie(response, esiaAccessToken.getSbj_id(), ervuIdResponse.getErvuId(), expiresIn);
@ -366,11 +384,11 @@ public class EsiaAuthService {
PersonModel personModel = null; PersonModel personModel = null;
try { try {
String userId = jwtTokenService.getUserAccountId(request); String userId = jwtTokenService.getUserAccountId(request);
String accessToken = EsiaTokensStore.getAccessToken(userId); String accessToken = EsiaAuthInfoStore.getAccessToken(userId);
personModel = personalDataService.getPersonModel(accessToken); personModel = personalDataService.getPersonModel(accessToken);
securityHelper.clearAccessCookies(response); securityHelper.clearAccessCookies(response);
EsiaTokensStore.removeAccessToken(userId); EsiaAuthInfoStore.removeAccessToken(userId);
EsiaTokensStore.removeRefreshToken(userId); EsiaAuthInfoStore.removeRefreshToken(userId);
String logoutUrl = esiaConfig.getEsiaBaseUri() + esiaConfig.getEsiaLogoutUrl(); String logoutUrl = esiaConfig.getEsiaBaseUri() + esiaConfig.getEsiaLogoutUrl();
String redirectUrl = esiaConfig.getLogoutRedirectUrl(); String redirectUrl = esiaConfig.getLogoutRedirectUrl();
URL url = new URL(logoutUrl); URL url = new URL(logoutUrl);
@ -416,13 +434,6 @@ public class EsiaAuthService {
return person; return person;
} }
private String getMessageId(Exception exception) {
return Integer.toUnsignedString(Objects
.hashCode(getCurrentUserEsiaId()), 36)
+ "-"
+ Integer.toUnsignedString(exception.hashCode(), 36);
}
private void createTokenAndAddCookie(HttpServletResponse response, String userId, String ervuId, private void createTokenAndAddCookie(HttpServletResponse response, String userId, String ervuId,
Long expiresIn) { Long expiresIn) {
Token token = jwtTokenService.createAccessToken(userId, expiresIn, ervuId); Token token = jwtTokenService.createAccessToken(userId, expiresIn, ervuId);
@ -442,6 +453,9 @@ public class EsiaAuthService {
if (!esiaHeader.getSbt().equals("access")) { if (!esiaHeader.getSbt().equals("access")) {
return "Token invalid. Token sbt: " + esiaHeader.getSbt() + " invalid"; return "Token invalid. Token sbt: " + esiaHeader.getSbt() + " invalid";
} }
if (!esiaHeader.getVer().equals(esiaConfig.getEsiaMarkerVer())) {
return "Token invalid. Token ver: " + esiaHeader.getVer() + " invalid";
}
if (!esiaHeader.getTyp().equals("JWT")) { if (!esiaHeader.getTyp().equals("JWT")) {
return "Token invalid. Token type: " + esiaHeader.getTyp() + " invalid"; return "Token invalid. Token type: " + esiaHeader.getTyp() + " invalid";
} }
@ -451,17 +465,16 @@ public class EsiaAuthService {
if (!esiaAccessToken.getIss().equals(esiaConfig.getEsiaIssuerUrl())) { if (!esiaAccessToken.getIss().equals(esiaConfig.getEsiaIssuerUrl())) {
return "Token invalid. Token issuer:" + esiaAccessToken.getIss() + " invalid"; return "Token invalid. Token issuer:" + esiaAccessToken.getIss() + " invalid";
} }
//TODO SUPPORT-8750 LocalDateTime iatTime = LocalDateTime.ofInstant(Instant.ofEpochSecond(esiaAccessToken.getIat()),
// LocalDateTime iatTime = LocalDateTime.ofInstant(Instant.ofEpochSecond(esiaAccessToken.getIat()), ZoneId.systemDefault()
// ZoneId.systemDefault() );
// ); LocalDateTime expTime = LocalDateTime.ofInstant(Instant.ofEpochSecond(esiaAccessToken.getExp()),
// LocalDateTime expTime = LocalDateTime.ofInstant(Instant.ofEpochSecond(esiaAccessToken.getExp()), ZoneId.systemDefault()
// ZoneId.systemDefault() );
// ); LocalDateTime currentTime = LocalDateTime.now();
// LocalDateTime currentTime = LocalDateTime.now(); if (!currentTime.isAfter(iatTime) || !expTime.isAfter(iatTime)) {
// if (!currentTime.isAfter(iatTime) || !expTime.isAfter(iatTime)) { return "Token invalid. Token expired";
// return "Token invalid. Token expired"; }
// }
HttpResponse<String> response = signVerify(accessToken); HttpResponse<String> response = signVerify(accessToken);
if (response.statusCode() != 200) { if (response.statusCode() != 200) {
if (response.statusCode() == 401) { if (response.statusCode() == 401) {
@ -489,4 +502,19 @@ public class EsiaAuthService {
throw new EsiaException(e); throw new EsiaException(e);
} }
} }
private String verifyStateFromCookie(HttpServletRequest request, String state, HttpServletResponse response) {
Cookie cookie = WebUtils.getCookie(request, PRNS_UUID);
if (cookie == null) {
return "State invalid. Cookie not found";
}
String prnsUUID = cookie.getValue();
String oldState = EsiaAuthInfoStore.getState(prnsUUID);
if (oldState == null || !oldState.equals(state)) {
return "State invalid. State from ESIA not equals with state before";
}
EsiaAuthInfoStore.removeState(prnsUUID);
securityHelper.clearCookie(response, PRNS_UUID, "/");
return null;
}
} }

View file

@ -1,20 +0,0 @@
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() {
EsiaTokensStore.removeExpiredRefreshToken();
EsiaTokensStore.removeExpiredAccessToken();
}
}

View file

@ -48,7 +48,13 @@ public final class SecurityHelper {
addResponseCookie(response, emptyAuthMarker); addResponseCookie(response, emptyAuthMarker);
} }
private void addResponseCookie(HttpServletResponse response, ResponseCookie cookie) { public void clearCookie(HttpServletResponse response, String name, String path) {
ResponseCookie emptyCookie = createCookie(name, null, path)
.maxAge(0).build();
addResponseCookie(response, emptyCookie);
}
public void addResponseCookie(HttpServletResponse response, ResponseCookie cookie) {
response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString()); response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString());
} }

View file

@ -14,7 +14,7 @@ import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import ru.micord.ervu.security.esia.token.EsiaTokensStore; import ru.micord.ervu.security.esia.EsiaAuthInfoStore;
import ru.micord.ervu.security.webbpm.jwt.UserIdsPair; import ru.micord.ervu.security.webbpm.jwt.UserIdsPair;
import ru.micord.ervu.security.webbpm.jwt.model.Token; import ru.micord.ervu.security.webbpm.jwt.model.Token;
@ -67,7 +67,7 @@ public class JwtTokenService {
return false; return false;
} }
String esiaUserId = new UserIdsPair(token.getUserAccountId()).getEsiaUserId(); String esiaUserId = new UserIdsPair(token.getUserAccountId()).getEsiaUserId();
return EsiaTokensStore.validateAccessToken(esiaUserId); return EsiaAuthInfoStore.validateAccessToken(esiaUserId);
} }
public Token getToken(String token) { public Token getToken(String token) {
@ -80,11 +80,11 @@ public class JwtTokenService {
} }
public String getAccessToken(HttpServletRequest request) { public String getAccessToken(HttpServletRequest request) {
return EsiaTokensStore.getAccessToken(getUserAccountId(request)); return EsiaAuthInfoStore.getAccessToken(getUserAccountId(request));
} }
public String getRefreshToken(HttpServletRequest request) { public String getRefreshToken(HttpServletRequest request) {
return EsiaTokensStore.getRefreshToken(getUserAccountId(request)); return EsiaAuthInfoStore.getRefreshToken(getUserAccountId(request));
} }
public String getUserAccountId(HttpServletRequest request) { public String getUserAccountId(HttpServletRequest request) {

View file

@ -35,5 +35,5 @@ 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_EXTRACT_HEADER_CLASS=request@urn://rostelekom.ru/ERVU-extractFromRegistryTR/1.0.3
ERVU_KAFKA_DOC_LOGIN_MODULE=org.apache.kafka.common.security.plain.PlainLoginModule ERVU_KAFKA_DOC_LOGIN_MODULE=org.apache.kafka.common.security.plain.PlainLoginModule
ESIA_TOKEN_CLEAR_CRON=0 0 */1 * * * ESIA_AUTH_INFO_CLEAR_CRON=0 0 */1 * * *
COOKIE_PATH=/fl COOKIE_PATH=/fl

View file

@ -44,5 +44,5 @@ AUDIT_KAFKA_PASSWORD=
AUDIT_KAFKA_SASL_MECHANISM= AUDIT_KAFKA_SASL_MECHANISM=
AUDIT_KAFKA_ENABLED=false AUDIT_KAFKA_ENABLED=false
ESIA_TOKEN_CLEAR_CRON=0 0 */1 * * * ESIA_AUTH_INFO_CLEAR_CRON=0 0 */1 * * *
COOKIE_PATH=/fl COOKIE_PATH=/fl

View file

@ -79,7 +79,7 @@
<property name="ervu.kafka.registry.extract.request.topic" value="ervu.extract.info.request"/> <property name="ervu.kafka.registry.extract.request.topic" value="ervu.extract.info.request"/>
<property name="ervu.kafka.registry.extract.reply.topic" value="ervu.extract.info.response"/> <property name="ervu.kafka.registry.extract.reply.topic" value="ervu.extract.info.response"/>
<property name="ervu.kafka.extract.header.class" value="request@urn://rostelekom.ru/ERVU-extractFromRegistryTR/1.0.3"/> <property name="ervu.kafka.extract.header.class" value="request@urn://rostelekom.ru/ERVU-extractFromRegistryTR/1.0.3"/>
<property name="esia.token.clear.cron" value="0 0 */1 * * *"/> <property name="esia.auth.info.clear.cron" value="0 0 */1 * * *"/>
<property name="audit.kafka.bootstrap.servers" value="localhost:9092"/> <property name="audit.kafka.bootstrap.servers" value="localhost:9092"/>
<property name="audit.kafka.authorization.topic" value="ervu.lkrp.auth.events"/> <property name="audit.kafka.authorization.topic" value="ervu.lkrp.auth.events"/>
<property name="audit.kafka.file.download.topic" value="ervu.lkrp.import.file"/> <property name="audit.kafka.file.download.topic" value="ervu.lkrp.import.file"/>

View file

@ -25,6 +25,7 @@ export abstract class AuthGuard implements CanActivate {
let url = new URL(window.location.href); let url = new URL(window.location.href);
let params = new URLSearchParams(url.search); let params = new URLSearchParams(url.search);
let code = params.get('code'); let code = params.get('code');
let state = params.get('state');
let error = params.get('error'); let error = params.get('error');
let errorDescription = params.get('error_description'); let errorDescription = params.get('error_description');
if (isAccess) { if (isAccess) {
@ -41,8 +42,8 @@ export abstract class AuthGuard implements CanActivate {
this.messageService.error(errorMessage); this.messageService.error(errorMessage);
console.error(consoleError); console.error(consoleError);
} }
if (code) { if (code && state) {
const params = new HttpParams().set('code', code); const params = new HttpParams().set('code', code).set('state', state);
this.httpClient.get("esia/auth", this.httpClient.get("esia/auth",
{ {
params: params, params: params,