Merge remote-tracking branch 'origin/hotfix/1.9.3'

This commit is contained in:
Zaripov Emil 2024-12-26 10:40:24 +03:00
commit 93c19f995e
16 changed files with 157 additions and 193 deletions

View file

@ -5,7 +5,7 @@
<parent>
<groupId>ru.micord.ervu.lkrp</groupId>
<artifactId>fl</artifactId>
<version>1.9.2</version>
<version>1.9.3-SNAPSHOT</version>
</parent>
<groupId>ru.micord.ervu.lkrp.fl</groupId>
<artifactId>backend</artifactId>

View file

@ -39,9 +39,11 @@ public class SummonsResponseDataConverter {
.issueDate(responseData.getIssueDate())
.issueOrg(responseData.getIssueOrg())
.issueIdCode(responseData.getIssueIdCode())
.militaryCommissariatName(responseData.getRecruitmentInfo().getMilitaryCommissariatName())
.residenceAddress(getAddressByCode(responseData.getAddressesList(), RESIDENCE_ADDRESS_CODE))
.stayAddress(getAddressByCode(responseData.getAddressesList(), STAY_ADDRESS_CODE))
.actualAddress(getAddressByCode(responseData.getAddressesList(), ACTUAL_ADDRESS_CODE));
Optional<SummonsInfo> summonsInfoOpt = responseData.getSummonsInfosList().stream()
// get last subpoena
.max(Comparator.comparing(

View file

@ -25,7 +25,6 @@ import ru.micord.ervu.security.webbpm.jwt.JwtMatcher;
import ru.micord.ervu.security.webbpm.jwt.UnauthorizedEntryPoint;
import ru.micord.ervu.security.webbpm.jwt.filter.JwtAuthenticationFilter;
import ru.micord.ervu.security.webbpm.jwt.helper.SecurityHelper;
import ru.micord.ervu.security.webbpm.jwt.service.JwtTokenService;
import static ru.micord.ervu.security.SecurityConstants.ESIA_LOGOUT;
@ -105,10 +104,9 @@ public class SecurityConfig {
@Bean
public JwtAuthenticationFilter jwtAuthenticationFilter(SecurityHelper securityHelper,
AuthenticationManager manager,
JwtTokenService jwtTokenService) {
AuthenticationManager manager) {
JwtAuthenticationFilter jwtAuthenticationFilter = new JwtAuthenticationFilter(
new JwtMatcher("/**", PERMIT_ALL), entryPoint(), securityHelper, jwtTokenService);
new JwtMatcher("/**", PERMIT_ALL), entryPoint(), securityHelper);
jwtAuthenticationFilter.setAuthenticationManager(manager);
return jwtAuthenticationFilter;
}

View file

@ -27,7 +27,7 @@ import ru.micord.ervu.kafka.model.Document;
import ru.micord.ervu.kafka.model.Person;
import ru.micord.ervu.kafka.model.Response;
import ru.micord.ervu.kafka.service.ReplyingKafkaService;
import ru.micord.ervu.security.esia.token.TokensStore;
import ru.micord.ervu.security.esia.token.EsiaTokensStore;
import ru.micord.ervu.security.esia.config.EsiaConfig;
import ru.micord.ervu.security.esia.model.FormUrlencoded;
import ru.micord.ervu.security.esia.model.EsiaAccessToken;
@ -199,8 +199,8 @@ public class EsiaAuthService {
EsiaAccessToken esiaAccessToken = personalDataService.readToken(esiaAccessTokenStr);
String prnOid = esiaAccessToken.getSbj_id();
Long expiresIn = tokenResponse.getExpires_in();
TokensStore.addAccessToken(prnOid, esiaAccessTokenStr, expiresIn);
TokensStore.addRefreshToken(prnOid, esiaRefreshTokenStr, expiresIn);
EsiaTokensStore.addAccessToken(prnOid, esiaAccessTokenStr, expiresIn);
EsiaTokensStore.addRefreshToken(prnOid, esiaRefreshTokenStr, expiresIn);
Response ervuIdResponse = getErvuIdResponse(esiaAccessTokenStr);
Token token = jwtTokenService.createAccessToken(esiaAccessToken.getSbj_id(), expiresIn, ervuIdResponse.getErvuId());
int expiry = tokenResponse.getExpires_in().intValue();
@ -276,8 +276,8 @@ public class EsiaAuthService {
EsiaAccessToken esiaAccessToken = personalDataService.readToken(esiaAccessTokenStr);
String prnOid = esiaAccessToken.getSbj_id();
Long expiresIn = tokenResponse.getExpires_in();
TokensStore.addAccessToken(prnOid, esiaAccessTokenStr, expiresIn);
TokensStore.addRefreshToken(prnOid, esiaNewRefreshTokenStr, expiresIn);
EsiaTokensStore.addAccessToken(prnOid, esiaAccessTokenStr, expiresIn);
EsiaTokensStore.addRefreshToken(prnOid, esiaNewRefreshTokenStr, expiresIn);
Response ervuIdResponse = getErvuIdResponse(esiaAccessTokenStr);
Token token = jwtTokenService.createAccessToken(esiaAccessToken.getSbj_id(), expiresIn, ervuIdResponse.getErvuId());
int expiry = tokenResponse.getExpires_in().intValue();
@ -335,8 +335,8 @@ public class EsiaAuthService {
try {
securityHelper.clearAccessCookies(response);
String userId = jwtTokenService.getUserAccountId(request);
TokensStore.removeAccessToken(userId);
TokensStore.removeRefreshToken(userId);
EsiaTokensStore.removeAccessToken(userId);
EsiaTokensStore.removeRefreshToken(userId);
String logoutUrl = esiaConfig.getEsiaBaseUri() + esiaConfig.getEsiaLogoutUrl();
String redirectUrl = esiaConfig.getRedirectUrl();
URL url = new URL(logoutUrl);

View file

@ -1,12 +1,17 @@
package ru.micord.ervu.security.esia.token;
import java.lang.invoke.MethodHandles;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author Eduard Tihomirov
*/
public class TokensStore {
public class EsiaTokensStore {
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> refreshTokensMap = new ConcurrentHashMap<>();
@ -21,6 +26,19 @@ public class TokensStore {
return accessTokensMap.get(prnOid).getAccessToken();
}
public static boolean validateAccessToken(String prnOid) {
ExpiringToken token = accessTokensMap.get(prnOid);
if (token == null || token.getAccessToken() == null) {
LOGGER.error("No ESIA access token for prnOid: " + prnOid);
return false;
}
else if (token.isExpired()) {
LOGGER.error("ESIA access token expired for prnOid: " + prnOid);
return false;
}
return true;
}
public static void removeExpiredAccessToken() {
for (String key : accessTokensMap.keySet()) {
ExpiringToken token = accessTokensMap.get(key);

View file

@ -14,7 +14,7 @@ public class TokensClearShedulerService {
@SchedulerLock(name = "clearToken")
@Transactional
public void load() {
TokensStore.removeExpiredRefreshToken();
TokensStore.removeExpiredAccessToken();
EsiaTokensStore.removeExpiredRefreshToken();
EsiaTokensStore.removeExpiredAccessToken();
}
}

View file

@ -2,6 +2,8 @@ package ru.micord.ervu.security.webbpm.jwt;
import java.util.Collections;
import javax.servlet.http.HttpServletRequest;
import io.jsonwebtoken.ExpiredJwtException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;
@ -11,9 +13,13 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticatio
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import ru.micord.ervu.security.webbpm.jwt.model.Token;
import ru.micord.ervu.security.webbpm.jwt.service.JwtTokenService;
import static org.springframework.web.context.request.RequestAttributes.REFERENCE_REQUEST;
@Component
public class JwtAuthenticationProvider implements AuthenticationProvider {
@ -42,16 +48,26 @@ public class JwtAuthenticationProvider implements AuthenticationProvider {
throw new BadCredentialsException("Authentication Failed.", e);
}
if (!jwtTokenService.isValid(token)) {
throw new BadCredentialsException("Auth token is not valid for user " + token.getUserAccountId());
if (jwtTokenService.isValid(token)) {
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(
REFERENCE_REQUEST);
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());
}
}
UsernamePasswordAuthenticationToken pwdToken =
UsernamePasswordAuthenticationToken.authenticated(token.getUserAccountId(), null,
Collections.emptyList()
);
return new JwtAuthentication(pwdToken, token.getUserAccountId(), token.getValue());
throw new BadCredentialsException(
"Auth token is not valid for user " + token.getUserAccountId());
}
@Override

View file

@ -16,6 +16,7 @@ import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.RequestMatcher;
import ru.micord.ervu.security.esia.token.EsiaTokensStore;
import ru.micord.ervu.security.webbpm.jwt.JwtAuthentication;
import ru.micord.ervu.security.webbpm.jwt.helper.SecurityHelper;
import ru.micord.ervu.security.webbpm.jwt.model.Token;
@ -35,16 +36,12 @@ public class JwtAuthenticationFilter extends AbstractAuthenticationProcessingFil
private final SecurityHelper securityHelper;
private final JwtTokenService jwtTokenService;
public JwtAuthenticationFilter(RequestMatcher requestMatcher,
AuthenticationEntryPoint entryPoint,
SecurityHelper securityHelper,
JwtTokenService jwtTokenService) {
SecurityHelper securityHelper) {
super(requestMatcher);
this.entryPoint = entryPoint;
this.securityHelper = securityHelper;
this.jwtTokenService = jwtTokenService;
}
@Override
@ -58,18 +55,11 @@ public class JwtAuthenticationFilter extends AbstractAuthenticationProcessingFil
}
try {
authentication = getAuthenticationManager().authenticate(authentication);
if (!httpServletRequest.getRequestURI().endsWith("esia/logout")) {
Token token = jwtTokenService.getToken(tokenStr);
String[] ids = token.getUserAccountId().split(":");
if (ids.length != 2) {
throw new CredentialsExpiredException("Invalid token. User has no ervuId");
}
}
}
catch (CredentialsExpiredException e) {
catch (AuthenticationException e) {
LOGGER.warn(e.getMessage());
securityHelper.clearAccessCookies(httpServletResponse);
httpServletResponse.setStatus(401);
LOGGER.warn(e.getMessage());
return null;
}
return authentication;

View file

@ -14,7 +14,7 @@ import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import ru.micord.ervu.security.esia.token.TokensStore;
import ru.micord.ervu.security.esia.token.EsiaTokensStore;
import ru.micord.ervu.security.webbpm.jwt.model.Token;
import ru.cg.webbpm.modules.resources.api.ResourceMetadataUtils;
@ -34,9 +34,6 @@ public class JwtTokenService {
ResourceMetadataUtils.PROJECT_GROUP_ID + "." + ResourceMetadataUtils.PROJECT_ARTIFACT_ID;
private final SecretKey SIGNING_KEY;
@Autowired
private HttpServletRequest request;
@Autowired
public JwtTokenService(@Value("${webbpm.security.token.secret.key:ZjE5ZjMxNmYtODViZC00ZTQ5LWIxZmYtOGEzYzE3Yjc1MDVk}")
String secretKey) {
@ -67,7 +64,8 @@ public class JwtTokenService {
LOGGER.info("Token {} is expired ", token.getValue());
return false;
}
return true;
String[] ids = token.getUserAccountId().split(":");
return EsiaTokensStore.validateAccessToken(ids[0]);
}
public Token getToken(String token) {
@ -79,17 +77,12 @@ public class JwtTokenService {
return new Token(claims.getSubject(), claims.getIssuer(), claims.getExpiration(), token);
}
public String getErvuId() {
String extractAuthToken = extractAuthToken(request);
return getToken(extractAuthToken).getUserAccountId().split(":")[1];
}
public String getAccessToken(HttpServletRequest request) {
return TokensStore.getAccessToken(getUserAccountId(request));
return EsiaTokensStore.getAccessToken(getUserAccountId(request));
}
public String getRefreshToken(HttpServletRequest request) {
return TokensStore.getRefreshToken(getUserAccountId(request));
return EsiaTokensStore.getRefreshToken(getUserAccountId(request));
}
public String getUserAccountId(HttpServletRequest request) {

View file

@ -1,6 +1,6 @@
ARG BUILDER_IMAGE=registry.altlinux.org/basealt/altsp:c10f1
ARG BACKEND_IMAGE=repo.micord.ru/alt/alt-tomcat:c10f1-9.0.59-20240903
ARG FRONTEND_IMAGE=docker.angie.software/angie:latest
ARG FRONTEND_IMAGE=nginx:1.24-alpine-slim
FROM $BUILDER_IMAGE AS builder
@ -45,5 +45,5 @@ COPY --from=builder /app/frontend/target/frontend*.war /var/lib/tomcat/webapps/R
FROM $FRONTEND_IMAGE AS frontend
COPY config/angie.conf /etc/angie/angie.conf
COPY config/nginx.conf /etc/nginx/nginx.conf
COPY --from=builder /app/frontend/dist /frontend

View file

@ -1,84 +0,0 @@
include /etc/angie/modules-enabled.d/*.conf;
worker_processes 10;
error_log /var/log/angie/error.log;
events {
worker_connections 1024;
}
include /etc/angie/conf-enabled.d/*.conf;
http {
include /etc/angie/mime.types;
default_type application/octet-stream;
sendfile on;
gzip on;
# text/html doesn't need to be defined there, it's compressed always
gzip_types text/plain text/css text/xml application/x-javascript application/atom+xml;
# gzip_comp_level 9;
include /etc/angie/sites-enabled.d/*.conf;
log_format angie_main
'$remote_addr - $remote_user [$time_local] $request '
'"$status" $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for" '
'"$request_filename" "$gzip_ratio" $upstream_response_time server: $host : $document_root $fastcgi_script_name ';
server {
listen 80 default;
access_log /var/log/angie/access.log angie_main;
error_log /var/log/angie/error.log error;
charset utf-8;
client_max_body_size 32m;
##
# `gzip` Settings
#
#
gzip on;
gzip_disable "msie6";
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_min_length 256;
gzip_types
application/atom+xml
application/geo+json
application/javascript
application/x-javascript
application/json
application/ld+json
application/manifest+json
application/rdf+xml
application/rss+xml
application/xhtml+xml
application/xml
font/eot
font/otf
font/ttf
image/svg+xml
text/css
text/javascript
text/plain
text/xml;
location / {
root /frontend;
index index.html;
expires -1;
try_files $uri $uri/ $uri/index.html;
}
}
}

View file

@ -11,74 +11,105 @@ events {
include /etc/nginx/conf-enabled.d/*.conf;
http {
include /etc/nginx/mime.types;
include /etc/nginx/mime.types;
default_type application/octet-stream;
sendfile on;
sendfile on;
gzip on;
gzip on;
# text/html doesn't need to be defined there, it's compressed always
gzip_types text/plain text/css text/xml application/x-javascript application/atom+xml;
# text/html doesn't need to be defined there, it's compressed always
gzip_types text/plain text/css text/xml application/x-javascript application/atom+xml;
# gzip_comp_level 9;
include /etc/nginx/sites-enabled.d/*.conf;
# gzip_comp_level 9;
include /etc/nginx/sites-enabled.d/*.conf;
log_format nginx_main
'$remote_addr - $remote_user [$time_local] $request '
'"$status" $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for" '
'"$request_filename" "$gzip_ratio" $upstream_response_time server: $host : $document_root $fastcgi_script_name ';
log_format nginx_main
'$remote_addr - $remote_user [$time_local] $request '
'"$status" $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for" '
'"$request_filename" "$gzip_ratio" $upstream_response_time server: $host : $document_root $fastcgi_script_name ';
server {
listen 80 default;
server {
listen 80 default;
access_log /var/log/nginx/access.log nginx_main;
error_log /var/log/nginx/error.log error;
error_log /var/log/nginx/error.log error;
charset utf-8;
charset utf-8;
client_max_body_size 32m;
client_max_body_size 32m;
##
# `gzip` Settings
#
#
gzip on;
gzip_disable "msie6";
##
# `gzip` Settings
#
#
gzip on;
gzip_disable "msie6";
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_min_length 256;
gzip_types
application/atom+xml
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_min_length 256;
gzip_types
application/atom+xml
application/geo+json
application/javascript
application/x-javascript
application/json
application/ld+json
application/manifest+json
application/rdf+xml
application/rss+xml
application/xhtml+xml
application/xml
font/eot
font/otf
font/ttf
image/svg+xml
text/css
text/javascript
text/plain
text/xml;
application/x-javascript
application/json
application/ld+json
application/manifest+json
application/rdf+xml
application/rss+xml
application/xhtml+xml
application/xml
font/eot
font/otf
font/ttf
image/svg+xml
text/css
text/javascript
text/plain
text/xml;
location / {
root /frontend;
index index.html;
expires -1;
try_files $uri $uri/ $uri/index.html;
}
}
location / {
root /frontend;
index index.html;
try_files $uri @index;
#Application config
location = /src/resources/app-config.json {
add_header Cache-Control "no-cache";
expires 0;
}
# Media: images, icons, video, audio, HTC
location ~* \.(?:jpg|jpeg|gif|png|ico|cur|gz|svg|svgz|mp4|mp3|ogg|ogv|webm|htc|woff2|woff|ttf)$ {
expires 1M;
access_log off;
# max-age must be in seconds
add_header Cache-Control "max-age=2629746, public";
}
# CSS and Javascript
location ~* \.(?:css|js)$ {
expires 1y;
access_log off;
add_header Cache-Control "max-age=31556952, public";
}
}
location @index {
root /frontend;
add_header Cache-Control "no-cache";
expires 0;
try_files /index.html =404;
}
location = /health {
access_log off;
add_header 'Content-Type' 'application/json';
return 200 '{"status":"UP"}';
}
}
}

View file

@ -4,7 +4,7 @@
<parent>
<groupId>ru.micord.ervu.lkrp</groupId>
<artifactId>fl</artifactId>
<version>1.9.2</version>
<version>1.9.3-SNAPSHOT</version>
</parent>
<groupId>ru.micord.ervu.lkrp.fl</groupId>

View file

@ -4,7 +4,7 @@
<parent>
<groupId>ru.micord.ervu.lkrp</groupId>
<artifactId>fl</artifactId>
<version>1.9.2</version>
<version>1.9.3-SNAPSHOT</version>
</parent>
<groupId>ru.micord.ervu.lkrp.fl</groupId>

View file

@ -4,7 +4,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>ru.micord.ervu.lkrp</groupId>
<artifactId>fl</artifactId>
<version>1.9.2</version>
<version>1.9.3-SNAPSHOT</version>
<packaging>pom</packaging>
<modules>
<module>backend</module>

View file

@ -4,7 +4,7 @@
<parent>
<groupId>ru.micord.ervu.lkrp</groupId>
<artifactId>fl</artifactId>
<version>1.9.2</version>
<version>1.9.3-SNAPSHOT</version>
</parent>
<groupId>ru.micord.ervu.lkrp.fl</groupId>