SUPPORT-8609 add csrf cookie protection
This commit is contained in:
parent
b6d1c9720c
commit
bb92c71194
19 changed files with 237 additions and 48 deletions
5
backend/src/main/java/SecurityInit.java
Normal file
5
backend/src/main/java/SecurityInit.java
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;
|
||||||
|
import org.springframework.web.WebApplicationInitializer;
|
||||||
|
|
||||||
|
public class SecurityInit extends AbstractSecurityWebApplicationInitializer implements WebApplicationInitializer {
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
package ru.micord.ervu.security;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.web.authentication.logout.HttpStatusReturningLogoutSuccessHandler;
|
||||||
|
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
|
||||||
|
import org.springframework.security.web.csrf.CsrfToken;
|
||||||
|
import org.springframework.security.web.csrf.CsrfTokenRepository;
|
||||||
|
|
||||||
|
public class CsrfTokenAwareLogoutSuccessHandler implements LogoutSuccessHandler {
|
||||||
|
|
||||||
|
private final CsrfTokenRepository csrfTokenRepository;
|
||||||
|
|
||||||
|
private final LogoutSuccessHandler delegate = new HttpStatusReturningLogoutSuccessHandler(HttpStatus.OK);
|
||||||
|
|
||||||
|
public CsrfTokenAwareLogoutSuccessHandler(CsrfTokenRepository csrfTokenRepository) {
|
||||||
|
this.csrfTokenRepository = csrfTokenRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException,
|
||||||
|
ServletException {
|
||||||
|
CsrfToken csrfToken = this.csrfTokenRepository.generateToken(request);
|
||||||
|
this.csrfTokenRepository.saveToken(csrfToken, request, response);
|
||||||
|
this.delegate.onLogoutSuccess(request, response, authentication);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -10,9 +10,14 @@ import org.springframework.security.config.annotation.web.configuration.WebSecur
|
||||||
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.authentication.UsernamePasswordAuthenticationFilter;
|
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||||
|
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
|
||||||
|
import org.springframework.security.web.csrf.CsrfTokenRequestHandler;
|
||||||
|
import org.springframework.security.web.csrf.XorCsrfTokenRequestAttributeHandler;
|
||||||
import ru.micord.ervu.security.webbpm.jwt.filter.JwtAuthenticationFilter;
|
import ru.micord.ervu.security.webbpm.jwt.filter.JwtAuthenticationFilter;
|
||||||
import ru.micord.ervu.security.webbpm.jwt.UnauthorizedEntryPoint;
|
import ru.micord.ervu.security.webbpm.jwt.UnauthorizedEntryPoint;
|
||||||
|
|
||||||
|
import static ru.micord.ervu.security.SecurityConstants.ESIA_LOGOUT;
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableWebSecurity
|
@EnableWebSecurity
|
||||||
public class SecurityConfig extends WebSecurityConfigurerAdapter {
|
public class SecurityConfig extends WebSecurityConfigurerAdapter {
|
||||||
|
|
@ -22,19 +27,31 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void configure(HttpSecurity http) throws Exception {
|
protected void configure(HttpSecurity http) throws Exception {
|
||||||
|
|
||||||
httpConfigure(http);
|
httpConfigure(http);
|
||||||
http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
|
http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void httpConfigure(HttpSecurity httpSecurity) throws Exception {
|
protected void httpConfigure(HttpSecurity httpSecurity) throws Exception {
|
||||||
String[] permitAll = {"/esia/url", "/esia/auth", "esia/refresh"};
|
String[] permitAll = {"/version","/esia/url", "/esia/auth", "esia/refresh"};
|
||||||
|
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.authorizeRequests()
|
httpSecurity.authorizeRequests()
|
||||||
.antMatchers(permitAll).permitAll()
|
.antMatchers(permitAll).permitAll()
|
||||||
.antMatchers("/**").authenticated()
|
.antMatchers("/**").authenticated()
|
||||||
.and()
|
.and()
|
||||||
.csrf().disable()
|
.csrf((csrf) -> csrf
|
||||||
|
.csrfTokenRepository(tokenRepository)
|
||||||
|
.csrfTokenRequestHandler(requestHandler)
|
||||||
|
)
|
||||||
|
.logout((logout) -> logout
|
||||||
|
.logoutUrl(ESIA_LOGOUT)
|
||||||
|
.logoutSuccessHandler(new CsrfTokenAwareLogoutSuccessHandler(tokenRepository))
|
||||||
|
)
|
||||||
.exceptionHandling().authenticationEntryPoint(entryPoint())
|
.exceptionHandling().authenticationEntryPoint(entryPoint())
|
||||||
.and()
|
.and()
|
||||||
.sessionManagement()
|
.sessionManagement()
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
package ru.micord.ervu.security;
|
||||||
|
|
||||||
|
public class SecurityConstants {
|
||||||
|
public static final String ESIA_LOGOUT = "/esia/logout";
|
||||||
|
}
|
||||||
|
|
@ -3,12 +3,13 @@ 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.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;
|
||||||
|
|
||||||
|
|
@ -24,37 +25,37 @@ 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 boolean esiaAuth(@RequestParam("code") String code, HttpServletRequest request, HttpServletResponse response) {
|
public boolean 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")
|
@PostMapping(SecurityConstants.ESIA_LOGOUT)
|
||||||
public String logout(HttpServletRequest request, HttpServletResponse response) {
|
public String logout(HttpServletRequest request, HttpServletResponse response) {
|
||||||
return esiaAuthService.logout(request, response);
|
return esiaAuthService.logout(request, response);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -200,11 +200,19 @@ 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);
|
||||||
if (!hasRole) {
|
if (!hasRole) {
|
||||||
throw new RuntimeException("The user does not have the required role");
|
throw new RuntimeException("The user does not have the required role");
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<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>
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
];
|
];
|
||||||
|
|
||||||
|
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: [],
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
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-ul') != 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
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
@ -55,11 +55,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-ul');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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({
|
||||||
|
|
|
||||||
21
pom.xml
21
pom.xml
|
|
@ -26,6 +26,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>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue