Merge branch 'feature/SUPPORT-9164' into develop

This commit is contained in:
Eduard Tihomirov 2025-05-13 13:20:41 +03:00
commit 8440561dfe
6 changed files with 46 additions and 32 deletions

View file

@ -2,4 +2,7 @@ package ru.micord.ervu.security;
public class SecurityConstants {
public static final String ESIA_LOGOUT = "/esia/logout";
public static final String AUTH_TOKEN = "auth_token";
public static final String AUTH_MARKER = "webbpm.ervu-lkrp-fl";
public static final String PRNS_UUID = "prns_uuid_fl";
}

View file

@ -9,6 +9,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.support.MessageSourceAccessor;
import ru.micord.ervu.security.esia.exception.EsiaException;
import ru.micord.ervu.security.esia.model.ExpiringState;
import ru.micord.ervu.security.esia.model.ExpiringToken;
@ -105,19 +106,24 @@ public class EsiaAuthInfoStore {
});
}
public static boolean containsState(String prnsUUID, String state) {
public static void validateState(String prnsUUID, String state) {
List<ExpiringState> states = PRNS_UUID_STATE_MAP.get(prnsUUID);
if (states == null) {
return false;
throw new EsiaException("State invalid. No state found");
}
long currentTime = System.currentTimeMillis();
states.removeIf(expiringState -> expiringState.getExpiryTime() < currentTime);
StringBuilder statesStringBuilder = new StringBuilder();
for (ExpiringState expiringState : states) {
if (expiringState.getState().equals(state)) {
return true;
if (expiringState.getExpiryTime() < currentTime) {
throw new EsiaException("State invalid. State : " + state + " expired at : " + expiringState.getExpiryTime());
}
return;
}
statesStringBuilder.append(expiringState.getState(), 0, 8).append(", ");
}
return false;
throw new EsiaException("State invalid. Backend states :" + statesStringBuilder + " cookie state :" + state);
}
public static void removeState(String prnsUUID) {

View file

@ -34,8 +34,6 @@ import org.springframework.security.core.context.SecurityContext;
import ru.micord.ervu.audit.constants.AuditConstants;
import ru.micord.ervu.audit.service.AuditService;
import org.springframework.web.util.WebUtils;
import ru.micord.ervu.audit.constants.AuditConstants;
import ru.micord.ervu.audit.service.AuditService;
import ru.micord.ervu.kafka.model.Document;
import ru.micord.ervu.kafka.model.Person;
import ru.micord.ervu.kafka.model.Response;
@ -60,8 +58,7 @@ import ru.micord.ervu.security.webbpm.jwt.model.Token;
import ru.cg.webbpm.modules.core.runtime.api.MessageBundleUtils;
import static ru.micord.ervu.security.webbpm.jwt.util.SecurityUtil.getCurrentUserEsiaId;
import ru.cg.webbpm.modules.core.runtime.api.MessageBundleUtils;
import static ru.micord.ervu.security.SecurityConstants.PRNS_UUID;
/**
* @author Eduard Tihomirov
@ -71,7 +68,6 @@ public class EsiaAuthService {
private static final Logger LOGGER = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
private static final MessageSourceAccessor MESSAGE_SOURCE = MessageBundleUtils.createAccessor(
"messages/common_errors_messages");
private static final String PRNS_UUID = "prns_uuid";
@Autowired
private ObjectMapper objectMapper;
@ -126,7 +122,7 @@ public class EsiaAuthService {
String clientSecret = signMap(parameters);
EsiaAuthInfoStore.addState(prnsUUID, state, esiaConfig.getEsiaStateCookieLifeTime(), esiaConfig.getEsiaLoginAttemptsCount());
ResponseCookie prnsCookie = securityHelper.createCookie(PRNS_UUID, prnsUUID, "/")
ResponseCookie prnsCookie = securityHelper.createAccessCookie(PRNS_UUID, prnsUUID)
.maxAge(esiaConfig.getEsiaStateCookieLifeTime())
.build();
securityHelper.addResponseCookie(response, prnsCookie);
@ -183,10 +179,7 @@ public class EsiaAuthService {
String prnOid = null;
Long expiresIn = null;
long signSecret = 0, requestAccessToken = 0, verifySecret = 0;
String verifyStateResult = verifyStateFromCookie(request, state, response);
if (verifyStateResult != null) {
throw new EsiaException(verifyStateResult);
}
verifyStateFromCookie(request, state, response);
try {
String clientId = esiaConfig.getClientId();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy.MM.dd HH:mm:ss xx");
@ -521,17 +514,18 @@ public class EsiaAuthService {
}
}
private String verifyStateFromCookie(HttpServletRequest request, String state, HttpServletResponse response) {
private void verifyStateFromCookie(HttpServletRequest request, String state, HttpServletResponse response) {
Cookie cookie = WebUtils.getCookie(request, PRNS_UUID);
if (cookie == null) {
return "State invalid. Cookie not found";
throw new EsiaException("State invalid. Cookie not found");
}
String prnsUUID = cookie.getValue();
if (!EsiaAuthInfoStore.containsState(prnsUUID, state)) {
return "State invalid. State from ESIA not equals with state before";
try {
EsiaAuthInfoStore.validateState(prnsUUID, state);
}
finally {
EsiaAuthInfoStore.removeState(prnsUUID);
securityHelper.clearAccessCookie(response, PRNS_UUID);
}
EsiaAuthInfoStore.removeState(prnsUUID);
securityHelper.clearCookie(response, PRNS_UUID, "/");
return null;
}
}

View file

@ -14,8 +14,9 @@ import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import static org.springframework.web.context.request.RequestAttributes.REFERENCE_REQUEST;
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.SecurityConstants.AUTH_MARKER;
import static ru.micord.ervu.security.SecurityConstants.AUTH_TOKEN;
import static ru.micord.ervu.security.SecurityConstants.PRNS_UUID;
public final class SecurityHelper {
@Value("${cookie.path:#{null}}")
@ -36,9 +37,7 @@ public final class SecurityHelper {
}
public void clearAccessCookies(HttpServletResponse response) {
ResponseCookie emptyAuthToken = createCookie(AUTH_TOKEN, null, accessCookiePath)
.maxAge(0).build();
addResponseCookie(response, emptyAuthToken);
clearCookie(response, AUTH_TOKEN, accessCookiePath);
ResponseCookie emptyAuthMarker = createCookie(AUTH_MARKER, null, "/")
.maxAge(0)
@ -46,6 +45,7 @@ public final class SecurityHelper {
.httpOnly(false)
.build();
addResponseCookie(response, emptyAuthMarker);
clearCookie(response, PRNS_UUID, accessCookiePath);
}
public void clearCookie(HttpServletResponse response, String name, String path) {
@ -92,4 +92,12 @@ public final class SecurityHelper {
.secure(accessCookieSecure)
.sameSite(accessCookieSameSite);
}
public ResponseCookie.ResponseCookieBuilder createAccessCookie(String name, String value) {
return createCookie(name, value, accessCookiePath);
}
public void clearAccessCookie(HttpServletResponse response, String name) {
clearCookie(response, name, accessCookiePath);
}
}

View file

@ -10,10 +10,9 @@ import org.springframework.web.util.WebUtils;
import ru.micord.ervu.security.webbpm.jwt.JwtAuthentication;
import ru.micord.ervu.security.webbpm.jwt.UserIdsPair;
public final class SecurityUtil {
public static final String AUTH_TOKEN = "auth_token";
import static ru.micord.ervu.security.SecurityConstants.AUTH_TOKEN;
public static final String AUTH_MARKER = "webbpm.ervu-lkrp-fl";
public final class SecurityUtil {
private SecurityUtil() {
//empty

View file

@ -2,7 +2,7 @@ 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 {MessagesService} from "@webbpm/base-package";
import {MessagesService, ProgressIndicationService} from "@webbpm/base-package";
import {AuthenticationService} from "../authentication.service";
import {EsiaErrorDetail} from "../EsiaErrorDetail";
@ -13,6 +13,7 @@ export abstract class AuthGuard implements CanActivate {
protected router: Router,
private httpClient: HttpClient,
private authenticationService: AuthenticationService,
private progressIndicationService: ProgressIndicationService,
private messageService: MessagesService
) {
}
@ -44,6 +45,7 @@ export abstract class AuthGuard implements CanActivate {
return false;
}
if (code && state) {
this.progressIndicationService.showProgressBar();
const params = new HttpParams().set('code', code).set('state', state);
this.httpClient.get("esia/auth",
{
@ -57,6 +59,7 @@ export abstract class AuthGuard implements CanActivate {
.toPromise()
.then(
() => {
this.progressIndicationService.hideProgressBar();
window.open(url.origin + url.pathname, "_self");
})
.catch(reason => {
@ -64,7 +67,8 @@ export abstract class AuthGuard implements CanActivate {
json.messages.forEach((errorMessage) => {
this.messageService.error(errorMessage, json);
})
});
})
.finally(() => this.progressIndicationService.hideProgressBar());
return false;
}
else {