Merge branch 'master' into develop

# Conflicts:
#	backend/pom.xml
#	distribution/pom.xml
#	frontend/pom.xml
#	pom.xml
#	resources/pom.xml
This commit is contained in:
Zaripov Emil 2024-10-25 12:03:50 +03:00
commit 7c7cb3d219
38 changed files with 980 additions and 328 deletions

View file

@ -5,12 +5,16 @@
<parent> <parent>
<groupId>ru.micord.ervu.lkrp</groupId> <groupId>ru.micord.ervu.lkrp</groupId>
<artifactId>ul</artifactId> <artifactId>ul</artifactId>
<version>1.9.0-SNAPSHOT</version> <version>1.8.2</version>
</parent> </parent>
<groupId>ru.micord.ervu.lkrp.ul</groupId> <groupId>ru.micord.ervu.lkrp.ul</groupId>
<artifactId>backend</artifactId> <artifactId>backend</artifactId>
<packaging>war</packaging> <packaging>war</packaging>
<dependencies> <dependencies>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-jwt</artifactId>
</dependency>
<dependency> <dependency>
<groupId>io.jsonwebtoken</groupId> <groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId> <artifactId>jjwt-api</artifactId>
@ -29,10 +33,23 @@
<artifactId>resources</artifactId> <artifactId>resources</artifactId>
<scope>runtime</scope> <scope>runtime</scope>
</dependency> </dependency>
<dependency>
<groupId>ru.cg.webbpm.modules.reporting.reporting-jasper</groupId>
<artifactId>reporting-jasper-fonts</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.ocpsoft.prettytime</groupId>
<artifactId>prettytime</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.jooq</groupId> <groupId>org.jooq</groupId>
<artifactId>jooq</artifactId> <artifactId>jooq</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.apache.santuario</groupId>
<artifactId>xmlsec</artifactId>
</dependency>
<dependency> <dependency>
<groupId>javax.servlet</groupId> <groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId> <artifactId>javax.servlet-api</artifactId>
@ -122,10 +139,6 @@
<artifactId>database-impl</artifactId> <artifactId>database-impl</artifactId>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
</dependency>
<dependency> <dependency>
<groupId>ru.cg.webbpm.modules.jndi</groupId> <groupId>ru.cg.webbpm.modules.jndi</groupId>
<artifactId>jndi-beans</artifactId> <artifactId>jndi-beans</artifactId>
@ -134,10 +147,51 @@
<groupId>ru.cg.webbpm.modules.jndi</groupId> <groupId>ru.cg.webbpm.modules.jndi</groupId>
<artifactId>jndi-inject</artifactId> <artifactId>jndi-inject</artifactId>
</dependency> </dependency>
<dependency>
<groupId>com.sun.mail</groupId>
<artifactId>javax.mail</artifactId>
</dependency>
<dependency>
<groupId>ru.cg.webbpm.modules.database</groupId>
<artifactId>database-test</artifactId>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>ru.cg.webbpm.modules</groupId> <groupId>ru.cg.webbpm.modules</groupId>
<artifactId>standard-annotations</artifactId> <artifactId>standard-annotations</artifactId>
</dependency> </dependency>
<dependency>
<groupId>ru.cg.webbpm.modules.core</groupId>
<artifactId>metrics</artifactId>
</dependency>
<dependency>
<groupId>ru.cg.webbpm.modules.reporting</groupId>
<artifactId>reporting-api</artifactId>
</dependency>
<dependency>
<groupId>ru.cg.webbpm.modules.reporting</groupId>
<artifactId>reporting-runtime-api</artifactId>
</dependency>
<dependency>
<groupId>ru.cg.webbpm.modules.reporting</groupId>
<artifactId>reporting-runtime-impl</artifactId>
</dependency>
<dependency>
<groupId>ru.cg.webbpm.modules.reporting.reporting-jasper</groupId>
<artifactId>reporting-jasper-impl</artifactId>
</dependency>
<dependency>
<groupId>ru.cg.webbpm.modules.reporting.reporting-jasper</groupId>
<artifactId>reporting-jasper-runtime-impl</artifactId>
</dependency>
<dependency>
<groupId>ru.cg.webbpm.modules.reporting.reporting-xdoc</groupId>
<artifactId>reporting-xdoc-impl</artifactId>
</dependency>
<dependency>
<groupId>ru.cg.webbpm.modules.reporting.reporting-xdoc</groupId>
<artifactId>reporting-xdoc-runtime-impl</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.liquibase</groupId> <groupId>org.liquibase</groupId>
<artifactId>liquibase-core</artifactId> <artifactId>liquibase-core</artifactId>
@ -146,6 +200,22 @@
<groupId>ru.cg.webbpm.modules</groupId> <groupId>ru.cg.webbpm.modules</groupId>
<artifactId>webkit-base</artifactId> <artifactId>webkit-base</artifactId>
</dependency> </dependency>
<dependency>
<groupId>xerces</groupId>
<artifactId>xercesImpl</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>ru.micord.fias</groupId>
<artifactId>client</artifactId>
</dependency>
<dependency>
<groupId>org.apache.tika</groupId>
<artifactId>tika-core</artifactId>
</dependency>
<dependency> <dependency>
<groupId>net.javacrumbs.shedlock</groupId> <groupId>net.javacrumbs.shedlock</groupId>
<artifactId>shedlock-spring</artifactId> <artifactId>shedlock-spring</artifactId>
@ -174,10 +244,6 @@
<groupId>org.apache.logging.log4j</groupId> <groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-web</artifactId> <artifactId>log4j-web</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
</dependency>
<dependency> <dependency>
<groupId>com.amazonaws</groupId> <groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-s3</artifactId> <artifactId>aws-java-sdk-s3</artifactId>

View file

@ -34,20 +34,17 @@ public class EmployeeInfoFileUploadController {
Cookie[] cookies = request.getCookies(); Cookie[] cookies = request.getCookies();
if (cookies != null) { if (cookies != null) {
for (Cookie cookie : cookies) { for (Cookie cookie : cookies) {
if (cookie.getName().equals("access_token")) { if (cookie.getName().equals("auth_token")) {
accessToken = cookie.getValue();
}
else if (cookie.getName().equals("auth_token")) {
authToken = cookie.getValue(); authToken = cookie.getValue();
} }
} }
} }
if (accessToken != null) { if (authToken != null) {
String offset = ZonedDateTime.now(TimeZone.getTimeZone(clientTimeZone).toZoneId()) String offset = ZonedDateTime.now(TimeZone.getTimeZone(clientTimeZone).toZoneId())
.getOffset().getId(); .getOffset().getId();
if (this.fileUploadService.saveEmployeeInformationFile(multipartFile, formType, accessToken, if (this.fileUploadService.saveEmployeeInformationFile(multipartFile, formType,
authToken, offset)) { authToken, offset)) {
return ResponseEntity.ok("File successfully uploaded."); return ResponseEntity.ok("File successfully uploaded.");
} }

View file

@ -24,6 +24,7 @@ import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.kafka.core.KafkaTemplate; import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import ru.micord.ervu.security.esia.token.TokensStore;
import ru.micord.ervu.security.esia.model.EmployeeModel; import ru.micord.ervu.security.esia.model.EmployeeModel;
import ru.micord.ervu.security.esia.model.PersonModel; import ru.micord.ervu.security.esia.model.PersonModel;
import ru.micord.ervu.security.esia.service.UlDataService; import ru.micord.ervu.security.esia.service.UlDataService;
@ -72,8 +73,7 @@ public class EmployeeInfoFileUploadService {
this.jwtTokenService = jwtTokenService; this.jwtTokenService = jwtTokenService;
} }
public boolean saveEmployeeInformationFile(MultipartFile multipartFile, String formType, public boolean saveEmployeeInformationFile(MultipartFile multipartFile, String formType, String authToken, String offset) {
String accessToken, String authToken, String offset) {
String fileUploadUrl = this.url + "/" + getNewFilename(multipartFile.getOriginalFilename()); String fileUploadUrl = this.url + "/" + getNewFilename(multipartFile.getOriginalFilename());
LocalDateTime now = LocalDateTime.now(); LocalDateTime now = LocalDateTime.now();
@ -85,10 +85,13 @@ public class EmployeeInfoFileUploadService {
String fileId = UUID.randomUUID().toString(); String fileId = UUID.randomUUID().toString();
String fileName = multipartFile.getOriginalFilename(); String fileName = multipartFile.getOriginalFilename();
EmployeeInfoFileFormType employeeInfoFileFormType = EmployeeInfoFileFormType.valueOf(formType); EmployeeInfoFileFormType employeeInfoFileFormType = EmployeeInfoFileFormType.valueOf(formType);
EmployeeModel employeeModel = ulDataService.getEmployeeModel(accessToken);
PersonModel personModel = employeeModel.getPerson();
Token token = jwtTokenService.getToken(authToken); Token token = jwtTokenService.getToken(authToken);
String[] ids = token.getUserAccountId().split(":"); String[] ids = token.getUserAccountId().split(":");
String userId = ids[0];
String ervuId = ids[1];
String accessToken = TokensStore.getAccessToken(userId);
EmployeeModel employeeModel = ulDataService.getEmployeeModel(accessToken);
PersonModel personModel = employeeModel.getPerson();
String departureDateTime = now.format(DateTimeFormatter.ofPattern(FORMAT)); String departureDateTime = now.format(DateTimeFormatter.ofPattern(FORMAT));
String jsonMessage = getJsonKafkaMessage( String jsonMessage = getJsonKafkaMessage(
employeeInfoKafkaMessageService.getKafkaMessage( employeeInfoKafkaMessageService.getKafkaMessage(
@ -100,15 +103,15 @@ public class EmployeeInfoFileUploadService {
accessToken, accessToken,
offset, offset,
fileStatus, fileStatus,
ids[1], ervuId,
ids[0], userId,
personModel personModel
) )
); );
interactionService.setStatus(fileId, fileStatus.getStatus(), fileName, interactionService.setStatus(fileId, fileStatus.getStatus(), fileName,
employeeInfoFileFormType.getFilePatternCode(), Timestamp.valueOf(now), employeeInfoFileFormType.getFilePatternCode(), Timestamp.valueOf(now),
convertToFio(personModel.getFirstName(), personModel.getMiddleName(), personModel.getLastName()), convertToFio(personModel.getFirstName(), personModel.getMiddleName(), personModel.getLastName()),
(int) multipartFile.getSize(), ids[1]); (int) multipartFile.getSize(), ervuId);
return sendMessage(jsonMessage); return sendMessage(jsonMessage);
} }
else { else {

View file

@ -0,0 +1,34 @@
package ru.micord.ervu.security;
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.security.web.csrf.CsrfTokenRepository;
import ru.micord.ervu.security.esia.service.EsiaAuthService;
public class LogoutSuccessHandler
implements org.springframework.security.web.authentication.logout.LogoutSuccessHandler {
private final CsrfTokenRepository csrfTokenRepository;
private final EsiaAuthService esiaAuthService;
public LogoutSuccessHandler(CsrfTokenRepository csrfTokenRepository,
EsiaAuthService esiaAuthService) {
this.csrfTokenRepository = csrfTokenRepository;
this.esiaAuthService = esiaAuthService;
}
@Override
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();
CsrfToken csrfToken = this.csrfTokenRepository.generateToken(request);
this.csrfTokenRepository.saveToken(csrfToken, request, response);
}
}

View file

@ -4,59 +4,105 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import ru.micord.ervu.security.webbpm.jwt.filter.JwtAuthenticationFilter; import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
import org.springframework.security.web.csrf.CsrfTokenRequestHandler;
import org.springframework.security.web.csrf.XorCsrfTokenRequestAttributeHandler;
import org.springframework.web.filter.RequestContextFilter;
import ru.micord.ervu.security.esia.service.EsiaAuthService;
import ru.micord.ervu.security.filter.FilterChainExceptionHandler;
import ru.micord.ervu.security.webbpm.jwt.JwtAuthenticationProvider;
import ru.micord.ervu.security.webbpm.jwt.JwtMatcher;
import ru.micord.ervu.security.webbpm.jwt.UnauthorizedEntryPoint; 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;
@Configuration @Configuration
@EnableWebSecurity @EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter { public class SecurityConfig {
private static final String[] PERMIT_ALL = new String[] {
"/version", "/esia/url", "/esia/auth", "esia/refresh"
};
@Autowired
private JwtAuthenticationFilter jwtAuthenticationFilter;
@Autowired
private EsiaAuthService esiaAuthService;
@Autowired
private FilterChainExceptionHandler filterChainExceptionHandler;
@Autowired
private JwtAuthenticationProvider jwtAuthenticationProvider;
@Autowired @Autowired
private JwtAuthenticationFilter jwtAuthenticationFilter; public void configureGlobal(AuthenticationManagerBuilder auth) {
auth.authenticationProvider(jwtAuthenticationProvider);
}
@Override @Bean
protected void configure(HttpSecurity http) throws Exception { public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
httpConfigure(http);
http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
http.addFilterBefore(new RequestContextFilter(), LogoutFilter.class);
http.addFilterAfter(filterChainExceptionHandler, RequestContextFilter.class);
return http.build();
}
httpConfigure(http); protected void httpConfigure(HttpSecurity httpSecurity) throws Exception {
http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); CookieCsrfTokenRepository tokenRepository = CookieCsrfTokenRepository.withHttpOnlyFalse();
} tokenRepository.setCookiePath("/");
XorCsrfTokenRequestAttributeHandler delegate = new XorCsrfTokenRequestAttributeHandler();
delegate.setCsrfRequestAttributeName(null);
// Use only the handle() method of XorCsrfTokenRequestAttributeHandler and the
// default implementation of resolveCsrfTokenValue() from CsrfTokenRequestHandler
CsrfTokenRequestHandler requestHandler = delegate::handle;
httpSecurity.authorizeHttpRequests(
(authorizeHttpRequests) -> authorizeHttpRequests.requestMatchers(PERMIT_ALL)
.permitAll()
.anyRequest()
.authenticated())
.csrf((csrf) -> csrf.csrfTokenRepository(tokenRepository)
.csrfTokenRequestHandler(requestHandler))
.logout((logout) -> logout.logoutUrl(ESIA_LOGOUT)
.logoutSuccessHandler(new LogoutSuccessHandler(tokenRepository, esiaAuthService)))
.exceptionHandling()
.authenticationEntryPoint(entryPoint())
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
protected void httpConfigure(HttpSecurity httpSecurity) throws Exception { public AuthenticationEntryPoint entryPoint() {
String[] permitAll = {"/version", "/esia/url", "/esia/auth", "esia/refresh"}; return new UnauthorizedEntryPoint();
}
httpSecurity.authorizeRequests() @Bean
.antMatchers(permitAll).permitAll() AuthenticationManager authenticationManager(
.antMatchers("/**").authenticated() AuthenticationConfiguration authenticationConfiguration) throws Exception {
.and() return authenticationConfiguration.getAuthenticationManager();
.csrf().disable() }
.exceptionHandling().authenticationEntryPoint(entryPoint())
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
public AuthenticationEntryPoint entryPoint() { @Bean
return new UnauthorizedEntryPoint(); public SecurityHelper securityHelper() {
} return new SecurityHelper();
}
@Bean @Bean
@Override public JwtAuthenticationFilter jwtAuthenticationFilter(SecurityHelper securityHelper,
public AuthenticationManager authenticationManagerBean() throws Exception { AuthenticationManager manager,
return super.authenticationManagerBean(); JwtTokenService jwtTokenService) {
} JwtAuthenticationFilter jwtAuthenticationFilter = new JwtAuthenticationFilter(
new JwtMatcher("/**", PERMIT_ALL), entryPoint(), securityHelper, jwtTokenService);
@Bean jwtAuthenticationFilter.setAuthenticationManager(manager);
public JwtAuthenticationFilter jwtAuthenticationFilter() throws Exception { return jwtAuthenticationFilter;
JwtAuthenticationFilter jwtAuthenticationFilter = new JwtAuthenticationFilter("/**", }
entryPoint()
);
jwtAuthenticationFilter.setAuthenticationManager(authenticationManagerBean());
return jwtAuthenticationFilter;
}
} }

View file

@ -0,0 +1,5 @@
package ru.micord.ervu.security;
public class SecurityConstants {
public static final String ESIA_LOGOUT = "/esia/logout";
}

View file

@ -3,13 +3,14 @@ package ru.micord.ervu.security.esia.controller;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import ru.micord.ervu.security.SecurityConstants;
import ru.micord.ervu.security.esia.model.OrgInfoModel; import ru.micord.ervu.security.esia.model.OrgInfoModel;
import ru.micord.ervu.security.esia.service.EsiaAuthService; import ru.micord.ervu.security.esia.service.EsiaAuthService;
import ru.micord.ervu.security.esia.service.EsiaDataService; import ru.micord.ervu.security.esia.service.EsiaDataService;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
@ -25,38 +26,33 @@ public class EsiaController {
@Autowired @Autowired
private EsiaDataService esiaDataService; private EsiaDataService esiaDataService;
@RequestMapping(value = "/esia/url") @GetMapping(value = "/esia/url")
public String getEsiaUrl() { public String getEsiaUrl() {
return esiaAuthService.generateAuthCodeUrl(); return esiaAuthService.generateAuthCodeUrl();
} }
@RequestMapping(value = "/esia/auth", params = "code", method = RequestMethod.GET) @GetMapping(value = "/esia/auth", params = "code")
public ResponseEntity<?> esiaAuth(@RequestParam("code") String code, HttpServletRequest request, HttpServletResponse response) { public ResponseEntity<?> esiaAuth(@RequestParam("code") String code, HttpServletRequest request, HttpServletResponse response) {
return esiaAuthService.getEsiaTokensByCode(code, request, response); return esiaAuthService.getEsiaTokensByCode(code, request, response);
} }
@RequestMapping(value = "/esia/refresh") @PostMapping(value = "/esia/refresh")
public void refreshToken(HttpServletRequest request, HttpServletResponse response) { public void refreshToken(HttpServletRequest request, HttpServletResponse response) {
esiaAuthService.getEsiaTokensByRefreshToken(request, response); esiaAuthService.getEsiaTokensByRefreshToken(request, response);
} }
@RequestMapping(value = "/esia/org") @GetMapping(value = "/esia/org")
public OrgInfoModel getOrgInfo(HttpServletRequest request) { public OrgInfoModel getOrgInfo(HttpServletRequest request) {
return esiaDataService.getOrgInfo(request); return esiaDataService.getOrgInfo(request);
} }
@RequestMapping(value = "/esia/userfullname") @GetMapping(value = "/esia/userfullname")
public String getUserFullname(HttpServletRequest request) { public String getUserFullname(HttpServletRequest request) {
return esiaDataService.getUserFullname(request); return esiaDataService.getUserFullname(request);
} }
@RequestMapping(value = "/esia/orgunitname") @GetMapping(value = "/esia/orgunitname")
public String getOrgUnitName(HttpServletRequest request) { public String getOrgUnitName(HttpServletRequest request) {
return esiaDataService.getOrgUnitName(request); return esiaDataService.getOrgUnitName(request);
} }
@RequestMapping(value = "/esia/logout")
public String logout(HttpServletRequest request, HttpServletResponse response) {
return esiaAuthService.logout(request, response);
}
} }

View file

@ -22,6 +22,7 @@ import javax.servlet.http.HttpServletResponse;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import ervu.service.okopf.OkopfService; import ervu.service.okopf.OkopfService;
import ru.micord.ervu.security.esia.token.TokensStore;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
@ -46,37 +47,31 @@ import ru.micord.ervu.security.esia.model.EsiaTokenResponse;
import ru.micord.ervu.security.esia.model.FormUrlencoded; import ru.micord.ervu.security.esia.model.FormUrlencoded;
import ru.micord.ervu.security.esia.model.OrganizationModel; import ru.micord.ervu.security.esia.model.OrganizationModel;
import ru.micord.ervu.security.webbpm.jwt.JwtAuthentication; import ru.micord.ervu.security.webbpm.jwt.JwtAuthentication;
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 ru.micord.ervu.security.webbpm.jwt.util.SecurityUtil;
/** /**
* @author Eduard Tihomirov * @author Eduard Tihomirov
*/ */
@Service @Service
public class EsiaAuthService { public class EsiaAuthService {
private static final Logger LOGGER = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); private static final Logger LOGGER = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
@Value("${cookie-path:#{null}}")
private String path;
@Autowired @Autowired
private ObjectMapper objectMapper; private ObjectMapper objectMapper;
@Autowired @Autowired
private EsiaConfig esiaConfig; private EsiaConfig esiaConfig;
@Autowired @Autowired
private UlDataService ulDataService; private UlDataService ulDataService;
@Autowired @Autowired
private JwtTokenService jwtTokenService; private JwtTokenService jwtTokenService;
@Autowired @Autowired
private ReplyingKafkaService replyingKafkaService; private ReplyingKafkaService replyingKafkaService;
@Autowired @Autowired
private OkopfService okopfService; private OkopfService okopfService;
@Autowired
private SecurityHelper securityHelper;
@Value("${ervu.kafka.org.reply.topic}") @Value("${ervu.kafka.org.reply.topic}")
private String requestReplyTopic; private String requestReplyTopic;
@ -205,44 +200,31 @@ public class EsiaAuthService {
.build() .build()
.send(postReq, HttpResponse.BodyHandlers.ofString()); .send(postReq, HttpResponse.BodyHandlers.ofString());
String responseString = postResp.body(); String responseString = postResp.body();
EsiaTokenResponse tokenResponse = objectMapper.readValue(responseString, EsiaTokenResponse.class); EsiaTokenResponse tokenResponse = objectMapper.readValue(responseString,
if (tokenResponse != null && tokenResponse.getError() != null) { EsiaTokenResponse.class
);
if (tokenResponse == null) {
throw new IllegalStateException("Got empty esia response");
}
if (tokenResponse.getError() != null) {
throw new RuntimeException(tokenResponse.getError_description()); throw new RuntimeException(tokenResponse.getError_description());
} }
String accessToken = tokenResponse.getAccess_token(); String accessToken = tokenResponse.getAccess_token();
boolean hasRole = ulDataService.checkRole(accessToken); boolean hasRole = ulDataService.checkRole(accessToken);
EsiaAccessToken esiaAccessToken = ulDataService.readToken(accessToken); EsiaAccessToken esiaAccessToken = ulDataService.readToken(accessToken);
String prnOid = esiaAccessToken.getSbj_id(); String prnOid = esiaAccessToken.getSbj_id();
if (!hasRole) {
LOGGER.error("The user with id = " + prnOid + " does not have the required role");
return new ResponseEntity<>(
"Доступ запрещен. Пользователь должен быть включен в группу \"Сотрудник, ответственный за военно-учетную работу\" в ЕСИА",
HttpStatus.FORBIDDEN
);
}
String cookiePath = null;
if (path != null) {
cookiePath = path;
}
else {
cookiePath = request.getContextPath();
}
Cookie cookie = new Cookie("access_token", accessToken);
cookie.setHttpOnly(true);
cookie.setPath(cookiePath);
response.addCookie(cookie);
String refreshToken = tokenResponse.getRefresh_token(); String refreshToken = tokenResponse.getRefresh_token();
Cookie cookieRefresh = new Cookie("refresh_token", refreshToken); String ervuId = getErvuId(accessToken, prnOid);
cookieRefresh.setHttpOnly(true); Long expiresIn = tokenResponse.getExpires_in();
cookieRefresh.setPath(cookiePath); TokensStore.addAccessToken(prnOid, accessToken, expiresIn);
response.addCookie(cookieRefresh); TokensStore.addRefreshToken(prnOid, refreshToken, expiresIn);
String ervuId = getErvuId(accessToken, esiaAccessToken.getSbj_id()); Token token = jwtTokenService.createAccessToken(esiaAccessToken.getSbj_id(), expiresIn, ervuId, hasRole);
Token token = jwtTokenService.createAccessToken(esiaAccessToken.getSbj_id(), tokenResponse.getExpires_in(), ervuId); int expiry = tokenResponse.getExpires_in().intValue();
Cookie authToken = new Cookie("auth_token", token.getValue()); Cookie accessCookie = securityHelper.createAccessCookie(token.getValue(), expiry);
authToken.setPath(cookiePath); response.addCookie(accessCookie);
authToken.setHttpOnly(true);
response.addCookie(authToken);
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
new UsernamePasswordAuthenticationToken(token.getUserAccountId(), null); new UsernamePasswordAuthenticationToken(token.getUserAccountId(), null);
SecurityContext context = SecurityContextHolder.createEmptyContext(); SecurityContext context = SecurityContextHolder.createEmptyContext();
@ -250,11 +232,15 @@ public class EsiaAuthService {
esiaAccessToken.getSbj_id(), token.getValue()); esiaAccessToken.getSbj_id(), token.getValue());
context.setAuthentication(authentication); context.setAuthentication(authentication);
SecurityContextHolder.setContext(context); SecurityContextHolder.setContext(context);
Cookie authMarkerCookie = securityHelper.createAuthMarkerCookie("true", expiry);
Cookie isAuth = new Cookie("webbpm.ervu-lkrp-ul", "true"); response.addCookie(authMarkerCookie);
isAuth.setMaxAge(tokenResponse.getExpires_in().intValue()); if (!hasRole) {
isAuth.setPath("/"); LOGGER.error("The user with id = " + prnOid + " does not have the required role");
response.addCookie(isAuth); return new ResponseEntity<>(
"Доступ запрещен. Пользователь должен быть включен в группу \"Сотрудник, ответственный за военно-учетную работу\" в ЕСИА",
HttpStatus.FORBIDDEN
);
}
return ResponseEntity.ok("Authentication successful"); return ResponseEntity.ok("Authentication successful");
} }
catch (Exception e) { catch (Exception e) {
@ -264,15 +250,7 @@ public class EsiaAuthService {
public void getEsiaTokensByRefreshToken(HttpServletRequest request, HttpServletResponse response) { public void getEsiaTokensByRefreshToken(HttpServletRequest request, HttpServletResponse response) {
try { try {
String refreshToken = null; String refreshToken = jwtTokenService.getRefreshToken(request);
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
if (cookie.getName().equals("refresh_token")) {
refreshToken = cookie.getValue();
}
}
}
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();
@ -321,30 +299,17 @@ public class EsiaAuthService {
throw new RuntimeException(tokenResponse.getError_description()); throw new RuntimeException(tokenResponse.getError_description());
} }
String accessToken = tokenResponse.getAccess_token(); String accessToken = tokenResponse.getAccess_token();
Cookie cookie = new Cookie("access_token", accessToken);
cookie.setHttpOnly(true);
String cookiePath = null;
if (path != null) {
cookiePath = path;
}
else {
cookiePath = request.getContextPath();
}
cookie.setPath(cookiePath);
response.addCookie(cookie);
String newRefreshToken = tokenResponse.getRefresh_token(); String newRefreshToken = tokenResponse.getRefresh_token();
Cookie cookieRefresh = new Cookie("refresh_token", newRefreshToken);
cookieRefresh.setHttpOnly(true);
cookieRefresh.setPath(cookiePath);
response.addCookie(cookieRefresh);
EsiaAccessToken esiaAccessToken = ulDataService.readToken(accessToken); EsiaAccessToken esiaAccessToken = ulDataService.readToken(accessToken);
String ervuId = getErvuId(accessToken, esiaAccessToken.getSbj_id()); String prnOid = esiaAccessToken.getSbj_id();
Token token = jwtTokenService.createAccessToken(esiaAccessToken.getSbj_id(), tokenResponse.getExpires_in(), ervuId); Long expiresIn = tokenResponse.getExpires_in();
Cookie authToken = new Cookie("auth_token", token.getValue()); TokensStore.addAccessToken(prnOid, accessToken, expiresIn);
authToken.setPath(cookiePath); TokensStore.addRefreshToken(prnOid, newRefreshToken, expiresIn);
authToken.setHttpOnly(true); String ervuId = getErvuId(accessToken, prnOid);
response.addCookie(authToken); 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);
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
new UsernamePasswordAuthenticationToken(token.getUserAccountId(), null); new UsernamePasswordAuthenticationToken(token.getUserAccountId(), null);
SecurityContext context = SecurityContextHolder.createEmptyContext(); SecurityContext context = SecurityContextHolder.createEmptyContext();
@ -352,11 +317,8 @@ public class EsiaAuthService {
esiaAccessToken.getSbj_id(), token.getValue()); esiaAccessToken.getSbj_id(), token.getValue());
context.setAuthentication(authentication); context.setAuthentication(authentication);
SecurityContextHolder.setContext(context); SecurityContextHolder.setContext(context);
Cookie authMarkerCookie = securityHelper.createAuthMarkerCookie("true", expiry);
Cookie isAuth = new Cookie("webbpm.ervu-lkrp-ul", "true"); response.addCookie(authMarkerCookie);
isAuth.setMaxAge(tokenResponse.getExpires_in().intValue());
isAuth.setPath("/");
response.addCookie(isAuth);
} }
catch (Exception e) { catch (Exception e) {
throw new RuntimeException(e); throw new RuntimeException(e);
@ -397,23 +359,10 @@ public class EsiaAuthService {
public String logout(HttpServletRequest request, HttpServletResponse response) { public String logout(HttpServletRequest request, HttpServletResponse response) {
try { try {
Cookie[] cookies = request.getCookies(); securityHelper.clearAccessCookies(response);
if (cookies != null) String userId = jwtTokenService.getUserAccountId(request);
for (Cookie cookie : cookies) { TokensStore.removeAccessToken(userId);
if (cookie.getName().equals("webbpm.ervu-lkrp-ul")) { TokensStore.removeRefreshToken(userId);
cookie.setValue("");
cookie.setPath("/");
cookie.setMaxAge(0);
response.addCookie(cookie);
}
else if (cookie.getName().equals("auth_token") || cookie.getName().equals("refresh_token")
|| cookie.getName().equals("access_token")) {
cookie.setValue("");
cookie.setPath(cookie.getPath());
cookie.setMaxAge(0);
response.addCookie(cookie);
}
}
String logoutUrl = esiaConfig.getEsiaBaseUri() + esiaConfig.getEsiaLogoutUrl(); String logoutUrl = esiaConfig.getEsiaBaseUri() + esiaConfig.getEsiaLogoutUrl();
String redirectUrl = esiaConfig.getRedirectUrl(); String redirectUrl = esiaConfig.getRedirectUrl();
URL url = new URL(logoutUrl); URL url = new URL(logoutUrl);

View file

@ -1,12 +1,18 @@
package ru.micord.ervu.security.esia.service; package ru.micord.ervu.security.esia.service;
import java.util.Arrays; import java.util.Arrays;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import ru.micord.ervu.security.esia.model.*; import ru.micord.ervu.security.esia.model.Addresses;
import ru.micord.ervu.security.esia.model.Contacts;
import ru.micord.ervu.security.esia.model.EmployeeModel;
import ru.micord.ervu.security.esia.model.EsiaAccessToken;
import ru.micord.ervu.security.esia.model.OrgInfoModel;
import ru.micord.ervu.security.esia.model.OrganizationModel;
import ru.micord.ervu.security.esia.model.PersonModel;
import ru.micord.ervu.security.webbpm.jwt.service.JwtTokenService;
/** /**
* @author Eduard Tihomirov * @author Eduard Tihomirov
@ -17,8 +23,11 @@ public class EsiaDataService {
@Autowired @Autowired
private UlDataService ulDataService; private UlDataService ulDataService;
@Autowired
private JwtTokenService jwtTokenService;
public OrgInfoModel getOrgInfo(HttpServletRequest request) { public OrgInfoModel getOrgInfo(HttpServletRequest request) {
String accessToken = getAccessToken(request); String accessToken = jwtTokenService.getAccessToken(request);
if (accessToken == null) { if (accessToken == null) {
return null; return null;
} }
@ -69,7 +78,7 @@ public class EsiaDataService {
} }
public String getOrgUnitName(HttpServletRequest request) { public String getOrgUnitName(HttpServletRequest request) {
String accessToken = getAccessToken(request); String accessToken = jwtTokenService.getAccessToken(request);
if (accessToken == null) { if (accessToken == null) {
return null; return null;
} }
@ -78,7 +87,7 @@ public class EsiaDataService {
} }
public String getUserFullname(HttpServletRequest request) { public String getUserFullname(HttpServletRequest request) {
String accessToken = getAccessToken(request); String accessToken = jwtTokenService.getAccessToken(request);
if (accessToken == null) { if (accessToken == null) {
return null; return null;
} }
@ -86,16 +95,4 @@ public class EsiaDataService {
PersonModel personModel = ulDataService.getPersonData(esiaAccessToken.getSbj_id(), accessToken); PersonModel personModel = ulDataService.getPersonData(esiaAccessToken.getSbj_id(), accessToken);
return personModel.getLastName() + " " + personModel.getFirstName().charAt(0) + ". " + personModel.getMiddleName().charAt(0) + "."; return personModel.getLastName() + " " + personModel.getFirstName().charAt(0) + ". " + personModel.getMiddleName().charAt(0) + ".";
} }
private String getAccessToken(HttpServletRequest request) {
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
if (cookie.getName().equals("access_token")) {
return cookie.getValue();
}
}
}
return null;
}
} }

View file

@ -0,0 +1,34 @@
package ru.micord.ervu.security.esia.token;
/**
* @author Eduard Tihomirov
*/
public class ExpiringToken {
private String accessToken;
private long expiryTime;
public ExpiringToken(String accessToken, long expiryTime) {
this.accessToken = accessToken;
this.expiryTime = expiryTime;
}
public String getAccessToken() {
return accessToken;
}
public void setAccessToken(String accessToken) {
this.accessToken = accessToken;
}
public long getExpiryTime() {
return expiryTime;
}
public void setExpiryTime(long expiryTime) {
this.expiryTime = expiryTime;
}
boolean isExpired() {
return System.currentTimeMillis() > expiryTime;
}
}

View file

@ -0,0 +1,20 @@
package ru.micord.ervu.security.esia.token;
import net.javacrumbs.shedlock.core.SchedulerLock;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* @author Eduard Tihomirov
*/
@Service
public class TokensClearShedulerService {
@Scheduled(cron = "${esia.token.clear.cron:0 0 */1 * * *}")
@SchedulerLock(name = "clearToken")
@Transactional
public void load() {
TokensStore.removeExpiredRefreshToken();
TokensStore.removeExpiredAccessToken();
}
}

View file

@ -0,0 +1,61 @@
package ru.micord.ervu.security.esia.token;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author Eduard Tihomirov
*/
public class TokensStore {
private static final Map<String, ExpiringToken> accessTokensMap = new ConcurrentHashMap<>();
private static final Map<String, ExpiringToken> refreshTokensMap = new ConcurrentHashMap<>();
public static void addAccessToken(String prnOid, String token, long expiresIn) {
if (token != null) {
long expiryTime = System.currentTimeMillis() + 1000L * expiresIn;
accessTokensMap.put(prnOid, new ExpiringToken(token, expiryTime));
}
}
public static String getAccessToken(String prnOid) {
return accessTokensMap.get(prnOid).getAccessToken();
}
public static void removeExpiredAccessToken() {
for (String key : accessTokensMap.keySet()) {
ExpiringToken token = accessTokensMap.get(key);
if (token != null && token.isExpired()) {
accessTokensMap.remove(key);
}
}
}
public static void removeExpiredRefreshToken() {
for (String key : refreshTokensMap.keySet()) {
ExpiringToken token = refreshTokensMap.get(key);
if (token != null && token.isExpired()) {
refreshTokensMap.remove(key);
}
}
}
public static void removeAccessToken(String prnOid) {
accessTokensMap.remove(prnOid);
}
public static void addRefreshToken(String prnOid, String token, long expiresIn) {
if (token != null) {
long expiryTime = System.currentTimeMillis() + 1000L * expiresIn;
refreshTokensMap.put(prnOid, new ExpiringToken(token, expiryTime));
}
}
public static String getRefreshToken(String prnOid) {
return refreshTokensMap.get(prnOid).getAccessToken();
}
public static void removeRefreshToken(String prnOid) {
refreshTokensMap.remove(prnOid);
}
}

View file

@ -0,0 +1,29 @@
package ru.micord.ervu.security.filter;
import javax.servlet.FilterChain;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.servlet.HandlerExceptionResolver;
@Component
public class FilterChainExceptionHandler extends OncePerRequestFilter {
@Autowired
@Qualifier("handlerExceptionResolver")
private HandlerExceptionResolver resolver;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) {
try {
filterChain.doFilter(request, response);
}
catch (Exception e) {
resolver.resolveException(request, response, null, e);
}
}
}

View file

@ -1,5 +1,7 @@
package ru.micord.ervu.security.webbpm.jwt; package ru.micord.ervu.security.webbpm.jwt;
import java.util.Collections;
import io.jsonwebtoken.ExpiredJwtException; import io.jsonwebtoken.ExpiredJwtException;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.AuthenticationProvider;
@ -44,10 +46,12 @@ public class JwtAuthenticationProvider implements AuthenticationProvider {
throw new BadCredentialsException("Auth token is not valid for user " + token.getUserAccountId()); throw new BadCredentialsException("Auth token is not valid for user " + token.getUserAccountId());
} }
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = UsernamePasswordAuthenticationToken pwdToken =
new UsernamePasswordAuthenticationToken(token.getUserAccountId(), null); UsernamePasswordAuthenticationToken.authenticated(token.getUserAccountId(), null,
Collections.emptyList()
);
return new JwtAuthentication(usernamePasswordAuthenticationToken, token.getUserAccountId(), token.getValue()); return new JwtAuthentication(pwdToken, token.getUserAccountId(), token.getValue());
} }
@Override @Override

View file

@ -0,0 +1,33 @@
package ru.micord.ervu.security.webbpm.jwt;
import java.util.Arrays;
import java.util.Set;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletRequest;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import static ru.micord.ervu.security.webbpm.jwt.util.SecurityUtil.extractAuthToken;
public final class JwtMatcher implements RequestMatcher {
private final Set<AntPathRequestMatcher> excludedPathMatchers;
private final AntPathRequestMatcher securedMatcher;
public JwtMatcher(String securedPath, String... excludedPaths) {
this.securedMatcher = new AntPathRequestMatcher(securedPath);
this.excludedPathMatchers = Arrays.stream(excludedPaths)
.map(AntPathRequestMatcher::new)
.collect(Collectors.toSet());
}
@Override
public boolean matches(HttpServletRequest request) {
if (this.excludedPathMatchers.stream().anyMatch(matcher -> matcher.matches(request))) {
return false;
}
else {
return extractAuthToken(request) != null && this.securedMatcher.matches(request);
}
}
}

View file

@ -4,10 +4,10 @@ import java.io.IOException;
import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodHandles;
import javax.servlet.FilterChain; import javax.servlet.FilterChain;
import javax.servlet.ServletException; import javax.servlet.ServletException;
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;
import io.jsonwebtoken.ExpiredJwtException;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.CredentialsExpiredException; import org.springframework.security.authentication.CredentialsExpiredException;
@ -16,70 +16,80 @@ import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.RequestMatcher;
import ru.micord.ervu.security.webbpm.jwt.JwtAuthentication; 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;
import ru.micord.ervu.security.webbpm.jwt.service.JwtTokenService;
import static ru.micord.ervu.security.webbpm.jwt.util.SecurityUtil.extractAuthToken;
/** /**
* @author Flyur Karimov * @author Flyur Karimov
*/ */
public class JwtAuthenticationFilter extends AbstractAuthenticationProcessingFilter { public class JwtAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
private static final Logger LOGGER = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); private static final Logger LOGGER = LoggerFactory.getLogger(
MethodHandles.lookup().lookupClass());
private final AuthenticationEntryPoint entryPoint; private final AuthenticationEntryPoint entryPoint;
public JwtAuthenticationFilter(String securityPath, AuthenticationEntryPoint entryPoint) { private final SecurityHelper securityHelper;
super(securityPath);
private final JwtTokenService jwtTokenService;
public JwtAuthenticationFilter(RequestMatcher requestMatcher,
AuthenticationEntryPoint entryPoint,
SecurityHelper securityHelper,
JwtTokenService jwtTokenService) {
super(requestMatcher);
this.entryPoint = entryPoint; this.entryPoint = entryPoint;
this.securityHelper = securityHelper;
this.jwtTokenService = jwtTokenService;
} }
@Override @Override
public Authentication attemptAuthentication(HttpServletRequest httpServletRequest, public Authentication attemptAuthentication(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse) throws AuthenticationException { HttpServletResponse httpServletResponse)
String token = extractAuthTokenFromRequest(httpServletRequest); throws AuthenticationException {
String tokenStr = extractAuthToken(httpServletRequest);
Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null) { if (authentication == null) {
authentication = new JwtAuthentication(null, null, token); authentication = new JwtAuthentication(null, null, tokenStr);
} }
try { try {
authentication = getAuthenticationManager().authenticate(authentication); authentication = getAuthenticationManager().authenticate(authentication);
if (!httpServletRequest.getRequestURI().endsWith("esia/logout")) {
Token token = jwtTokenService.getToken(tokenStr);
if (!token.getHasRole()) {
throw new CredentialsExpiredException("Invalid token. User has no required role");
}
}
} }
catch (CredentialsExpiredException e) { catch (CredentialsExpiredException e) {
securityHelper.clearAccessCookies(httpServletResponse);
httpServletResponse.setStatus(401); httpServletResponse.setStatus(401);
LOGGER.warn(e.getMessage()); LOGGER.warn(e.getMessage());
return null;
} }
return authentication; return authentication;
} }
@Override
protected boolean requiresAuthentication(HttpServletRequest request, HttpServletResponse response) {
return extractAuthTokenFromRequest(request) != null;
}
@Override @Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response,
FilterChain chain, Authentication authentication) throws IOException, ServletException { FilterChain chain, Authentication authentication)
throws IOException, ServletException {
SecurityContextHolder.getContext().setAuthentication(authentication); SecurityContextHolder.getContext().setAuthentication(authentication);
chain.doFilter(request, response); chain.doFilter(request, response);
} }
@Override @Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, protected void unsuccessfulAuthentication(HttpServletRequest request,
AuthenticationException exception) throws IOException, ServletException { HttpServletResponse response,
AuthenticationException exception)
throws IOException, ServletException {
LOGGER.error("Jwt unsuccessful authentication exception", exception); LOGGER.error("Jwt unsuccessful authentication exception", exception);
SecurityContextHolder.clearContext(); SecurityContextHolder.clearContext();
entryPoint.commence(request, response, exception); entryPoint.commence(request, response, exception);
} }
public String extractAuthTokenFromRequest(HttpServletRequest httpRequest) {
String token = null;
Cookie[] cookies = httpRequest.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
if (cookie.getName().equals("auth_token")) {
token = cookie.getValue();
LOGGER.info("Token extracted from cookie: {}", token);
}
}
}
return token;
}
} }

View file

@ -0,0 +1,43 @@
package ru.micord.ervu.security.webbpm.jwt.helper;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Value;
import ru.micord.ervu.security.webbpm.jwt.util.SecurityUtil;
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;
public void clearAccessCookies(HttpServletResponse response) {
Cookie tokenCookie = createCookie(AUTH_TOKEN, null, null);
tokenCookie.setMaxAge(0);
tokenCookie.setPath(accessCookiePath);
tokenCookie.setHttpOnly(true);
response.addCookie(tokenCookie);
Cookie markerCookie = createCookie(AUTH_MARKER, null, null);
markerCookie.setMaxAge(0);
markerCookie.setPath("/");
response.addCookie(markerCookie);
}
public Cookie createAccessCookie(String cookieValue, int expiry) {
Cookie authToken = createCookie(SecurityUtil.AUTH_TOKEN, cookieValue, accessCookiePath);
authToken.setPath(accessCookiePath);
authToken.setMaxAge(expiry);
return authToken;
}
public Cookie createAuthMarkerCookie(String cookieValue, int expiry) {
Cookie marker = createCookie(AUTH_MARKER, cookieValue, "/");
marker.setMaxAge(expiry);
marker.setHttpOnly(false);
return marker;
}
}

View file

@ -7,12 +7,14 @@ public class Token {
private final String issuer; private final String issuer;
private final Date expirationDate; private final Date expirationDate;
private final String value; private final String value;
private final Boolean hasRole;
public Token(String userAccountId, String issuer, Date expirationDate, String value) { public Token(String userAccountId, String issuer, Date expirationDate, String value, Boolean hasRole) {
this.userAccountId = userAccountId; this.userAccountId = userAccountId;
this.issuer = issuer; this.issuer = issuer;
this.expirationDate = expirationDate; this.expirationDate = expirationDate;
this.value = value; this.value = value;
this.hasRole = hasRole;
} }
public String getUserAccountId() { public String getUserAccountId() {
@ -34,4 +36,8 @@ public class Token {
public String getValue() { public String getValue() {
return value; return value;
} }
public Boolean getHasRole() {
return hasRole;
}
} }

View file

@ -4,6 +4,7 @@ import java.lang.invoke.MethodHandles;
import java.util.Base64; import java.util.Base64;
import java.util.Date; import java.util.Date;
import javax.crypto.SecretKey; import javax.crypto.SecretKey;
import javax.servlet.http.HttpServletRequest;
import io.jsonwebtoken.Claims; import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts; import io.jsonwebtoken.Jwts;
@ -13,10 +14,13 @@ import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import ru.micord.ervu.security.esia.token.TokensStore;
import ru.micord.ervu.security.webbpm.jwt.model.Token; import ru.micord.ervu.security.webbpm.jwt.model.Token;
import ru.cg.webbpm.modules.resources.api.ResourceMetadataUtils; import ru.cg.webbpm.modules.resources.api.ResourceMetadataUtils;
import static ru.micord.ervu.security.webbpm.jwt.util.SecurityUtil.extractAuthToken;
/** /**
* @author Flyur Karimov * @author Flyur Karimov
*/ */
@ -38,7 +42,7 @@ public class JwtTokenService {
this.SIGNING_KEY = Keys.hmacShaKeyFor(encodedKey); this.SIGNING_KEY = Keys.hmacShaKeyFor(encodedKey);
} }
public Token createAccessToken(String userAccountId, Long expiresIn, String ervuId) { public Token createAccessToken(String userAccountId, Long expiresIn, String ervuId, Boolean hasRole) {
Date expirationDate = new Date(System.currentTimeMillis() + 1000L * expiresIn); Date expirationDate = new Date(System.currentTimeMillis() + 1000L * expiresIn);
String value = Jwts.builder() String value = Jwts.builder()
@ -46,9 +50,10 @@ public class JwtTokenService {
.setIssuer(tokenIssuerName) .setIssuer(tokenIssuerName)
.setIssuedAt(new Date(System.currentTimeMillis())) .setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(expirationDate) .setExpiration(expirationDate)
.claim("hasRole", hasRole)
.signWith(SIGNING_KEY) .signWith(SIGNING_KEY)
.compact(); .compact();
return new Token(userAccountId + ":" + ervuId, tokenIssuerName, expirationDate, value); return new Token(userAccountId + ":" + ervuId, tokenIssuerName, expirationDate, value, hasRole);
} }
public boolean isValid(Token token) { public boolean isValid(Token token) {
@ -70,6 +75,26 @@ public class JwtTokenService {
.parseClaimsJws(token) .parseClaimsJws(token)
.getBody(); .getBody();
return new Token(claims.getSubject(), claims.getIssuer(), claims.getExpiration(), token); return new Token(claims.getSubject(), claims.getIssuer(), claims.getExpiration(), token, claims.get("hasRole", Boolean.class));
}
public String getAccessToken(HttpServletRequest request) {
return TokensStore.getAccessToken(getUserAccountId(request));
}
public String getRefreshToken(HttpServletRequest request) {
return TokensStore.getRefreshToken(getUserAccountId(request));
}
public String getUserAccountId(HttpServletRequest request) {
String authToken = extractAuthToken(request);
if (authToken != null) {
String[] ids = getToken(authToken).getUserAccountId().split(":");
return ids[0];
}
else {
throw new RuntimeException("Failed to get auth data. User unauthorized.");
}
} }
} }

View file

@ -0,0 +1,45 @@
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";
public static final String AUTH_MARKER = "webbpm.ervu-lkrp-ul";
private 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;
}
}

View file

@ -51,3 +51,6 @@ ERVU_FILE_UPLOAD_FILE_SIZE_THRESHOLD=0
S3_ENDPOINT=http://ervu-minio.k8s.micord.ru:31900 S3_ENDPOINT=http://ervu-minio.k8s.micord.ru:31900
S3_ACCESS_KEY=rlTdTvkmSXu9FsLhfecw S3_ACCESS_KEY=rlTdTvkmSXu9FsLhfecw
S3_SECRET_KEY=NUmY0wwRIEyAd98GCKd1cOgJWvLQYAcMMul5Ulu0 S3_SECRET_KEY=NUmY0wwRIEyAd98GCKd1cOgJWvLQYAcMMul5Ulu0
ESIA_TOKEN_CLEAR_CRON=0 0 */1 * * *
COOKIE_PATH=/ul

View file

@ -94,6 +94,7 @@
<property name="s3.secret_key" value="NUmY0wwRIEyAd98GCKd1cOgJWvLQYAcMMul5Ulu0"/> <property name="s3.secret_key" value="NUmY0wwRIEyAd98GCKd1cOgJWvLQYAcMMul5Ulu0"/>
<property name="av.kafka.group.id" value="1"/> <property name="av.kafka.group.id" value="1"/>
<property name="av.kafka.download.response" value="ervu.lkrp.av-fileupload-status"/> <property name="av.kafka.download.response" value="ervu.lkrp.av-fileupload-status"/>
<property name="esia.token.clear.cron" value="0 0 */1 * * *"/>
<property name="esia.upload.data.role" value="MNSV89_UPLOAD_DATA"/> <property name="esia.upload.data.role" value="MNSV89_UPLOAD_DATA"/>
</system-properties> </system-properties>
<management> <management>

View file

@ -4,7 +4,7 @@
<parent> <parent>
<groupId>ru.micord.ervu.lkrp</groupId> <groupId>ru.micord.ervu.lkrp</groupId>
<artifactId>ul</artifactId> <artifactId>ul</artifactId>
<version>1.9.0-SNAPSHOT</version> <version>1.8.2</version>
</parent> </parent>
<groupId>ru.micord.ervu.lkrp.ul</groupId> <groupId>ru.micord.ervu.lkrp.ul</groupId>

View file

@ -4,7 +4,7 @@
<parent> <parent>
<groupId>ru.micord.ervu.lkrp</groupId> <groupId>ru.micord.ervu.lkrp</groupId>
<artifactId>ul</artifactId> <artifactId>ul</artifactId>
<version>1.9.0-SNAPSHOT</version> <version>1.8.2</version>
</parent> </parent>
<groupId>ru.micord.ervu.lkrp.ul</groupId> <groupId>ru.micord.ervu.lkrp.ul</groupId>

View file

@ -1,6 +1,7 @@
<button class="user-info" ngbDropdownToggle *ngIf="getIsAuth()">{{getUserFullname()}}</button> <button class="user-info" ngbDropdownToggle *ngIf="isAuthenticated()">{{getUserFullname()}}</button>
<div ngbDropdownMenu *ngIf="getIsAuth()"> <div ngbDropdownMenu *ngIf="isAuthenticated()">
<div class="user-department">{{getOrgUnitName()}}</div> <div class="user-department">{{getOrgUnitName()}}</div>
<a routerLink="/mydata" class="data">Данные организации</a> <a routerLink="/mydata" class="data">Данные организации</a>
<button ngbDropdownItem class="exit" (click)="logout()">Выйти</button> <button ngbDropdownItem class="exit" (click)="logout()">Выйти</button>
</div> </div>
<button class="exit" *ngIf="!isAuthenticated()" (click)="logout()">Выйти</button>

View file

@ -11,6 +11,8 @@ import {ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef} from
import {FileItem, FileUploader} from "ng2-file-upload"; import {FileItem, FileUploader} from "ng2-file-upload";
import {FileLikeObject} from "ng2-file-upload/file-upload/file-like-object.class"; import {FileLikeObject} from "ng2-file-upload/file-upload/file-like-object.class";
import {EmployeeInfoFileFormType} from "./EmployeeInfoFileFormType"; import {EmployeeInfoFileFormType} from "./EmployeeInfoFileFormType";
import {CookieService} from "ngx-cookie";
import {TokenConstants} from "../../../modules/security/TokenConstants";
@Component({ @Component({
moduleId: module.id, moduleId: module.id,
@ -59,6 +61,7 @@ export class ErvuFileUpload extends InputControl {
private messagesService: MessagesService; private messagesService: MessagesService;
private isUploadErrorOccurred = false; private isUploadErrorOccurred = false;
private appConfigService: AppConfigService; private appConfigService: AppConfigService;
private cookieService: CookieService;
constructor(el: ElementRef, cd: ChangeDetectorRef) { constructor(el: ElementRef, cd: ChangeDetectorRef) {
super(el, cd); super(el, cd);
@ -69,6 +72,7 @@ export class ErvuFileUpload extends InputControl {
super.initialize(); super.initialize();
this.messagesService = this.injector.get(MessagesService); this.messagesService = this.injector.get(MessagesService);
this.appConfigService = this.injector.get(AppConfigService); this.appConfigService = this.injector.get(AppConfigService);
this.cookieService = this.injector.get(CookieService);
this.url = `/${this.appConfigService.getParamValue(ErvuFileUpload.BACKEND_URL)}/employee/document`; this.url = `/${this.appConfigService.getParamValue(ErvuFileUpload.BACKEND_URL)}/employee/document`;
this.uploader.setOptions({ this.uploader.setOptions({
@ -99,6 +103,10 @@ export class ErvuFileUpload extends InputControl {
{ {
name: "Client-Time-Zone", name: "Client-Time-Zone",
value: Intl.DateTimeFormat().resolvedOptions().timeZone value: Intl.DateTimeFormat().resolvedOptions().timeZone
},
{
name: TokenConstants.CSRF_HEADER_NAME,
value: this.cookieService.get(TokenConstants.CSRF_TOKEN_NAME)
} }
] ]
}); });
@ -129,6 +137,24 @@ export class ErvuFileUpload extends InputControl {
private setUploaderMethods() { private setUploaderMethods() {
this.uploader.onBeforeUploadItem = (fileItem: FileItem) => { this.uploader.onBeforeUploadItem = (fileItem: FileItem) => {
//refresh headers
this.uploader.setOptions({
headers: [
{
name: "X-Employee-Info-File-Form-Type",
value: EmployeeInfoFileFormType[this.formType]
},
{
name: "Client-Time-Zone",
value: Intl.DateTimeFormat().resolvedOptions().timeZone
},
{
name: TokenConstants.CSRF_HEADER_NAME,
value: this.cookieService.get(TokenConstants.CSRF_TOKEN_NAME)
}
]
});
this.fileUploadStartEvent.trigger(); this.fileUploadStartEvent.trigger();
this.isDropZoneVisible = false; this.isDropZoneVisible = false;
this.isFilesListVisible = false; this.isFilesListVisible = false;
@ -239,4 +265,4 @@ export class ErvuFileUpload extends InputControl {
this.isUploadErrorOccurred = false; this.isUploadErrorOccurred = false;
this.cd.markForCheck(); this.cd.markForCheck();
} }
} }

View file

@ -2,10 +2,10 @@ import {AnalyticalScope, Behavior, Container, ControlWithValue} from "@webbpm/ba
import {HttpClient} from "@angular/common/http"; import {HttpClient} from "@angular/common/http";
import {OrgData} from "./OrgData"; import {OrgData} from "./OrgData";
import {OrgInfoModel} from "../generated/ru/micord/ervu/security/esia/model/OrgInfoModel"; import {OrgInfoModel} from "../generated/ru/micord/ervu/security/esia/model/OrgInfoModel";
import {CookieService} from "ngx-cookie"; import {AuthenticationService} from "../modules/security/authentication.service";
@AnalyticalScope(Container) @AnalyticalScope(Container)
export class OrgDataRoot extends Behavior{ export class OrgDataRoot extends Behavior {
private container: Container; private container: Container;
@ -14,8 +14,9 @@ export class OrgDataRoot extends Behavior{
this.container = this.getScript(Container); this.container = this.getScript(Container);
let orgScripts: OrgData[] = this.container.getScriptsInThisAndChildren(OrgData); let orgScripts: OrgData[] = this.container.getScriptsInThisAndChildren(OrgData);
let httpClient = this.injector.get(HttpClient); let httpClient = this.injector.get(HttpClient);
let cookieService = this.injector.get(CookieService); let cookieService = this.injector.get(AuthenticationService);
if (cookieService.get("webbpm.ervu-lkrp-ul")) {
if (cookieService.isAuthenticated()) {
httpClient.get<OrgInfoModel>("esia/org") httpClient.get<OrgInfoModel>("esia/org")
.toPromise() .toPromise()
.then(orgInfoModel => { .then(orgInfoModel => {
@ -23,9 +24,9 @@ export class OrgDataRoot extends Behavior{
return; return;
} }
for (let orgData of orgScripts) { for (let orgData of orgScripts) {
let control: ControlWithValue = orgData.getScriptInObject(orgData.getObjectId(), let control: ControlWithValue = orgData.getScriptInObject(orgData.getObjectId(),
'component.ControlWithValue'); 'component.ControlWithValue');
control.setValue(orgInfoModel[orgData.dataId]); control.setValue(orgInfoModel[orgData.dataId]);
} }
}); });
} }

View file

@ -1,4 +1,4 @@
import {forwardRef, NgModule} from "@angular/core"; import {APP_INITIALIZER, forwardRef, NgModule} from "@angular/core";
import {NgbModule} from "@ng-bootstrap/ng-bootstrap"; import {NgbModule} from "@ng-bootstrap/ng-bootstrap";
import {CommonModule, registerLocaleData} from "@angular/common"; import {CommonModule, registerLocaleData} from "@angular/common";
import localeRu from '@angular/common/locales/ru'; import localeRu from '@angular/common/locales/ru';
@ -25,6 +25,7 @@ import {FileUploadModule} from "ng2-file-upload";
import {ErvuFileUpload} from "../../ervu/component/fileupload/ErvuFileUpload"; import {ErvuFileUpload} from "../../ervu/component/fileupload/ErvuFileUpload";
import {InMemoryStaticGrid} from "../../ervu/component/grid/InMemoryStaticGrid"; import {InMemoryStaticGrid} from "../../ervu/component/grid/InMemoryStaticGrid";
import {ErvuDownloadFileButton} from "../../ervu/component/button/ErvuDownloadFileButton"; import {ErvuDownloadFileButton} from "../../ervu/component/button/ErvuDownloadFileButton";
import {AuthenticationService} from "../security/authentication.service";
registerLocaleData(localeRu); registerLocaleData(localeRu);
export const DIRECTIVES = [ export const DIRECTIVES = [
@ -39,6 +40,10 @@ export const DIRECTIVES = [
forwardRef(() => InMemoryStaticGrid) forwardRef(() => InMemoryStaticGrid)
]; ];
export function checkAuthentication(authService: AuthenticationService): () => Promise<any> {
return () => authService.checkAuthentication();
}
@NgModule({ @NgModule({
imports: [ imports: [
CommonModule, CommonModule,
@ -60,6 +65,13 @@ export const DIRECTIVES = [
DIRECTIVES DIRECTIVES
], ],
providers: [ providers: [
AuthenticationService,
{
provide: APP_INITIALIZER,
useFactory: checkAuthentication,
deps: [AuthenticationService],
multi: true,
},
{ provide: ProgressIndicationService, useClass: AppProgressIndicationService } { provide: ProgressIndicationService, useClass: AppProgressIndicationService }
], ],
bootstrap: [], bootstrap: [],

View file

@ -1,7 +1,7 @@
import {ChangeDetectorRef, Component, OnInit} from "@angular/core"; import {ChangeDetectorRef, Component, OnInit} from "@angular/core";
import {Router} from "@angular/router"; import {Router} from "@angular/router";
import {HttpClient} from "@angular/common/http"; import {HttpClient} from "@angular/common/http";
import {CookieService} from "ngx-cookie"; import {AuthenticationService} from "../../security/authentication.service";
@Component({ @Component({
moduleId: module.id, moduleId: module.id,
@ -13,13 +13,12 @@ export class LogOutComponent implements OnInit{
private userFullname: string; private userFullname: string;
private orgUnitName: string; private orgUnitName: string;
constructor(private router: Router, private httpClient: HttpClient, constructor(private router: Router, private httpClient: HttpClient,
private cookieService: CookieService, private cd: ChangeDetectorRef) { private authenticationService: AuthenticationService, private cd: ChangeDetectorRef) {
} }
ngOnInit(): void { ngOnInit(): void {
let isAuth = this.getIsAuth(); let isAuth = this.authenticationService.isAuthenticated();
if (isAuth) { if (isAuth) {
Promise.all([ Promise.all([
this.httpClient.get<string>("esia/userfullname").toPromise(), this.httpClient.get<string>("esia/userfullname").toPromise(),
@ -33,20 +32,18 @@ export class LogOutComponent implements OnInit{
} }
public logout(): void { public logout(): void {
this.httpClient.get<string>("esia/logout").toPromise().then(url => { this.authenticationService.logout();
window.open(url, "_self");
})
} }
public getUserFullname(): string { public getUserFullname(): string {
return this.userFullname; return this.userFullname;
} }
public getIsAuth(): boolean { public isAuthenticated(): boolean {
return this.cookieService.get("webbpm.ervu-lkrp-ul") != null; return this.authenticationService.isAuthenticated();
} }
public getOrgUnitName(): string { public getOrgUnitName(): string {
return this.orgUnitName; return this.orgUnitName;
} }
} }

View file

@ -0,0 +1,4 @@
export class TokenConstants {
public static readonly CSRF_TOKEN_NAME = "XSRF-TOKEN";
public static readonly CSRF_HEADER_NAME = "X-XSRF-TOKEN";
}

View file

@ -0,0 +1,29 @@
import {Injectable} from '@angular/core';
import {HttpClient} from '@angular/common/http';
import {Observable} from "rxjs";
import {CookieService} from "ngx-cookie";
import {tap} from "rxjs/operators";
import {AppConfigService} from "@webbpm/base-package";
@Injectable({providedIn: 'root'})
export class AuthenticationService {
constructor(private http: HttpClient,
private cookieService: CookieService,
private appConfigService: AppConfigService) {
}
checkAuthentication(): Promise<any>{
return this.appConfigService.load().then(value => this.http.get<any>("version").toPromise())
}
logout(): Promise<any> {
return this.http.post<string>('esia/logout', {}, { responseType: 'text' as 'json' }).toPromise().then(url => {
window.open(url, "_self");
});
}
public isAuthenticated(): boolean {
return this.cookieService.get('webbpm.ervu-lkrp-ul') != null;
}
}

View file

@ -2,8 +2,8 @@ import {Injectable} from "@angular/core";
import {ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot} from "@angular/router"; import {ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot} from "@angular/router";
import {Observable} from "rxjs"; import {Observable} from "rxjs";
import {HttpClient, HttpParams} from "@angular/common/http"; import {HttpClient, HttpParams} from "@angular/common/http";
import {CookieService} from "ngx-cookie";
import {MessagesService} from "@webbpm/base-package"; import {MessagesService} from "@webbpm/base-package";
import {AuthenticationService} from "../authentication.service";
@Injectable({providedIn:'root'}) @Injectable({providedIn:'root'})
export abstract class AuthGuard implements CanActivate { export abstract class AuthGuard implements CanActivate {
@ -11,7 +11,7 @@ export abstract class AuthGuard implements CanActivate {
protected constructor( protected constructor(
protected router: Router, protected router: Router,
private httpClient: HttpClient, private httpClient: HttpClient,
private cookieService: CookieService, private authenticationService: AuthenticationService,
private messageService: MessagesService private messageService: MessagesService
) { ) {
} }
@ -42,22 +42,23 @@ export abstract class AuthGuard implements CanActivate {
else if (code) { else if (code) {
const params = new HttpParams().set('code', code); const params = new HttpParams().set('code', code);
this.httpClient.get("esia/auth", this.httpClient.get("esia/auth",
{params: params, responseType: 'text', observe: 'response'}) {
params: params, responseType: 'text', observe: 'response', headers: {
"Error-intercept-skip": "true"
}
})
.toPromise() .toPromise()
.then( .then(
(response) => { (response) => {
if (response.status == 200) { window.open(url.origin + url.pathname, "_self");
window.open(url.origin + url.pathname, "_self"); })
} .catch((reason) => {
else { let errorMessage = reason.error.messages != null
let errorMessage = response.body; ? reason.error.messages
this.messageService.error(errorMessage) : reason.error.replaceAll('\\', '');
throw new Error(errorMessage) this.messageService.error(errorMessage);
} console.error(reason);
}) });
.catch((reason) =>
console.error(reason)
);
return false; return false;
} }
else { else {
@ -72,12 +73,8 @@ export abstract class AuthGuard implements CanActivate {
}); });
} }
private checkAccess(): Promise<boolean> | boolean { private checkAccess(): boolean {
return this.getIsAuth() != null; return this.authenticationService.isAuthenticated();
};
public getIsAuth(): string {
return this.cookieService.get('webbpm.ervu-lkrp-ul');
} }
private extractCode(message: string): string | null { private extractCode(message: string): string | null {

View file

@ -0,0 +1,26 @@
import {HttpEvent, HttpHandler, HttpInterceptor, HttpRequest} from '@angular/common/http';
import {Injectable} from '@angular/core';
import {Observable} from 'rxjs';
import {CookieService} from "ngx-cookie";
import {TokenConstants} from "../../security/TokenConstants";
@Injectable()
export class AbsoluteUrlCsrfInterceptor implements HttpInterceptor {
constructor(private cookieService: CookieService) {
}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
let requestToForward = req;
let token = this.cookieService.get(TokenConstants.CSRF_TOKEN_NAME) as string;
if (token != null) {
let headers = {};
let headerName = TokenConstants.CSRF_HEADER_NAME;
headers[headerName] = token;
requestToForward = req.clone({setHeaders: headers});
}
return next.handle(requestToForward);
}
}

View file

@ -4,9 +4,11 @@ import {
HttpSecurityErrorInterceptor, HttpSecurityErrorInterceptor,
HttpSecurityInterceptor HttpSecurityInterceptor
} from "@webbpm/base-package"; } from "@webbpm/base-package";
import {AbsoluteUrlCsrfInterceptor} from "./absolute-url-csrf.interceptor";
export const DEFAULT_HTTP_INTERCEPTOR_PROVIDERS = [ export const DEFAULT_HTTP_INTERCEPTOR_PROVIDERS = [
{provide: HTTP_INTERCEPTORS, useClass: HttpSecurityInterceptor, multi: true}, {provide: HTTP_INTERCEPTORS, useClass: HttpSecurityInterceptor, multi: true},
{provide: HTTP_INTERCEPTORS, useClass: HttpSecurityErrorInterceptor, multi: true}, {provide: HTTP_INTERCEPTORS, useClass: HttpSecurityErrorInterceptor, multi: true},
{provide: HTTP_INTERCEPTORS, useClass: FormDirtyInterceptor, multi: true} {provide: HTTP_INTERCEPTORS, useClass: FormDirtyInterceptor, multi: true},
{provide: HTTP_INTERCEPTORS, useClass: AbsoluteUrlCsrfInterceptor, multi: true}
]; ];

View file

@ -1,9 +1,11 @@
import {HTTP_INTERCEPTORS} from "@angular/common/http"; import {HTTP_INTERCEPTORS} from "@angular/common/http";
import {FormDirtyInterceptor, HttpSecurityInterceptor} from "@webbpm/base-package"; import {FormDirtyInterceptor, HttpSecurityInterceptor} from "@webbpm/base-package";
import {DevHttpSecurityErrorInterceptor} from "./http-security-error-interceptor.dev"; import {DevHttpSecurityErrorInterceptor} from "./http-security-error-interceptor.dev";
import {AbsoluteUrlCsrfInterceptor} from "./absolute-url-csrf.interceptor";
export const DEFAULT_HTTP_INTERCEPTOR_PROVIDERS = [ export const DEFAULT_HTTP_INTERCEPTOR_PROVIDERS = [
{provide: HTTP_INTERCEPTORS, useClass: HttpSecurityInterceptor, multi: true}, {provide: HTTP_INTERCEPTORS, useClass: HttpSecurityInterceptor, multi: true},
{provide: HTTP_INTERCEPTORS, useClass: DevHttpSecurityErrorInterceptor, multi: true}, {provide: HTTP_INTERCEPTORS, useClass: DevHttpSecurityErrorInterceptor, multi: true},
{provide: HTTP_INTERCEPTORS, useClass: FormDirtyInterceptor, multi: true} {provide: HTTP_INTERCEPTORS, useClass: FormDirtyInterceptor, multi: true},
{provide: HTTP_INTERCEPTORS, useClass: AbsoluteUrlCsrfInterceptor, multi: true},
]; ];

View file

@ -17,6 +17,8 @@ import {
import {AppRoutingModule} from "../app/app-routing.module"; import {AppRoutingModule} from "../app/app-routing.module";
import {GlobalErrorHandler} from "./handler/global-error.handler.prod"; import {GlobalErrorHandler} from "./handler/global-error.handler.prod";
import {DEFAULT_HTTP_INTERCEPTOR_PROVIDERS} from "./interceptor/default-interceptors.prod"; import {DEFAULT_HTTP_INTERCEPTOR_PROVIDERS} from "./interceptor/default-interceptors.prod";
import {HttpClientModule, HttpClientXsrfModule} from "@angular/common/http";
import {TokenConstants} from "../security/TokenConstants";
let IMPORTS = [ let IMPORTS = [
BrowserAnimationsModule, BrowserAnimationsModule,
@ -30,7 +32,10 @@ let IMPORTS = [
CoreModule, CoreModule,
ComponentsModule, ComponentsModule,
AppModule, AppModule,
WebbpmRoutingModule WebbpmRoutingModule,
HttpClientModule,
HttpClientXsrfModule.withOptions(
{cookieName: TokenConstants.CSRF_TOKEN_NAME, headerName: TokenConstants.CSRF_HEADER_NAME})
]; ];
@NgModule({ @NgModule({

210
pom.xml
View file

@ -4,7 +4,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>ru.micord.ervu.lkrp</groupId> <groupId>ru.micord.ervu.lkrp</groupId>
<artifactId>ul</artifactId> <artifactId>ul</artifactId>
<version>1.9.0-SNAPSHOT</version> <version>1.8.2</version>
<packaging>pom</packaging> <packaging>pom</packaging>
<modules> <modules>
<module>backend</module> <module>backend</module>
@ -20,11 +20,33 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<enable.version.in.url>false</enable.version.in.url> <enable.version.in.url>false</enable.version.in.url>
<joda-time.version>2.9.2</joda-time.version> <joda-time.version>2.9.2</joda-time.version>
<webbpm-platform.version>3.182.0</webbpm-platform.version> <webbpm-platform.version>3.178.2</webbpm-platform.version>
<wbp.overall-timeout>72000</wbp.overall-timeout> <wbp.overall-timeout>72000</wbp.overall-timeout>
<jasperreports.version>6.15.0</jasperreports.version>
</properties> </properties>
<dependencyManagement> <dependencyManagement>
<dependencies> <dependencies>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-bom</artifactId>
<version>5.8.14</version>
<scope>import</scope>
<type>pom</type>
<exclusions>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</exclusion>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</exclusion>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency> <dependency>
<groupId>ru.cg.webbpm</groupId> <groupId>ru.cg.webbpm</groupId>
<artifactId>platform-bom</artifactId> <artifactId>platform-bom</artifactId>
@ -41,36 +63,8 @@
<dependency> <dependency>
<groupId>ru.cg.webbpm.packages.base</groupId> <groupId>ru.cg.webbpm.packages.base</groupId>
<artifactId>backend</artifactId> <artifactId>backend</artifactId>
<version>3.182.0</version> <version>3.178.2</version>
<exclusions> <exclusions>
<exclusion>
<groupId>xerces</groupId>
<artifactId>xercesImpl</artifactId>
</exclusion>
<exclusion>
<groupId>ru.micord.fias</groupId>
<artifactId>client</artifactId>
</exclusion>
<exclusion>
<groupId>ru.micord.gar</groupId>
<artifactId>gar-client</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.tika</groupId>
<artifactId>tika-core</artifactId>
</exclusion>
<exclusion>
<groupId>org.mnode.ical4j</groupId>
<artifactId>ical4j</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.commons</groupId>
<artifactId>commons-csv</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework.ldap</groupId>
<artifactId>spring-ldap-core</artifactId>
</exclusion>
<exclusion> <exclusion>
<groupId>ru.cg.webbpm.modules.bpmn</groupId> <groupId>ru.cg.webbpm.modules.bpmn</groupId>
<artifactId>bpmn-workflow-api</artifactId> <artifactId>bpmn-workflow-api</artifactId>
@ -128,32 +122,20 @@
<artifactId>security-adapter</artifactId> <artifactId>security-adapter</artifactId>
</exclusion> </exclusion>
<exclusion> <exclusion>
<groupId>ru.cg.webbpm.modules.reporting.reporting-jasper</groupId> <groupId>ru.micord.fias</groupId>
<artifactId>reporting-jasper-impl</artifactId> <artifactId>client</artifactId>
</exclusion> </exclusion>
<exclusion> <exclusion>
<groupId>ru.cg.webbpm.modules.reporting.reporting-jasper</groupId> <groupId>ru.micord.gar</groupId>
<artifactId>reporting-jasper-fonts</artifactId> <artifactId>gar-client</artifactId>
</exclusion> </exclusion>
<exclusion> <exclusion>
<groupId>ru.cg.webbpm.modules.reporting.reporting-jasper</groupId> <groupId>net.javacrumbs.shedlock</groupId>
<artifactId>reporting-jasper-runtime-impl</artifactId> <artifactId>shedlock-spring</artifactId>
</exclusion> </exclusion>
<exclusion> <exclusion>
<groupId>ru.cg.webbpm.modules.reporting</groupId> <groupId>net.javacrumbs.shedlock</groupId>
<artifactId>reporting-api</artifactId> <artifactId>shedlock-provider-jdbc-template</artifactId>
</exclusion>
<exclusion>
<groupId>ru.cg.webbpm.modules.reporting</groupId>
<artifactId>reporting-runtime-api</artifactId>
</exclusion>
<exclusion>
<groupId>ru.cg.webbpm.modules.reporting</groupId>
<artifactId>reporting-runtime-impl</artifactId>
</exclusion>
<exclusion>
<groupId>ru.cg.webbpm.modules.integration</groupId>
<artifactId>telegram</artifactId>
</exclusion> </exclusion>
</exclusions> </exclusions>
</dependency> </dependency>
@ -174,6 +156,11 @@
<artifactId>resources</artifactId> <artifactId>resources</artifactId>
<version>${project.version}</version> <version>${project.version}</version>
</dependency> </dependency>
<dependency>
<groupId>ru.cg.webbpm.modules.reporting.reporting-jasper</groupId>
<artifactId>reporting-jasper-fonts</artifactId>
<version>${webbpm-platform.version}</version>
</dependency>
<dependency> <dependency>
<groupId>ru.cg.webbpm.modules.core</groupId> <groupId>ru.cg.webbpm.modules.core</groupId>
<artifactId>core-runtime-api</artifactId> <artifactId>core-runtime-api</artifactId>
@ -184,6 +171,11 @@
<artifactId>error-handling-api</artifactId> <artifactId>error-handling-api</artifactId>
<version>${webbpm-platform.version}</version> <version>${webbpm-platform.version}</version>
</dependency> </dependency>
<dependency>
<groupId>ru.cg.webbpm.modules.core</groupId>
<artifactId>metrics</artifactId>
<version>${webbpm-platform.version}</version>
</dependency>
<dependency> <dependency>
<groupId>ru.cg.webbpm.modules.resources</groupId> <groupId>ru.cg.webbpm.modules.resources</groupId>
<artifactId>resources-api</artifactId> <artifactId>resources-api</artifactId>
@ -221,6 +213,11 @@
<version>${webbpm-platform.version}</version> <version>${webbpm-platform.version}</version>
<scope>runtime</scope> <scope>runtime</scope>
</dependency> </dependency>
<dependency>
<groupId>ru.cg.webbpm.modules.security</groupId>
<artifactId>security-api</artifactId>
<version>${webbpm-platform.version}</version>
</dependency>
<dependency> <dependency>
<groupId>ru.cg.webbpm.modules</groupId> <groupId>ru.cg.webbpm.modules</groupId>
<artifactId>webkit-rpc</artifactId> <artifactId>webkit-rpc</artifactId>
@ -251,11 +248,120 @@
<artifactId>inject</artifactId> <artifactId>inject</artifactId>
<version>${webbpm-platform.version}</version> <version>${webbpm-platform.version}</version>
</dependency> </dependency>
<dependency>
<groupId>ru.cg.webbpm.modules.reporting</groupId>
<artifactId>reporting-api</artifactId>
<version>${webbpm-platform.version}</version>
</dependency>
<dependency>
<groupId>ru.cg.webbpm.modules.reporting</groupId>
<artifactId>reporting-runtime-api</artifactId>
<version>${webbpm-platform.version}</version>
</dependency>
<dependency>
<groupId>ru.cg.webbpm.modules.reporting</groupId>
<artifactId>reporting-runtime-impl</artifactId>
<version>${webbpm-platform.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>ru.cg.webbpm.modules.reporting.reporting-jasper</groupId>
<artifactId>reporting-jasper-impl</artifactId>
<version>${webbpm-platform.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>ru.cg.webbpm.modules.reporting.reporting-jasper</groupId>
<artifactId>reporting-jasper-runtime-impl</artifactId>
<version>${webbpm-platform.version}</version>
<scope>runtime</scope>
<exclusions>
<exclusion>
<groupId>bouncycastle</groupId>
<artifactId>bcmail-jdk14</artifactId>
</exclusion>
<exclusion>
<groupId>bouncycastle</groupId>
<artifactId>bcprov-jdk14</artifactId>
</exclusion>
<exclusion>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk14</artifactId>
</exclusion>
<exclusion>
<groupId>org.bouncycastle</groupId>
<artifactId>bcmail-jdk14</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>ru.cg.webbpm.modules.reporting.reporting-xdoc</groupId>
<artifactId>reporting-xdoc-impl</artifactId>
<version>${webbpm-platform.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>ru.cg.webbpm.modules.reporting.reporting-xdoc</groupId>
<artifactId>reporting-xdoc-runtime-impl</artifactId>
<version>${webbpm-platform.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>ru.cg.webbpm</groupId>
<artifactId>web-tests-core</artifactId>
<version>${webbpm-platform.version}</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>${org.bouncycastle.version}</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
<version>${org.bouncycastle.version}</version>
</dependency>
<dependency> <dependency>
<groupId>org.springframework.kafka</groupId> <groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId> <artifactId>spring-kafka</artifactId>
<version>${spring-kafka.version}</version> <version>${spring-kafka.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
<version>4.4.12</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.1</version>
</dependency>
<dependency>
<groupId>org.mnode.ical4j</groupId>
<artifactId>ical4j</artifactId>
<version>3.0.5</version>
</dependency>
<dependency>
<groupId>org.apache.pdfbox</groupId>
<artifactId>pdfbox</artifactId>
<version>2.0.21</version>
</dependency>
<dependency>
<groupId>org.odftoolkit</groupId>
<artifactId>odfdom-java</artifactId>
<version>0.8.7</version>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>${mockito-jupiter.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.28</version>
</dependency>
<dependency> <dependency>
<groupId>org.slf4j</groupId> <groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId> <artifactId>slf4j-api</artifactId>
@ -358,7 +464,7 @@
</plugin> </plugin>
<plugin> <plugin>
<artifactId>maven-war-plugin</artifactId> <artifactId>maven-war-plugin</artifactId>
<version>3.1.0</version> <version>3.1.0.cg1</version>
<configuration> <configuration>
<attachClasses>true</attachClasses> <attachClasses>true</attachClasses>
<failOnMissingWebXml>false</failOnMissingWebXml> <failOnMissingWebXml>false</failOnMissingWebXml>

View file

@ -4,7 +4,7 @@
<parent> <parent>
<groupId>ru.micord.ervu.lkrp</groupId> <groupId>ru.micord.ervu.lkrp</groupId>
<artifactId>ul</artifactId> <artifactId>ul</artifactId>
<version>1.9.0-SNAPSHOT</version> <version>1.8.2</version>
</parent> </parent>
<groupId>ru.micord.ervu.lkrp.ul</groupId> <groupId>ru.micord.ervu.lkrp.ul</groupId>
@ -47,6 +47,43 @@
</resource> </resource>
</resources> </resources>
<plugins> <plugins>
<plugin>
<groupId>com.alexnederlof</groupId>
<artifactId>jasperreports-plugin</artifactId>
<version>2.8</version>
<dependencies>
<dependency>
<groupId>net.sf.jasperreports</groupId>
<artifactId>jasperreports-functions</artifactId>
<version>${jasperreports.version}</version>
</dependency>
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>${joda-time.version}</version>
</dependency>
</dependencies>
<executions>
<execution>
<phase>process-sources</phase>
<goals>
<goal>jasper</goal>
</goals>
</execution>
</executions>
<configuration>
<!-- These are the default configurations: -->
<compiler>net.sf.jasperreports.engine.design.JRJdtCompiler</compiler>
<sourceDirectory>src/main/resources</sourceDirectory>
<outputDirectory>src/main/resources</outputDirectory>
<outputFileExt>.jasper</outputFileExt>
<xmlValidation>true</xmlValidation>
<verbose>false</verbose>
<numberOfThreads>4</numberOfThreads>
<failOnMissingSourceDirectory>true</failOnMissingSourceDirectory>
<sourceScanner>org.codehaus.plexus.compiler.util.scan.StaleSourceScanner</sourceScanner>
</configuration>
</plugin>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-clean-plugin</artifactId> <artifactId>maven-clean-plugin</artifactId>