SUPPORT-8942: Fix
This commit is contained in:
parent
c4a70ae591
commit
766b740011
6 changed files with 78 additions and 26 deletions
|
|
@ -53,6 +53,9 @@ public class EsiaConfig {
|
||||||
@Value("${esia.issuer.url}")
|
@Value("${esia.issuer.url}")
|
||||||
private String esiaIssuerUrl;
|
private String esiaIssuerUrl;
|
||||||
|
|
||||||
|
@Value("${esia.marker.ver}")
|
||||||
|
private String esiaMarkerVer;
|
||||||
|
|
||||||
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 +110,8 @@ public class EsiaConfig {
|
||||||
public String getEsiaIssuerUrl() {
|
public String getEsiaIssuerUrl() {
|
||||||
return esiaIssuerUrl;
|
return esiaIssuerUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getEsiaMarkerVer() {
|
||||||
|
return esiaMarkerVer;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,14 +34,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(value = "code") String code,
|
||||||
HttpServletResponse response) {
|
@RequestParam(value = "state") String state,
|
||||||
esiaAuthService.authEsiaTokensByCode(code, response);
|
HttpServletResponse response, HttpServletRequest request) {
|
||||||
|
esiaAuthService.authEsiaTokensByCode(code, state, response, request);
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequestMapping(value = "/esia/refresh")
|
@RequestMapping(value = "/esia/refresh")
|
||||||
|
|
|
||||||
|
|
@ -8,13 +8,13 @@ import java.net.http.HttpClient;
|
||||||
import java.net.http.HttpRequest;
|
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.*;
|
||||||
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.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,7 +25,9 @@ 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 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;
|
||||||
|
|
@ -59,6 +61,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;
|
||||||
|
|
||||||
|
|
@ -84,13 +87,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");
|
||||||
|
|
@ -104,6 +108,9 @@ public class EsiaAuthService {
|
||||||
parameters.put("redirect_uri", esiaConfig.getRedirectUrl());
|
parameters.put("redirect_uri", esiaConfig.getRedirectUrl());
|
||||||
|
|
||||||
String clientSecret = signMap(parameters);
|
String clientSecret = signMap(parameters);
|
||||||
|
EsiaTokensStore.addState(prnsUUID, state);
|
||||||
|
ResponseCookie prnsCookie = securityHelper.createCookie(PRNS_UUID, prnsUUID, "/").build();
|
||||||
|
securityHelper.addResponseCookie(response, prnsCookie);
|
||||||
|
|
||||||
String responseType = "code";
|
String responseType = "code";
|
||||||
|
|
||||||
|
|
@ -152,17 +159,21 @@ public class EsiaAuthService {
|
||||||
return uriBuilder.toString();
|
return uriBuilder.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void authEsiaTokensByCode(String esiaAuthCode, HttpServletResponse response) {
|
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);
|
||||||
|
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();
|
||||||
|
|
||||||
|
|
@ -170,7 +181,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);
|
||||||
|
|
||||||
|
|
@ -183,7 +194,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)
|
||||||
|
|
@ -211,6 +222,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);
|
||||||
|
|
@ -416,6 +430,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";
|
||||||
}
|
}
|
||||||
|
|
@ -425,17 +442,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) {
|
||||||
|
|
@ -463,4 +479,18 @@ public class EsiaAuthService {
|
||||||
throw new EsiaException(e);
|
throw new EsiaException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String verifyStateFromCookie(HttpServletRequest request, String state) {
|
||||||
|
Cookie cookie = WebUtils.getCookie(request, PRNS_UUID);
|
||||||
|
if (cookie == null) {
|
||||||
|
return "State invalid. Cookie not found";
|
||||||
|
}
|
||||||
|
String prnsUUID = cookie.getValue();
|
||||||
|
String oldState = EsiaTokensStore.getState(prnsUUID);
|
||||||
|
if (oldState == null || !oldState.equals(state)) {
|
||||||
|
return "State invalid. State from ESIA not equals with state before";
|
||||||
|
}
|
||||||
|
EsiaTokensStore.removeState(prnsUUID);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ public class EsiaTokensStore {
|
||||||
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, String> 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 +76,16 @@ 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) {
|
||||||
|
prnsUUIDStateMap.put(prnsUUID, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getState(String prnsUUID) {
|
||||||
|
return prnsUUIDStateMap.get(prnsUUID);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String removeState(String prnsUUID) {
|
||||||
|
return prnsUUIDStateMap.remove(prnsUUID);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ public final class SecurityHelper {
|
||||||
addResponseCookie(response, emptyAuthMarker);
|
addResponseCookie(response, emptyAuthMarker);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addResponseCookie(HttpServletResponse response, ResponseCookie cookie) {
|
public void addResponseCookie(HttpServletResponse response, ResponseCookie cookie) {
|
||||||
response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString());
|
response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue