SUPPORT-8830 fixes
This commit is contained in:
parent
61a2c3b5e3
commit
d45d25baaf
10 changed files with 127 additions and 76 deletions
|
|
@ -25,9 +25,7 @@ public class LogoutSuccessHandler
|
|||
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response,
|
||||
Authentication authentication) throws IOException {
|
||||
String url = esiaAuthService.logout(request, response);
|
||||
response.setStatus(HttpServletResponse.SC_OK);
|
||||
response.getWriter().write(url);
|
||||
response.getWriter().flush();
|
||||
response.sendRedirect(url);
|
||||
CsrfToken csrfToken = this.csrfTokenRepository.generateToken(request);
|
||||
this.csrfTokenRepository.saveToken(csrfToken, request, response);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,6 +29,9 @@ public class EsiaConfig {
|
|||
@Value("${esia.redirect.url}")
|
||||
private String redirectUrl;
|
||||
|
||||
@Value("${esia.logout.redirect.url}")
|
||||
private String logoutRedirectUrl;
|
||||
|
||||
@Value("${sign.url}")
|
||||
private String signUrl;
|
||||
|
||||
|
|
@ -101,6 +104,10 @@ public class EsiaConfig {
|
|||
return esiaTokenUrl;
|
||||
}
|
||||
|
||||
public String getLogoutRedirectUrl() {
|
||||
return logoutRedirectUrl;
|
||||
}
|
||||
|
||||
public String getEsiaUploadDataRole() {
|
||||
return esiaUploadDataRole;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ import java.util.Arrays;
|
|||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
|
|
@ -120,7 +119,7 @@ public class EsiaAuthService {
|
|||
"obj_type", "B L F A",
|
||||
"client_certificate_hash", esiaConfig.getClientCertHash());
|
||||
|
||||
return makeRequest(url, params);
|
||||
return buildUrl(url, params);
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
|
|
@ -143,12 +142,13 @@ public class EsiaAuthService {
|
|||
.replace("+", "%20");
|
||||
}
|
||||
|
||||
private static String makeRequest(URL url, Map<String, String> params) {
|
||||
private static String buildUrl(URL url, Map<String, String> params) {
|
||||
StringBuilder uriBuilder = new StringBuilder(url.toString());
|
||||
uriBuilder.append('?');
|
||||
for (Map.Entry<String, String> node : params.entrySet()) {
|
||||
uriBuilder.append(node.getKey()).append('=').append(node.getValue()).append("&");
|
||||
}
|
||||
uriBuilder.deleteCharAt(uriBuilder.length() - 1);
|
||||
return uriBuilder.toString();
|
||||
}
|
||||
|
||||
|
|
@ -218,8 +218,7 @@ public class EsiaAuthService {
|
|||
EsiaTokensStore.addRefreshToken(prnOid, esiaRefreshTokenStr, expiresIn);
|
||||
Token token = jwtTokenService.createAccessToken(esiaAccessToken.getSbj_id(), expiresIn, ervuId, hasRole);
|
||||
int expiry = tokenResponse.getExpires_in().intValue();
|
||||
Cookie accessCookie = securityHelper.createAccessCookie(token.getValue(), expiry);
|
||||
response.addCookie(accessCookie);
|
||||
securityHelper.addAccessCookies(response,token.getValue(), expiry);
|
||||
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
|
||||
new UsernamePasswordAuthenticationToken(token.getUserAccountId(), null);
|
||||
SecurityContext context = SecurityContextHolder.createEmptyContext();
|
||||
|
|
@ -228,8 +227,6 @@ public class EsiaAuthService {
|
|||
authenticationManager.authenticate(jwtAuthentication);
|
||||
context.setAuthentication(jwtAuthentication);
|
||||
SecurityContextHolder.setContext(context);
|
||||
Cookie authMarkerCookie = securityHelper.createAuthMarkerCookie("true", expiry);
|
||||
response.addCookie(authMarkerCookie);
|
||||
if (!hasRole) {
|
||||
LOGGER.error("The user with id = " + prnOid + " does not have the required role");
|
||||
return new ResponseEntity<>(
|
||||
|
|
@ -307,8 +304,7 @@ public class EsiaAuthService {
|
|||
String ervuId = getErvuId(esiaAccessTokenStr, prnOid);
|
||||
Token token = jwtTokenService.createAccessToken(esiaAccessToken.getSbj_id(), expiresIn, ervuId, true);
|
||||
int expiry = tokenResponse.getExpires_in().intValue();
|
||||
Cookie accessCookie = securityHelper.createAccessCookie(token.getValue(), expiry);
|
||||
response.addCookie(accessCookie);
|
||||
securityHelper.addAccessCookies(response, token.getValue(), expiry);
|
||||
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
|
||||
new UsernamePasswordAuthenticationToken(token.getUserAccountId(), null);
|
||||
SecurityContext context = SecurityContextHolder.createEmptyContext();
|
||||
|
|
@ -317,8 +313,6 @@ public class EsiaAuthService {
|
|||
authenticationManager.authenticate(jwtAuthentication);
|
||||
context.setAuthentication(jwtAuthentication);
|
||||
SecurityContextHolder.setContext(context);
|
||||
Cookie authMarkerCookie = securityHelper.createAuthMarkerCookie("true", expiry);
|
||||
response.addCookie(authMarkerCookie);
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
|
|
@ -364,12 +358,12 @@ public class EsiaAuthService {
|
|||
EsiaTokensStore.removeAccessToken(userId);
|
||||
EsiaTokensStore.removeRefreshToken(userId);
|
||||
String logoutUrl = esiaConfig.getEsiaBaseUri() + esiaConfig.getEsiaLogoutUrl();
|
||||
String redirectUrl = esiaConfig.getRedirectUrl();
|
||||
String redirectUrl = esiaConfig.getLogoutRedirectUrl();
|
||||
URL url = new URL(logoutUrl);
|
||||
Map<String, String> params = mapOf(
|
||||
"client_id", esiaConfig.getClientId(),
|
||||
"redirect_url", redirectUrl);
|
||||
return makeRequest(url, params);
|
||||
return buildUrl(url, params);
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
|
|
|
|||
|
|
@ -1,43 +1,89 @@
|
|||
package ru.micord.ervu.security.webbpm.jwt.helper;
|
||||
|
||||
import javax.servlet.http.Cookie;
|
||||
import java.net.IDN;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import ru.micord.ervu.security.webbpm.jwt.util.SecurityUtil;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.ResponseCookie;
|
||||
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.webbpm.jwt.util.SecurityUtil.createCookie;
|
||||
|
||||
public final class SecurityHelper {
|
||||
@Value("${cookie.path:#{null}}")
|
||||
private String accessCookiePath;
|
||||
@Value("${cookie.domain:#{null}}")
|
||||
private String accessCookieDomain;
|
||||
@Value("${cookie.secure:false}")
|
||||
private boolean accessCookieSecure;
|
||||
@Value("${cookie.same.site:Lax}")
|
||||
private String accessCookieSameSite;
|
||||
|
||||
@PostConstruct
|
||||
private void init() {
|
||||
|
||||
if (accessCookieDomain != null) {
|
||||
accessCookieDomain = IDN.toASCII(accessCookieDomain);
|
||||
}
|
||||
}
|
||||
|
||||
public void clearAccessCookies(HttpServletResponse response) {
|
||||
Cookie tokenCookie = createCookie(AUTH_TOKEN, null, null);
|
||||
tokenCookie.setMaxAge(0);
|
||||
tokenCookie.setPath(accessCookiePath);
|
||||
tokenCookie.setHttpOnly(true);
|
||||
response.addCookie(tokenCookie);
|
||||
ResponseCookie emptyAuthToken = createCookie(AUTH_TOKEN, null, accessCookiePath)
|
||||
.maxAge(0).build();
|
||||
addResponseCookie(response, emptyAuthToken);
|
||||
|
||||
Cookie markerCookie = createCookie(AUTH_MARKER, null, null);
|
||||
markerCookie.setMaxAge(0);
|
||||
markerCookie.setPath("/");
|
||||
response.addCookie(markerCookie);
|
||||
ResponseCookie emptyAuthMarker = createCookie(AUTH_MARKER, null, "/")
|
||||
.maxAge(0)
|
||||
.secure(false)
|
||||
.httpOnly(false)
|
||||
.build();
|
||||
addResponseCookie(response, emptyAuthMarker);
|
||||
}
|
||||
|
||||
public Cookie createAccessCookie(String cookieValue, int expiry) {
|
||||
Cookie authToken = createCookie(SecurityUtil.AUTH_TOKEN, cookieValue, accessCookiePath);
|
||||
authToken.setPath(accessCookiePath);
|
||||
authToken.setMaxAge(expiry);
|
||||
return authToken;
|
||||
private void addResponseCookie(HttpServletResponse response, ResponseCookie cookie) {
|
||||
response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString());
|
||||
}
|
||||
|
||||
public Cookie createAuthMarkerCookie(String cookieValue, int expiry) {
|
||||
Cookie marker = createCookie(AUTH_MARKER, cookieValue, "/");
|
||||
marker.setMaxAge(expiry);
|
||||
marker.setHttpOnly(false);
|
||||
return marker;
|
||||
public void addAccessCookies(HttpServletResponse response, String cookieValue, int expiry) {
|
||||
ResponseCookie authTokenCookie = createCookie(AUTH_TOKEN, cookieValue, accessCookiePath)
|
||||
.maxAge(expiry)
|
||||
.build();
|
||||
addResponseCookie(response, authTokenCookie);
|
||||
|
||||
ResponseCookie authMarker = createCookie(AUTH_MARKER, "true", "/")
|
||||
.maxAge(expiry)
|
||||
.secure(false)
|
||||
.httpOnly(false)
|
||||
.build();
|
||||
addResponseCookie(response, authMarker);
|
||||
}
|
||||
|
||||
public ResponseCookie.ResponseCookieBuilder createCookie(String name, String value, String path) {
|
||||
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
|
||||
|
||||
if (requestAttributes == null) {
|
||||
throw new IllegalStateException("Must be called only in request context");
|
||||
}
|
||||
HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(
|
||||
REFERENCE_REQUEST);
|
||||
|
||||
if (request == null) {
|
||||
throw new IllegalStateException("Must be called only in request context");
|
||||
}
|
||||
String cookieValue = value != null ? URLEncoder.encode(value, StandardCharsets.UTF_8) : "";
|
||||
return ResponseCookie.from(name, cookieValue)
|
||||
.path(path != null ? path : request.getContextPath())
|
||||
.httpOnly(true)
|
||||
.domain(accessCookieDomain)
|
||||
.secure(accessCookieSecure)
|
||||
.sameSite(accessCookieSameSite);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,10 @@
|
|||
package ru.micord.ervu.security.webbpm.jwt.util;
|
||||
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.springframework.web.context.request.RequestAttributes;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.util.WebUtils;
|
||||
|
||||
import static org.springframework.web.context.request.RequestAttributes.REFERENCE_REQUEST;
|
||||
|
||||
public final class SecurityUtil {
|
||||
public static final String AUTH_TOKEN = "auth_token";
|
||||
|
||||
|
|
@ -20,24 +14,6 @@ public final class SecurityUtil {
|
|||
//empty
|
||||
}
|
||||
|
||||
public static Cookie createCookie(String name, String value, String path) {
|
||||
String cookieValue = value != null ? URLEncoder.encode(value, StandardCharsets.UTF_8) : null;
|
||||
Cookie cookie = new Cookie(name, cookieValue);
|
||||
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
|
||||
HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(
|
||||
REFERENCE_REQUEST);
|
||||
|
||||
if (path != null) {
|
||||
cookie.setPath(path);
|
||||
}
|
||||
else {
|
||||
cookie.setPath(request.getContextPath());
|
||||
}
|
||||
cookie.setHttpOnly(true);
|
||||
|
||||
return cookie;
|
||||
}
|
||||
|
||||
public static String extractAuthToken(HttpServletRequest httpRequest) {
|
||||
Cookie cookie = WebUtils.getCookie(httpRequest, AUTH_TOKEN);
|
||||
return cookie != null ? cookie.getValue() : null;
|
||||
|
|
|
|||
|
|
@ -768,9 +768,13 @@ JBPM использует 3 корневых категории логирова
|
|||
- `ESIA_CLIENT_ID` - – идентификатор системы-клиента (мнемоника системы в ЕСИА указанная прописными буквами)
|
||||
- `ESIA_REDIRECT_URL` - ссылка, по которой должен быть направлен пользователь
|
||||
после того, как ЕСИА даст разрешение на доступ к ресурсу.
|
||||
Важно: ESIA_REDIRECT_URL должна содержать полный адрес вплоть до последнего слэша
|
||||
Важно: `ESIA_REDIRECT_URL` должна содержать полный адрес вплоть до последнего слэша
|
||||
- https://lkul.ervu.loc/ - правильное значение параметра
|
||||
- https://lkul.ervu.loc - неправильное значение параметра
|
||||
- `ESIA_LOGOUT_REDIRECT_URL` - ссылка, по которой должен быть направлен пользователь после logout-a
|
||||
Важно: `ESIA_LOGOUT_REDIRECT_URL` должна содержать полный адрес вплоть до последнего слэша:
|
||||
> - https://lkul.ervu.loc/home.html - правильное значение параметра
|
||||
> - https://lkul.ervu.loc - неправильное значение параметра
|
||||
|
||||
- `ESIA_UPLOAD_DATA_ROLE` - мнемоника группы, для роли "Сотрудник, ответственный за военно-учетную работу".
|
||||
- `SIGN_URL` - url для подписания с помощью КриптоПро секрета клиента, необходимого для аутентификации через ЕСИА.
|
||||
|
|
@ -813,4 +817,4 @@ JBPM использует 3 корневых категории логирова
|
|||
- `ERVU_KAFKA_JOURNAL_REPLY_TOPIC` - топик для чтения данных по журналу взаимодействия
|
||||
- `ERVU_KAFKA_EXCERPT_REQUEST_TOPIC` - топик для записи запроса для получения выписки по журналу взаимодействия
|
||||
- `ERVU_KAFKA_EXCERPT_REPLY_TOPIC` - топик для чтения выписки по журналу взаимодействия. Содержит ссылку на S3 с файлом выписки
|
||||
- `DB.JOURNAL.EXCLUDED.STATUSES` - статусы файла, которые необходимо исключить при получении данных по журналу взаимодействия из базы данных приложения
|
||||
- `DB.JOURNAL.EXCLUDED.STATUSES` - статусы файла, которые необходимо исключить при получении данных по журналу взаимодействия из базы данных приложения
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ ESIA_ORG_SCOPE_URL=http://esia.gosuslugi.ru/
|
|||
ESIA_BASE_URI=https://esia-portal1.test.gosuslugi.ru/
|
||||
ESIA_CLIENT_ID=MNSV89
|
||||
ESIA_REDIRECT_URL=https://lkrp-dev.micord.ru/ul/
|
||||
ESIA_LOGOUT_REDIRECT_URL=https://lkrp-dev.micord.ru/ul/home.html
|
||||
ESIA_UPLOAD_DATA_ROLE=MNSV89_UPLOAD_DATA
|
||||
SIGN_URL=https://ervu-sign-dev.k8s.micord.ru/sign
|
||||
ESIA_CLIENT_CERT_HASH=04508B4B0B58776A954A0E15F574B4E58799D74C61EE020B3330716C203E3BDD
|
||||
|
|
|
|||
|
|
@ -4,6 +4,9 @@
|
|||
<title>Личный кабинет юр.лица</title>
|
||||
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta http-equiv="Content-Security-Policy"
|
||||
content="default-src 'self'; script-src 'self'; style-src 'unsafe-inline' 'self' data:; font-src 'self' data:; img-src 'self' data:"/>
|
||||
<meta name="referrer" content="strict-origin-when-cross-origin"/>
|
||||
<link rel="icon" type="image/png" href="src/resources/img/logo.png"/>
|
||||
</head>
|
||||
<body webbpm class="webbpm ervu_lkrp_ul">
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
<form #logoutForm method="post" [action]="formAction" hidden style="display: none">
|
||||
<input type="hidden" name="_csrf" [value]="csrfValue"/>
|
||||
</form>
|
||||
<button class="user-info" ngbDropdownToggle *ngIf="isAuthenticated()">{{getUserFullname()}}</button>
|
||||
<div ngbDropdownMenu *ngIf="isAuthenticated()">
|
||||
<div class="user-department">{{getOrgUnitName()}}</div>
|
||||
<a routerLink="/mydata" class="data">Данные организации</a>
|
||||
<button ngbDropdownItem class="exit" (click)="logout()">Выйти</button>
|
||||
<button ngbDropdownItem class="exit" (click)="logoutForm.submit()">Выйти</button>
|
||||
</div>
|
||||
<button class="exit" *ngIf="!isAuthenticated()" (click)="logout()">Выйти</button>
|
||||
<button class="exit" *ngIf="!isAuthenticated()" (click)="logoutForm.submit()">Выйти</button>
|
||||
|
|
|
|||
|
|
@ -1,23 +1,46 @@
|
|||
import {ChangeDetectorRef, Component, OnInit} from "@angular/core";
|
||||
import {Router} from "@angular/router";
|
||||
import {HttpClient} from "@angular/common/http";
|
||||
import {ChangeDetectorRef, Component, DoCheck, OnInit} from "@angular/core";
|
||||
import {HttpClient, HttpXsrfTokenExtractor} from "@angular/common/http";
|
||||
import {AuthenticationService} from "../../security/authentication.service";
|
||||
import {AppConfigService} from "@webbpm/base-package";
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: "[log-out]",
|
||||
templateUrl: "../../../../../src/resources/template/app/component/log_out.html"
|
||||
})
|
||||
export class LogOutComponent implements OnInit{
|
||||
export class LogOutComponent implements OnInit, DoCheck{
|
||||
private static readonly BACKEND_URL: string = "backend.url";
|
||||
private static readonly BACKEND_CONTEXT: string = "backend.context";
|
||||
private static readonly LOGOUT_URL_POSTFIX: string = "/esia/logout";
|
||||
|
||||
private userFullname: string;
|
||||
private orgUnitName: string;
|
||||
csrfValue: any;
|
||||
formAction: any;
|
||||
|
||||
constructor(private router: Router, private httpClient: HttpClient,
|
||||
private authenticationService: AuthenticationService, private cd: ChangeDetectorRef) {
|
||||
constructor(private httpClient: HttpClient,
|
||||
private authenticationService: AuthenticationService,
|
||||
private appConfigService: AppConfigService,
|
||||
private tokenExtractor: HttpXsrfTokenExtractor,
|
||||
private cd: ChangeDetectorRef) {
|
||||
let backendUrl = this.appConfigService.getParamValue(LogOutComponent.BACKEND_URL);
|
||||
let backendContext = this.appConfigService.getParamValue(
|
||||
LogOutComponent.BACKEND_CONTEXT);
|
||||
|
||||
if (backendUrl) {
|
||||
this.formAction = `${backendUrl}${LogOutComponent.LOGOUT_URL_POSTFIX}`;
|
||||
}
|
||||
else if (backendContext) {
|
||||
this.formAction = `/${backendContext}${LogOutComponent.LOGOUT_URL_POSTFIX}`;
|
||||
}
|
||||
}
|
||||
|
||||
ngDoCheck(): void {
|
||||
this.csrfValue = this.tokenExtractor.getToken();
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.csrfValue = this.tokenExtractor.getToken();
|
||||
let isAuth = this.authenticationService.isAuthenticated();
|
||||
if (isAuth) {
|
||||
Promise.all([
|
||||
|
|
@ -31,10 +54,6 @@ export class LogOutComponent implements OnInit{
|
|||
}
|
||||
}
|
||||
|
||||
public logout(): void {
|
||||
this.authenticationService.logout();
|
||||
}
|
||||
|
||||
public getUserFullname(): string {
|
||||
return this.userFullname;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue