Merge branch 'feature/SUPPORT-8845_fix_logout' into hotfix/1.9.5
# Conflicts: # backend/src/main/java/ru/micord/ervu/security/esia/service/EsiaAuthService.java
This commit is contained in:
commit
5108caf305
7 changed files with 125 additions and 64 deletions
|
|
@ -31,7 +31,7 @@ import static ru.micord.ervu.security.SecurityConstants.ESIA_LOGOUT;
|
||||||
@EnableWebSecurity
|
@EnableWebSecurity
|
||||||
public class SecurityConfig {
|
public class SecurityConfig {
|
||||||
private static final String[] PERMIT_ALL = new String[] {
|
private static final String[] PERMIT_ALL = new String[] {
|
||||||
"/version", "/esia/url", "/esia/auth", "esia/refresh"
|
"/version", "/esia/url", "/esia/auth", "esia/refresh", "/esia/logout"
|
||||||
};
|
};
|
||||||
@Autowired
|
@Autowired
|
||||||
private JwtAuthenticationFilter jwtAuthenticationFilter;
|
private JwtAuthenticationFilter jwtAuthenticationFilter;
|
||||||
|
|
|
||||||
|
|
@ -38,9 +38,11 @@ public class EsiaController {
|
||||||
return esiaAuthService.generateAuthCodeUrl();
|
return esiaAuthService.generateAuthCodeUrl();
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping(value = "/esia/auth", params = "code")
|
@GetMapping(value = "/esia/auth")
|
||||||
public ResponseEntity<?> esiaAuth(@RequestParam("code") String code, HttpServletRequest request, HttpServletResponse response) {
|
public ResponseEntity<?> esiaAuth(@RequestParam(value = "code", required = false) String code,
|
||||||
return esiaAuthService.getEsiaTokensByCode(code, request, response);
|
@RequestParam(value = "error", required = false) String error, HttpServletRequest request,
|
||||||
|
HttpServletResponse response) {
|
||||||
|
return esiaAuthService.getEsiaTokensByCode(code, error, request, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequestMapping(value = "/esia/refresh")
|
@RequestMapping(value = "/esia/refresh")
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
package ru.micord.ervu.security.esia.exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Adel Kalimullin
|
||||||
|
*/
|
||||||
|
public class EsiaException extends RuntimeException{
|
||||||
|
|
||||||
|
public EsiaException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public EsiaException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public EsiaException(Throwable cause) {
|
||||||
|
super(cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package ru.micord.ervu.security.esia.service;
|
package ru.micord.ervu.security.esia.service;
|
||||||
|
|
||||||
|
import java.lang.invoke.MethodHandles;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.net.URLEncoder;
|
import java.net.URLEncoder;
|
||||||
|
|
@ -12,19 +13,24 @@ 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.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
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.http.HttpStatus;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.security.core.context.SecurityContext;
|
import org.springframework.security.core.context.SecurityContext;
|
||||||
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;
|
||||||
import ru.micord.ervu.kafka.service.ReplyingKafkaService;
|
import ru.micord.ervu.kafka.service.ReplyingKafkaService;
|
||||||
|
import ru.micord.ervu.security.esia.exception.EsiaException;
|
||||||
import ru.micord.ervu.security.esia.token.EsiaTokensStore;
|
import ru.micord.ervu.security.esia.token.EsiaTokensStore;
|
||||||
import ru.micord.ervu.security.esia.config.EsiaConfig;
|
import ru.micord.ervu.security.esia.config.EsiaConfig;
|
||||||
import ru.micord.ervu.security.esia.model.FormUrlencoded;
|
import ru.micord.ervu.security.esia.model.FormUrlencoded;
|
||||||
|
|
@ -41,11 +47,14 @@ 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.getCurrentUsername;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Eduard Tihomirov
|
* @author Eduard Tihomirov
|
||||||
*/
|
*/
|
||||||
@Service
|
@Service
|
||||||
public class EsiaAuthService {
|
public class EsiaAuthService {
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private ObjectMapper objectMapper;
|
private ObjectMapper objectMapper;
|
||||||
|
|
@ -110,7 +119,7 @@ public class EsiaAuthService {
|
||||||
return buildUrl(url, params);
|
return buildUrl(url, params);
|
||||||
}
|
}
|
||||||
catch (Exception e) {
|
catch (Exception e) {
|
||||||
throw new RuntimeException(e);
|
throw new EsiaException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -140,7 +149,17 @@ public class EsiaAuthService {
|
||||||
return uriBuilder.toString();
|
return uriBuilder.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ResponseEntity<?> getEsiaTokensByCode(String esiaAuthCode, HttpServletRequest request, HttpServletResponse response) {
|
public ResponseEntity<?> getEsiaTokensByCode(String esiaAuthCode, String error,
|
||||||
|
HttpServletRequest request, HttpServletResponse response) {
|
||||||
|
if (error != null && !error.equals("null")) {
|
||||||
|
return new ResponseEntity<>(
|
||||||
|
"Произошла неизвестная ошибка. Обратитесь к системному администратору",
|
||||||
|
HttpStatus.FORBIDDEN
|
||||||
|
);
|
||||||
|
}
|
||||||
|
String esiaAccessTokenStr = null;
|
||||||
|
String prnOid = null;
|
||||||
|
Long expiresIn = null;
|
||||||
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");
|
||||||
|
|
@ -191,28 +210,31 @@ 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);
|
||||||
}
|
}
|
||||||
String esiaAccessTokenStr = tokenResponse.getAccess_token();
|
esiaAccessTokenStr = tokenResponse.getAccess_token();
|
||||||
String esiaRefreshTokenStr = tokenResponse.getRefresh_token();
|
String esiaRefreshTokenStr = tokenResponse.getRefresh_token();
|
||||||
EsiaAccessToken esiaAccessToken = personalDataService.readToken(esiaAccessTokenStr);
|
EsiaAccessToken esiaAccessToken = personalDataService.readToken(esiaAccessTokenStr);
|
||||||
String prnOid = esiaAccessToken.getSbj_id();
|
prnOid = esiaAccessToken.getSbj_id();
|
||||||
Long expiresIn = tokenResponse.getExpires_in();
|
expiresIn = tokenResponse.getExpires_in();
|
||||||
EsiaTokensStore.addAccessToken(prnOid, esiaAccessTokenStr, expiresIn);
|
EsiaTokensStore.addAccessToken(prnOid, esiaAccessTokenStr, expiresIn);
|
||||||
EsiaTokensStore.addRefreshToken(prnOid, esiaRefreshTokenStr, expiresIn);
|
EsiaTokensStore.addRefreshToken(prnOid, esiaRefreshTokenStr, expiresIn);
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
throw new EsiaException(e);
|
||||||
|
}
|
||||||
|
try {
|
||||||
Response ervuIdResponse = getErvuIdResponse(esiaAccessTokenStr);
|
Response ervuIdResponse = getErvuIdResponse(esiaAccessTokenStr);
|
||||||
Token token = jwtTokenService.createAccessToken(esiaAccessToken.getSbj_id(), expiresIn, ervuIdResponse.getErvuId());
|
createTokenAndAddCookie(response, prnOid, ervuIdResponse.getErvuId(), expiresIn);
|
||||||
int expiry = tokenResponse.getExpires_in().intValue();
|
|
||||||
securityHelper.addAccessCookies(response, token.getValue(), expiry);
|
|
||||||
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
|
|
||||||
new UsernamePasswordAuthenticationToken(token.getUserAccountId(), null);
|
|
||||||
SecurityContext context = SecurityContextHolder.createEmptyContext();
|
|
||||||
JwtAuthentication jwtAuthentication = new JwtAuthentication(usernamePasswordAuthenticationToken,
|
|
||||||
esiaAccessToken.getSbj_id(), token.getValue());
|
|
||||||
context.setAuthentication(jwtAuthentication);
|
|
||||||
SecurityContextHolder.setContext(context);
|
|
||||||
return ResponseEntity.ok("Authentication successful");
|
return ResponseEntity.ok("Authentication successful");
|
||||||
}
|
}
|
||||||
catch (Exception e) {
|
catch (Exception e) {
|
||||||
throw new RuntimeException(e);
|
createTokenAndAddCookie(response, prnOid, null, expiresIn);
|
||||||
|
String messageId = getMessageId(e);
|
||||||
|
String messageWithId = String.format("[%s] %s", messageId, e.getMessage());
|
||||||
|
LOGGER.error(messageWithId, e);
|
||||||
|
return new ResponseEntity<>(
|
||||||
|
"Произошла ошибка " + messageId + ". Обратитесь к системному администратору",
|
||||||
|
HttpStatus.FORBIDDEN
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -272,19 +294,10 @@ public class EsiaAuthService {
|
||||||
EsiaTokensStore.addAccessToken(prnOid, esiaAccessTokenStr, expiresIn);
|
EsiaTokensStore.addAccessToken(prnOid, esiaAccessTokenStr, expiresIn);
|
||||||
EsiaTokensStore.addRefreshToken(prnOid, esiaNewRefreshTokenStr, expiresIn);
|
EsiaTokensStore.addRefreshToken(prnOid, esiaNewRefreshTokenStr, expiresIn);
|
||||||
Response ervuIdResponse = getErvuIdResponse(esiaAccessTokenStr);
|
Response ervuIdResponse = getErvuIdResponse(esiaAccessTokenStr);
|
||||||
Token token = jwtTokenService.createAccessToken(esiaAccessToken.getSbj_id(), expiresIn, ervuIdResponse.getErvuId());
|
createTokenAndAddCookie(response, esiaAccessToken.getSbj_id(), ervuIdResponse.getErvuId(), expiresIn);
|
||||||
int expiry = tokenResponse.getExpires_in().intValue();
|
|
||||||
securityHelper.addAccessCookies(response, token.getValue(), expiry);
|
|
||||||
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
|
|
||||||
new UsernamePasswordAuthenticationToken(token.getUserAccountId(), null);
|
|
||||||
SecurityContext context = SecurityContextHolder.createEmptyContext();
|
|
||||||
JwtAuthentication jwtAuthentication = new JwtAuthentication(usernamePasswordAuthenticationToken,
|
|
||||||
esiaAccessToken.getSbj_id(), token.getValue());
|
|
||||||
context.setAuthentication(jwtAuthentication);
|
|
||||||
SecurityContextHolder.setContext(context);
|
|
||||||
}
|
}
|
||||||
catch (Exception e) {
|
catch (Exception e) {
|
||||||
throw new RuntimeException(e);
|
throw new EsiaException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -310,13 +323,13 @@ public class EsiaAuthService {
|
||||||
|
|
||||||
}
|
}
|
||||||
catch (Exception e) {
|
catch (Exception e) {
|
||||||
throw new RuntimeException(e);
|
throw new EsiaException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void errorHandler(HttpResponse<?> httpResponse) {
|
private void errorHandler(HttpResponse<?> httpResponse) {
|
||||||
if (httpResponse.statusCode() != 200) {
|
if (httpResponse.statusCode() != 200) {
|
||||||
throw new RuntimeException(httpResponse.statusCode() + " " + httpResponse.body());
|
throw new EsiaException(httpResponse.statusCode() + " " + httpResponse.body());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -335,7 +348,7 @@ public class EsiaAuthService {
|
||||||
return buildUrl(url, params);
|
return buildUrl(url, params);
|
||||||
}
|
}
|
||||||
catch (Exception e) {
|
catch (Exception e) {
|
||||||
throw new RuntimeException(e);
|
throw new EsiaException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -349,7 +362,7 @@ public class EsiaAuthService {
|
||||||
return objectMapper.readValue(kafkaResponse, Response.class);
|
return objectMapper.readValue(kafkaResponse, Response.class);
|
||||||
}
|
}
|
||||||
catch (Exception e) {
|
catch (Exception e) {
|
||||||
throw new RuntimeException(e);
|
throw new EsiaException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -367,4 +380,24 @@ public class EsiaAuthService {
|
||||||
person.setDocument(document);
|
person.setDocument(document);
|
||||||
return person;
|
return person;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String getMessageId(Exception exception) {
|
||||||
|
return Integer.toUnsignedString(Objects
|
||||||
|
.hashCode(getCurrentUsername()), 36)
|
||||||
|
+ "-"
|
||||||
|
+ Integer.toUnsignedString(exception.hashCode(), 36);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createTokenAndAddCookie(HttpServletResponse response, String userId, String ervuId,
|
||||||
|
Long expiresIn) {
|
||||||
|
Token token = jwtTokenService.createAccessToken(userId, expiresIn, ervuId);
|
||||||
|
securityHelper.addAccessCookies(response, token.getValue(), expiresIn.intValue());
|
||||||
|
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
|
||||||
|
new UsernamePasswordAuthenticationToken(token.getUserAccountId(), null);
|
||||||
|
SecurityContext context = SecurityContextHolder.createEmptyContext();
|
||||||
|
JwtAuthentication authentication = new JwtAuthentication(usernamePasswordAuthenticationToken,
|
||||||
|
userId, token.getValue());
|
||||||
|
context.setAuthentication(authentication);
|
||||||
|
SecurityContextHolder.setContext(context);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -47,23 +47,19 @@ public class JwtAuthenticationProvider implements AuthenticationProvider {
|
||||||
catch (Exception e) {
|
catch (Exception e) {
|
||||||
throw new BadCredentialsException("Authentication Failed.", e);
|
throw new BadCredentialsException("Authentication Failed.", e);
|
||||||
}
|
}
|
||||||
|
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
|
||||||
|
HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(
|
||||||
|
REFERENCE_REQUEST);
|
||||||
|
if (request == null) {
|
||||||
|
throw new IllegalStateException("No request found in request attributes");
|
||||||
|
}
|
||||||
if (jwtTokenService.isValid(token)) {
|
if (jwtTokenService.isValid(token)) {
|
||||||
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
|
UsernamePasswordAuthenticationToken pwdToken =
|
||||||
HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(
|
UsernamePasswordAuthenticationToken.authenticated(token.getUserAccountId(), null,
|
||||||
REFERENCE_REQUEST);
|
Collections.emptyList()
|
||||||
String[] ids = token.getUserAccountId().split(":");
|
);
|
||||||
if (request == null) {
|
|
||||||
throw new IllegalStateException("No request found in request attributes");
|
|
||||||
}
|
|
||||||
if (request.getRequestURI().endsWith("esia/logout") || ids.length == 2) {
|
|
||||||
UsernamePasswordAuthenticationToken pwdToken =
|
|
||||||
UsernamePasswordAuthenticationToken.authenticated(token.getUserAccountId(), null,
|
|
||||||
Collections.emptyList()
|
|
||||||
);
|
|
||||||
|
|
||||||
return new JwtAuthentication(pwdToken, token.getUserAccountId(), token.getValue());
|
return new JwtAuthentication(pwdToken, token.getUserAccountId(), token.getValue());
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new BadCredentialsException(
|
throw new BadCredentialsException(
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import java.util.Optional;
|
||||||
import javax.servlet.http.Cookie;
|
import javax.servlet.http.Cookie;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
import org.springframework.web.util.WebUtils;
|
import org.springframework.web.util.WebUtils;
|
||||||
import ru.micord.ervu.security.webbpm.jwt.JwtAuthentication;
|
import ru.micord.ervu.security.webbpm.jwt.JwtAuthentication;
|
||||||
|
|
@ -31,4 +32,12 @@ public final class SecurityUtil {
|
||||||
})
|
})
|
||||||
.orElse(null);
|
.orElse(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String getCurrentUsername() {
|
||||||
|
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
|
||||||
|
if (auth != null && auth.isAuthenticated()) {
|
||||||
|
return auth.getName();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,18 +30,8 @@ export abstract class AuthGuard implements CanActivate {
|
||||||
if (isAccess) {
|
if (isAccess) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else if (error) {
|
if (code || error) {
|
||||||
let userErrorMessage = 'Произошла неизвестная ошибка. Обратитесь к системному администратору';
|
const params = new HttpParams().set('code', code).set('error', error);
|
||||||
let errorCode = this.extractCode(errorDescription);
|
|
||||||
if (errorCode) {
|
|
||||||
userErrorMessage = EsiaErrorDetail.getDescription(errorCode);
|
|
||||||
}
|
|
||||||
let errorMessage = error + ', error description = ' + errorDescription;
|
|
||||||
this.messageService.error(userErrorMessage)
|
|
||||||
throw new Error(errorMessage);
|
|
||||||
}
|
|
||||||
else if (code) {
|
|
||||||
const params = new HttpParams().set('code', code);
|
|
||||||
this.httpClient.get("esia/auth",
|
this.httpClient.get("esia/auth",
|
||||||
{
|
{
|
||||||
params: params, responseType: 'text', observe: 'response', headers: {
|
params: params, responseType: 'text', observe: 'response', headers: {
|
||||||
|
|
@ -57,8 +47,20 @@ export abstract class AuthGuard implements CanActivate {
|
||||||
let errorMessage = reason.error.messages != null
|
let errorMessage = reason.error.messages != null
|
||||||
? reason.error.messages
|
? reason.error.messages
|
||||||
: reason.error.replaceAll('\\', '');
|
: reason.error.replaceAll('\\', '');
|
||||||
this.messageService.error(errorMessage);
|
if (error) {
|
||||||
console.error(reason);
|
errorMessage = 'Произошла неизвестная ошибка. Обратитесь к системному администратору';
|
||||||
|
let errorCode = this.extractCode(errorDescription);
|
||||||
|
if (errorCode) {
|
||||||
|
errorMessage = EsiaErrorDetail.getDescription(errorCode);
|
||||||
|
}
|
||||||
|
let consoleError = error + ', error description = ' + errorDescription;
|
||||||
|
this.messageService.error(errorMessage);
|
||||||
|
console.error(consoleError);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.messageService.error(errorMessage);
|
||||||
|
console.error(reason);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue