Merge remote-tracking branch 'origin/hotfix/1.8.2'
This commit is contained in:
commit
fa2aa366a1
32 changed files with 707 additions and 269 deletions
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
package ru.micord.ervu.security;
|
||||||
|
|
||||||
|
public class SecurityConstants {
|
||||||
|
public static final String ESIA_LOGOUT = "/esia/logout";
|
||||||
|
}
|
||||||
|
|
@ -1,15 +1,22 @@
|
||||||
package ru.micord.ervu.security.esia.controller;
|
package ru.micord.ervu.security.esia.controller;
|
||||||
|
|
||||||
import javax.servlet.http.Cookie;
|
import java.text.DateFormat;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
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.RestController;
|
||||||
import ru.micord.ervu.security.esia.model.PersonDataModel;
|
import ru.micord.ervu.security.esia.model.PersonDataModel;
|
||||||
import ru.micord.ervu.security.esia.model.PersonModel;
|
import ru.micord.ervu.security.esia.model.PersonModel;
|
||||||
import ru.micord.ervu.security.esia.service.EsiaAuthService;
|
import ru.micord.ervu.security.esia.service.EsiaAuthService;
|
||||||
import ru.micord.ervu.security.esia.service.PersonalDataService;
|
import ru.micord.ervu.security.esia.service.PersonalDataService;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import ru.micord.ervu.security.webbpm.jwt.service.JwtTokenService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Eduard Tihomirov
|
* @author Eduard Tihomirov
|
||||||
|
|
@ -23,13 +30,16 @@ public class EsiaController {
|
||||||
@Autowired
|
@Autowired
|
||||||
private PersonalDataService personalDataService;
|
private PersonalDataService personalDataService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private JwtTokenService jwtTokenService;
|
||||||
|
|
||||||
@RequestMapping(value = "/esia/url")
|
@RequestMapping(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 boolean 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -40,18 +50,8 @@ public class EsiaController {
|
||||||
|
|
||||||
@RequestMapping(value = "/esia/person")
|
@RequestMapping(value = "/esia/person")
|
||||||
public PersonDataModel getPersonModel(HttpServletRequest request) {
|
public PersonDataModel getPersonModel(HttpServletRequest request) {
|
||||||
String accessToken = null;
|
String accessToken = jwtTokenService.getAccessToken(request);
|
||||||
Cookie[] cookies = request.getCookies();
|
DateFormat df = new SimpleDateFormat("dd.MM.yyyy");
|
||||||
if (cookies != null) {
|
|
||||||
for (Cookie cookie : cookies) {
|
|
||||||
if (cookie.getName().equals("access_token")) {
|
|
||||||
accessToken = cookie.getValue();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (accessToken == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
PersonModel personModel = personalDataService.getPersonModel(accessToken);
|
PersonModel personModel = personalDataService.getPersonModel(accessToken);
|
||||||
PersonDataModel personDataModel = new PersonDataModel();
|
PersonDataModel personDataModel = new PersonDataModel();
|
||||||
personDataModel.birthDate = personModel.getBirthDate();
|
personDataModel.birthDate = personModel.getBirthDate();
|
||||||
|
|
@ -69,24 +69,8 @@ public class EsiaController {
|
||||||
|
|
||||||
@RequestMapping(value = "/esia/userfullname")
|
@RequestMapping(value = "/esia/userfullname")
|
||||||
public String getUserFullname(HttpServletRequest request) {
|
public String getUserFullname(HttpServletRequest request) {
|
||||||
String accessToken = null;
|
String accessToken = jwtTokenService.getAccessToken(request);
|
||||||
Cookie[] cookies = request.getCookies();
|
|
||||||
if (cookies != null) {
|
|
||||||
for (Cookie cookie : cookies) {
|
|
||||||
if (cookie.getName().equals("access_token")) {
|
|
||||||
accessToken = cookie.getValue();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (accessToken == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
PersonModel personModel = personalDataService.getPersonModel(accessToken);
|
PersonModel personModel = personalDataService.getPersonModel(accessToken);
|
||||||
return personModel.getLastName() + " " + personModel.getFirstName().charAt(0) + ". " + personModel.getMiddleName().charAt(0) + ".";
|
return personModel.getLastName() + " " + personModel.getFirstName().charAt(0) + ". " + personModel.getMiddleName().charAt(0) + ".";
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequestMapping(value = "/esia/logout")
|
|
||||||
public String logout(HttpServletRequest request, HttpServletResponse response) {
|
|
||||||
return esiaAuthService.logout(request, response);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,10 +22,14 @@ import javax.servlet.http.HttpServletResponse;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import org.springframework.beans.factory.annotation.Qualifier;
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.security.core.context.SecurityContext;
|
||||||
import ru.micord.ervu.kafka.model.Document;
|
import ru.micord.ervu.kafka.model.Document;
|
||||||
import ru.micord.ervu.kafka.model.Person;
|
import ru.micord.ervu.kafka.model.Person;
|
||||||
import ru.micord.ervu.kafka.model.Response;
|
import ru.micord.ervu.kafka.model.Response;
|
||||||
import ru.micord.ervu.kafka.service.ReplyingKafkaService;
|
import ru.micord.ervu.kafka.service.ReplyingKafkaService;
|
||||||
|
import ru.micord.ervu.security.esia.token.TokensStore;
|
||||||
import ru.micord.ervu.security.esia.config.EsiaConfig;
|
import ru.micord.ervu.security.esia.config.EsiaConfig;
|
||||||
import ru.micord.ervu.security.esia.model.FormUrlencoded;
|
import ru.micord.ervu.security.esia.model.FormUrlencoded;
|
||||||
import ru.micord.ervu.security.esia.model.EsiaAccessToken;
|
import ru.micord.ervu.security.esia.model.EsiaAccessToken;
|
||||||
|
|
@ -36,6 +40,8 @@ import org.springframework.http.HttpHeaders;
|
||||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
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;
|
||||||
|
|
||||||
|
|
@ -44,10 +50,6 @@ import ru.micord.ervu.security.webbpm.jwt.model.Token;
|
||||||
*/
|
*/
|
||||||
@Service
|
@Service
|
||||||
public class EsiaAuthService {
|
public class EsiaAuthService {
|
||||||
|
|
||||||
@Value("${cookie.path:#{null}}")
|
|
||||||
private String path;
|
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private ObjectMapper objectMapper;
|
private ObjectMapper objectMapper;
|
||||||
|
|
||||||
|
|
@ -64,6 +66,9 @@ public class EsiaAuthService {
|
||||||
@Autowired
|
@Autowired
|
||||||
private PersonalDataService personalDataService;
|
private PersonalDataService personalDataService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private SecurityHelper securityHelper;
|
||||||
|
|
||||||
@Value("${ervu.kafka.reply.topic}")
|
@Value("${ervu.kafka.reply.topic}")
|
||||||
private String requestReplyTopic;
|
private String requestReplyTopic;
|
||||||
|
|
||||||
|
|
@ -141,7 +146,7 @@ public class EsiaAuthService {
|
||||||
return uriBuilder.toString();
|
return uriBuilder.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean getEsiaTokensByCode(String esiaAuthCode, HttpServletRequest request, HttpServletResponse response) {
|
public ResponseEntity<?> getEsiaTokensByCode(String esiaAuthCode, HttpServletRequest request, HttpServletResponse response) {
|
||||||
try {
|
try {
|
||||||
String clientId = esiaConfig.getClientId();
|
String clientId = esiaConfig.getClientId();
|
||||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy.MM.dd HH:mm:ss xx");
|
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy.MM.dd HH:mm:ss xx");
|
||||||
|
|
@ -183,50 +188,45 @@ 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 cookiePath = null;
|
|
||||||
if (path != null) {
|
|
||||||
cookiePath = path;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
cookiePath = request.getContextPath();
|
|
||||||
}
|
|
||||||
String accessToken = tokenResponse.getAccess_token();
|
String accessToken = tokenResponse.getAccess_token();
|
||||||
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);
|
EsiaAccessToken esiaAccessToken = personalDataService.readToken(accessToken);
|
||||||
cookieRefresh.setHttpOnly(true);
|
String prnOid = esiaAccessToken.getSbj_id();
|
||||||
|
Long expiresIn = tokenResponse.getExpires_in();
|
||||||
cookieRefresh.setPath(cookiePath);
|
TokensStore.addAccessToken(prnOid, accessToken, expiresIn);
|
||||||
response.addCookie(cookieRefresh);
|
TokensStore.addRefreshToken(prnOid, refreshToken, expiresIn);
|
||||||
|
Response ervuIdResponse = getErvuIdResponse(accessToken);
|
||||||
byte[] decodedBytes = Base64.getDecoder()
|
Token token = jwtTokenService.createAccessToken(esiaAccessToken.getSbj_id(), expiresIn, ervuIdResponse.getErvuId());
|
||||||
.decode(
|
int expiry = tokenResponse.getExpires_in().intValue();
|
||||||
accessToken.substring(accessToken.indexOf('.') + 1, accessToken.lastIndexOf('.')));
|
Cookie accessCookie = securityHelper.createAccessCookie(token.getValue(), expiry);
|
||||||
String decodedString = new String(decodedBytes);
|
response.addCookie(accessCookie);
|
||||||
EsiaAccessToken esiaAccessToken = objectMapper.readValue(decodedString, EsiaAccessToken.class);
|
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
|
||||||
String ervuId = getErvuId(accessToken);
|
new UsernamePasswordAuthenticationToken(token.getUserAccountId(), null);
|
||||||
Token token = jwtTokenService.createAccessToken(esiaAccessToken.getSbj_id(), tokenResponse.getExpires_in(), ervuId);
|
SecurityContext context = SecurityContextHolder.createEmptyContext();
|
||||||
Cookie authToken = new Cookie("auth_token", token.getValue());
|
JwtAuthentication authentication = new JwtAuthentication(usernamePasswordAuthenticationToken,
|
||||||
authToken.setPath(cookiePath);
|
esiaAccessToken.getSbj_id(), token.getValue());
|
||||||
authToken.setHttpOnly(true);
|
context.setAuthentication(authentication);
|
||||||
response.addCookie(authToken);
|
SecurityContextHolder.setContext(context);
|
||||||
SecurityContextHolder.getContext()
|
Cookie authMarkerCookie = securityHelper.createAuthMarkerCookie("true", expiry);
|
||||||
.setAuthentication(
|
response.addCookie(authMarkerCookie);
|
||||||
new UsernamePasswordAuthenticationToken(esiaAccessToken.getSbj_id(), null));
|
if (ervuIdResponse.getErrorData() != null) {
|
||||||
|
return new ResponseEntity<>(
|
||||||
Cookie isAuth = new Cookie("webbpm.ervu-lkrp-fl", "true");
|
"Доступ запрещен. " + ervuIdResponse.getErrorData().getName(),
|
||||||
isAuth.setMaxAge(tokenResponse.getExpires_in().intValue());
|
HttpStatus.FORBIDDEN
|
||||||
isAuth.setPath("/");
|
);
|
||||||
response.addCookie(isAuth);
|
}
|
||||||
return true;
|
return ResponseEntity.ok("Authentication successful");
|
||||||
}
|
}
|
||||||
catch (Exception e) {
|
catch (Exception e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
|
|
@ -235,15 +235,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();
|
||||||
|
|
@ -288,43 +280,26 @@ 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);
|
EsiaAccessToken esiaAccessToken = personalDataService.readToken(accessToken);
|
||||||
cookieRefresh.setHttpOnly(true);
|
String prnOid = esiaAccessToken.getSbj_id();
|
||||||
cookieRefresh.setPath(cookiePath);
|
Long expiresIn = tokenResponse.getExpires_in();
|
||||||
response.addCookie(cookieRefresh);
|
TokensStore.addAccessToken(prnOid, accessToken, expiresIn);
|
||||||
|
TokensStore.addRefreshToken(prnOid, newRefreshToken, expiresIn);
|
||||||
byte[] decodedBytes = Base64.getDecoder()
|
Response ervuIdResponse = getErvuIdResponse(accessToken);
|
||||||
.decode(
|
Token token = jwtTokenService.createAccessToken(esiaAccessToken.getSbj_id(), expiresIn, ervuIdResponse.getErvuId());
|
||||||
accessToken.substring(accessToken.indexOf('.') + 1, accessToken.lastIndexOf('.')));
|
int expiry = tokenResponse.getExpires_in().intValue();
|
||||||
String decodedString = new String(decodedBytes);
|
Cookie accessCookie = securityHelper.createAccessCookie(token.getValue(), expiry);
|
||||||
EsiaAccessToken esiaAccessToken = objectMapper.readValue(decodedString, EsiaAccessToken.class);
|
response.addCookie(accessCookie);
|
||||||
String ervuId = getErvuId(accessToken);
|
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
|
||||||
Token token = jwtTokenService.createAccessToken(esiaAccessToken.getSbj_id(), tokenResponse.getExpires_in(), ervuId);
|
new UsernamePasswordAuthenticationToken(token.getUserAccountId(), null);
|
||||||
Cookie authToken = new Cookie("auth_token", token.getValue());
|
SecurityContext context = SecurityContextHolder.createEmptyContext();
|
||||||
authToken.setPath(cookiePath);
|
JwtAuthentication authentication = new JwtAuthentication(usernamePasswordAuthenticationToken,
|
||||||
authToken.setHttpOnly(true);
|
esiaAccessToken.getSbj_id(), token.getValue());
|
||||||
response.addCookie(authToken);
|
context.setAuthentication(authentication);
|
||||||
SecurityContextHolder.getContext()
|
SecurityContextHolder.setContext(context);
|
||||||
.setAuthentication(
|
Cookie authMarkerCookie = securityHelper.createAuthMarkerCookie("true", expiry);
|
||||||
new UsernamePasswordAuthenticationToken(esiaAccessToken.getSbj_id(), null));
|
response.addCookie(authMarkerCookie);
|
||||||
|
|
||||||
Cookie isAuth = new Cookie("webbpm.ervu-lkrp-fl", "true");
|
|
||||||
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);
|
||||||
|
|
@ -365,23 +340,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-fl")) {
|
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);
|
||||||
|
|
@ -395,22 +357,14 @@ public class EsiaAuthService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getErvuId(String accessToken) {
|
public Response getErvuIdResponse(String accessToken) {
|
||||||
try {
|
try {
|
||||||
PersonModel personModel = personalDataService.getPersonModel(accessToken);
|
PersonModel personModel = personalDataService.getPersonModel(accessToken);
|
||||||
Person person = copyToPerson(personModel);
|
Person person = copyToPerson(personModel);
|
||||||
String kafkaResponse = replyingKafkaService.sendMessageAndGetReply(requestTopic,
|
String kafkaResponse = replyingKafkaService.sendMessageAndGetReply(requestTopic,
|
||||||
requestReplyTopic, objectMapper.writeValueAsString(person)
|
requestReplyTopic, objectMapper.writeValueAsString(person)
|
||||||
);
|
);
|
||||||
Response response = objectMapper.readValue(kafkaResponse, Response.class);
|
return objectMapper.readValue(kafkaResponse, Response.class);
|
||||||
if (response.getErrorData() != null) {
|
|
||||||
throw new RuntimeException(
|
|
||||||
"Error code = " + response.getErrorData().getCode() + ", error name = "
|
|
||||||
+ response.getErrorData().getName());
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return response.getErvuId();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception e) {
|
catch (Exception e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
|
|
|
||||||
|
|
@ -31,11 +31,7 @@ public class EsiaPersonalDataService implements PersonalDataService {
|
||||||
@Override
|
@Override
|
||||||
public PersonModel getPersonModel(String accessToken) {
|
public PersonModel getPersonModel(String accessToken) {
|
||||||
try {
|
try {
|
||||||
byte[] decodedBytes = Base64.getDecoder()
|
EsiaAccessToken esiaAccessToken = readToken(accessToken);
|
||||||
.decode(
|
|
||||||
accessToken.substring(accessToken.indexOf('.') + 1, accessToken.lastIndexOf('.')));
|
|
||||||
String decodedString = new String(decodedBytes);
|
|
||||||
EsiaAccessToken esiaAccessToken = objectMapper.readValue(decodedString, EsiaAccessToken.class);
|
|
||||||
String prnsId = esiaAccessToken.getSbj_id();
|
String prnsId = esiaAccessToken.getSbj_id();
|
||||||
PersonModel personModel = getPersonData(prnsId, accessToken);
|
PersonModel personModel = getPersonData(prnsId, accessToken);
|
||||||
personModel.setPassportModel(
|
personModel.setPassportModel(
|
||||||
|
|
@ -88,4 +84,23 @@ public class EsiaPersonalDataService implements PersonalDataService {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EsiaAccessToken readToken(String accessToken) {
|
||||||
|
try {
|
||||||
|
byte[] decodedBytes = Base64.getDecoder()
|
||||||
|
.decode(
|
||||||
|
accessToken.substring(accessToken.indexOf('.') + 1, accessToken.lastIndexOf('.'))
|
||||||
|
.replace('-', '+')
|
||||||
|
.replace('_', '/'));
|
||||||
|
String decodedString = new String(decodedBytes);
|
||||||
|
EsiaAccessToken esiaAccessToken = objectMapper.readValue(decodedString,
|
||||||
|
EsiaAccessToken.class
|
||||||
|
);
|
||||||
|
return esiaAccessToken;
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package ru.micord.ervu.security.esia.service;
|
package ru.micord.ervu.security.esia.service;
|
||||||
|
|
||||||
|
import ru.micord.ervu.security.esia.model.EsiaAccessToken;
|
||||||
import ru.micord.ervu.security.esia.model.PersonModel;
|
import ru.micord.ervu.security.esia.model.PersonModel;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -8,4 +9,5 @@ import ru.micord.ervu.security.esia.model.PersonModel;
|
||||||
public interface PersonalDataService {
|
public interface PersonalDataService {
|
||||||
|
|
||||||
PersonModel getPersonModel(String accessToken);
|
PersonModel getPersonModel(String accessToken);
|
||||||
|
EsiaAccessToken readToken(String accessToken);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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());
|
return new JwtAuthentication(pwdToken, token.getUserAccountId(), token.getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,7 +4,6 @@ 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;
|
||||||
|
|
||||||
|
|
@ -16,69 +15,81 @@ 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);
|
||||||
|
String[] ids = token.getUserAccountId().split(":");
|
||||||
|
if (ids.length != 2) {
|
||||||
|
throw new CredentialsExpiredException("Invalid token. User has no ervuId");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return token;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,12 +1,9 @@
|
||||||
package ru.micord.ervu.security.webbpm.jwt.service;
|
package ru.micord.ervu.security.webbpm.jwt.service;
|
||||||
|
|
||||||
import java.lang.invoke.MethodHandles;
|
import java.lang.invoke.MethodHandles;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.Optional;
|
|
||||||
import javax.crypto.SecretKey;
|
import javax.crypto.SecretKey;
|
||||||
import javax.servlet.http.Cookie;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
import io.jsonwebtoken.Claims;
|
import io.jsonwebtoken.Claims;
|
||||||
|
|
@ -17,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
|
||||||
*/
|
*/
|
||||||
|
|
@ -29,7 +29,7 @@ public class JwtTokenService {
|
||||||
|
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
|
private static final Logger LOGGER = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
|
||||||
|
|
||||||
@Value("${webbpm.security.token.issuer:}")
|
@Value("${webbpm.security.token.issuer:#{null}}")
|
||||||
private final String tokenIssuerName =
|
private final String tokenIssuerName =
|
||||||
ResourceMetadataUtils.PROJECT_GROUP_ID + "." + ResourceMetadataUtils.PROJECT_ARTIFACT_ID;
|
ResourceMetadataUtils.PROJECT_GROUP_ID + "." + ResourceMetadataUtils.PROJECT_ARTIFACT_ID;
|
||||||
private final SecretKey SIGNING_KEY;
|
private final SecretKey SIGNING_KEY;
|
||||||
|
|
@ -80,13 +80,27 @@ public class JwtTokenService {
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getErvuId() {
|
public String getErvuId() {
|
||||||
String authToken = Optional.ofNullable(request.getCookies())
|
String extractAuthToken = extractAuthToken(request);
|
||||||
.map(cookies -> Arrays.stream(cookies)
|
return getToken(extractAuthToken).getUserAccountId().split(":")[1];
|
||||||
.filter(cookie -> cookie.getName().equals("auth_token"))
|
}
|
||||||
.findFirst()
|
|
||||||
.map(Cookie::getValue)
|
public String getAccessToken(HttpServletRequest request) {
|
||||||
.orElseThrow(() -> new RuntimeException("Failed to get auth data. User unauthorized.")))
|
return TokensStore.getAccessToken(getUserAccountId(request));
|
||||||
.orElseThrow(() -> new RuntimeException("Failed to get auth data. User unauthorized."));
|
}
|
||||||
return getToken(authToken).getUserAccountId().split(":")[1];
|
|
||||||
|
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.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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-fl";
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -30,4 +30,7 @@ ERVU_KAFKA_RECRUIT_HEADER_CLASS=Request@urn://rostelekom.ru/RP-SummonsTR/1.0.5
|
||||||
ERVU_KAFKA_REGISTRY_EXTRACT_REQUEST_TOPIC=ervu.extract.info.request
|
ERVU_KAFKA_REGISTRY_EXTRACT_REQUEST_TOPIC=ervu.extract.info.request
|
||||||
ERVU_KAFKA_REGISTRY_EXTRACT_REPLY_TOPIC=ervu.extract.info.response
|
ERVU_KAFKA_REGISTRY_EXTRACT_REPLY_TOPIC=ervu.extract.info.response
|
||||||
ERVU_KAFKA_EXTRACT_HEADER_CLASS=Request@urn://rostelekom.ru/ERVU-extractFromRegistryTR/1.0.3
|
ERVU_KAFKA_EXTRACT_HEADER_CLASS=Request@urn://rostelekom.ru/ERVU-extractFromRegistryTR/1.0.3
|
||||||
ERVU_KAFKA_DOC_LOGIN_MODULE=org.apache.kafka.common.security.scram.ScramLoginModule
|
ERVU_KAFKA_DOC_LOGIN_MODULE=org.apache.kafka.common.security.scram.ScramLoginModule
|
||||||
|
|
||||||
|
ESIA_TOKEN_CLEAR_CRON=0 0 */1 * * *
|
||||||
|
COOKIE_PATH=/fl
|
||||||
|
|
@ -78,6 +78,7 @@
|
||||||
<property name="ervu.kafka.registry.extract.request.topic" value="ervu.extract.info.request"/>
|
<property name="ervu.kafka.registry.extract.request.topic" value="ervu.extract.info.request"/>
|
||||||
<property name="ervu.kafka.registry.extract.reply.topic" value="ervu.extract.info.response"/>
|
<property name="ervu.kafka.registry.extract.reply.topic" value="ervu.extract.info.response"/>
|
||||||
<property name="ervu.kafka.extract.header.class" value="request@urn://rostelekom.ru/ERVU-extractFromRegistryTR/1.0.3"/>
|
<property name="ervu.kafka.extract.header.class" value="request@urn://rostelekom.ru/ERVU-extractFromRegistryTR/1.0.3"/>
|
||||||
|
<property name="esia.token.clear.cron" value="0 0 */1 * * *"/>
|
||||||
</system-properties>
|
</system-properties>
|
||||||
<management>
|
<management>
|
||||||
<audit-log>
|
<audit-log>
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
"filter_cleanup_interval_hours": 720,
|
"filter_cleanup_interval_hours": 720,
|
||||||
"filter_cleanup_check_period_minutes": 30,
|
"filter_cleanup_check_period_minutes": 30,
|
||||||
"auth_method": "form",
|
"auth_method": "form",
|
||||||
"enable.version.in.url": "%enable.version.in.url%",
|
"enable.version.in.url": "false",
|
||||||
"backend.context": "fl",
|
"backend.context": "fl",
|
||||||
"guard.confirm_exit": false,
|
"guard.confirm_exit": false,
|
||||||
"message_service_error_timeout": "",
|
"message_service_error_timeout": "",
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
%project.version%
|
1.8.2-SNAPSHOT
|
||||||
|
|
|
||||||
|
|
@ -2,4 +2,5 @@
|
||||||
<div ngbDropdownMenu *ngIf="getIsAuth()">
|
<div ngbDropdownMenu *ngIf="getIsAuth()">
|
||||||
<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="!getIsAuth()" (click)="logout()">Выйти</button>
|
||||||
|
|
@ -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';
|
||||||
|
|
@ -23,6 +23,7 @@ import {TextWithDialogLinks} from "../../ervu/component/textwithdialoglinks/Text
|
||||||
import {LogOutComponent} from "./component/logout.component";
|
import {LogOutComponent} from "./component/logout.component";
|
||||||
import {LoadForm} from "../../ervu/component/container/LoadForm";
|
import {LoadForm} from "../../ervu/component/container/LoadForm";
|
||||||
import {InMemoryStaticGrid} from "../../ervu/component/grid/InMemoryStaticGrid";
|
import {InMemoryStaticGrid} from "../../ervu/component/grid/InMemoryStaticGrid";
|
||||||
|
import {AuthenticationService} from "../security/authentication.service";
|
||||||
|
|
||||||
registerLocaleData(localeRu);
|
registerLocaleData(localeRu);
|
||||||
export const DIRECTIVES = [
|
export const DIRECTIVES = [
|
||||||
|
|
@ -37,6 +38,10 @@ export const DIRECTIVES = [
|
||||||
forwardRef(() => InMemoryStaticGrid)
|
forwardRef(() => InMemoryStaticGrid)
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export function checkAuthentication(authService: AuthenticationService): () => Promise<any> {
|
||||||
|
return () => authService.checkAuthentication();
|
||||||
|
}
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
|
|
@ -57,6 +62,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: [],
|
||||||
|
|
|
||||||
|
|
@ -27,10 +27,10 @@ export class LogOutComponent implements OnInit{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public logout(): void {
|
logout(): Promise<any> {
|
||||||
this.httpClient.get<string>("esia/logout").toPromise().then(url => {
|
return this.httpClient.post<string>('esia/logout', {}, { responseType: 'text' as 'json' }).toPromise().then(url => {
|
||||||
window.open(url, "_self");
|
window.open(url, "_self");
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public getUserFullname(): string {
|
public getUserFullname(): string {
|
||||||
|
|
|
||||||
4
frontend/src/ts/modules/security/TokenConstants.ts
Normal file
4
frontend/src/ts/modules/security/TokenConstants.ts
Normal 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";
|
||||||
|
}
|
||||||
27
frontend/src/ts/modules/security/authentication.service.ts
Normal file
27
frontend/src/ts/modules/security/authentication.service.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
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<string> {
|
||||||
|
return this.http.post<string>('esia/logout', {}).toPromise();
|
||||||
|
}
|
||||||
|
|
||||||
|
public isAuthenticated(): boolean {
|
||||||
|
return this.cookieService.get('webbpm.ervu-lkrp-fl') != null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
@ -36,11 +36,24 @@ 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<boolean>("esia/auth", {params: params}).toPromise().then(
|
this.httpClient.get("esia/auth",
|
||||||
() => window.open(url.origin + url.pathname, "_self"))
|
{
|
||||||
.catch((reason) =>
|
params: params, responseType: 'text', observe: 'response', headers: {
|
||||||
console.error(reason)
|
"Error-intercept-skip": "true"
|
||||||
);
|
}
|
||||||
|
})
|
||||||
|
.toPromise()
|
||||||
|
.then(
|
||||||
|
(response) => {
|
||||||
|
window.open(url.origin + url.pathname, "_self");
|
||||||
|
})
|
||||||
|
.catch((reason) => {
|
||||||
|
let errorMessage = reason.error.messages != null
|
||||||
|
? reason.error.messages
|
||||||
|
: reason.error.replaceAll('\\', '');
|
||||||
|
this.messageService.error(errorMessage);
|
||||||
|
console.error(reason);
|
||||||
|
});
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
@ -55,11 +68,7 @@ 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-fl');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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}
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -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},
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -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({
|
||||||
|
|
|
||||||
29
pom.xml
29
pom.xml
|
|
@ -25,6 +25,27 @@
|
||||||
</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>
|
||||||
|
|
@ -163,14 +184,6 @@
|
||||||
<groupId>ru.cg.webbpm.modules.database</groupId>
|
<groupId>ru.cg.webbpm.modules.database</groupId>
|
||||||
<artifactId>database-impl</artifactId>
|
<artifactId>database-impl</artifactId>
|
||||||
</exclusion>
|
</exclusion>
|
||||||
<exclusion>
|
|
||||||
<groupId>net.javacrumbs.shedlock</groupId>
|
|
||||||
<artifactId>shedlock-spring</artifactId>
|
|
||||||
</exclusion>
|
|
||||||
<exclusion>
|
|
||||||
<groupId>net.javacrumbs.shedlock</groupId>
|
|
||||||
<artifactId>shedlock-provider-jdbc-template</artifactId>
|
|
||||||
</exclusion>
|
|
||||||
</exclusions>
|
</exclusions>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue