From 9b98b56ce40e905549ba6df38bc710228af949d7 Mon Sep 17 00:00:00 2001 From: Chi Nguyen <6671583+cnguyen-de@users.noreply.github.com> Date: Thu, 10 Jun 2021 11:28:30 +0200 Subject: [PATCH] TSK-1651: Add XSRF Support (#1602) * TSK-1651: Add XSRF Support * TSK-1651: now sending XRSF Header for requests with absolute path * TSK-1651: created configuration to enable XSRF for spring-boot example Co-authored-by: Mustapha Zorgati <15628173+mustaphazorgati@users.noreply.github.com> --- .../security/BootWebSecurityConfigurer.java | 36 +++++++++++-------- .../src/main/resources/application.properties | 3 ++ web/src/app/app.module.ts | 5 +-- .../http-client-interceptor.service.ts | 21 +++++++++-- 4 files changed, 46 insertions(+), 19 deletions(-) diff --git a/rest/taskana-rest-spring-example-boot/src/main/java/pro/taskana/example/boot/security/BootWebSecurityConfigurer.java b/rest/taskana-rest-spring-example-boot/src/main/java/pro/taskana/example/boot/security/BootWebSecurityConfigurer.java index abf958994..81d63c19b 100644 --- a/rest/taskana-rest-spring-example-boot/src/main/java/pro/taskana/example/boot/security/BootWebSecurityConfigurer.java +++ b/rest/taskana-rest-spring-example-boot/src/main/java/pro/taskana/example/boot/security/BootWebSecurityConfigurer.java @@ -8,6 +8,7 @@ import org.springframework.security.config.annotation.web.configuration.EnableWe import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper; import org.springframework.security.ldap.userdetails.LdapAuthoritiesPopulator; +import org.springframework.security.web.csrf.CookieCsrfTokenRepository; import org.springframework.security.web.jaasapi.JaasApiIntegrationFilter; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; @@ -26,15 +27,18 @@ public class BootWebSecurityConfigurer extends WebSecurityConfigurerAdapter { private final String ldapUserDnPatterns; private final boolean devMode; + private final boolean enableCsrf; public BootWebSecurityConfigurer( @Value("${taskana.ldap.serverUrl:ldap://localhost:10389}") String ldapServerUrl, @Value("${taskana.ldap.baseDn:OU=Test,O=TASKANA}") String ldapBaseDn, @Value("${taskana.ldap.groupSearchBase:cn=groups}") String ldapGroupSearchBase, @Value("${taskana.ldap.userDnPatterns:uid={0},cn=users}") String ldapUserDnPatterns, + @Value("${enableCsrf:false}") boolean enableCsrf, LdapAuthoritiesPopulator ldapAuthoritiesPopulator, GrantedAuthoritiesMapper grantedAuthoritiesMapper, @Value("${devMode:false}") boolean devMode) { + this.enableCsrf = enableCsrf; this.ldapAuthoritiesPopulator = ldapAuthoritiesPopulator; this.grantedAuthoritiesMapper = grantedAuthoritiesMapper; this.ldapServerUrl = ldapServerUrl; @@ -60,21 +64,25 @@ public class BootWebSecurityConfigurer extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { + HttpSecurity httpSecurity = + http.authorizeRequests() + .antMatchers("/css/**", "/img/**") + .permitAll() + .and() + .authorizeRequests() + .antMatchers(HttpMethod.GET, "/docs/**") + .permitAll() + .and() + .addFilter(jaasApiIntegrationFilter()) + .addFilterAfter(new SpringSecurityToJaasFilter(), JaasApiIntegrationFilter.class); - http.authorizeRequests() - .antMatchers("/css/**", "/img/**") - .permitAll() - .and() - .csrf() - .disable() - .httpBasic() - .and() - .authorizeRequests() - .antMatchers(HttpMethod.GET, "/docs/**") - .permitAll() - .and() - .addFilter(jaasApiIntegrationFilter()) - .addFilterAfter(new SpringSecurityToJaasFilter(), JaasApiIntegrationFilter.class); + if (enableCsrf) { + CookieCsrfTokenRepository csrfTokenRepository = CookieCsrfTokenRepository.withHttpOnlyFalse(); + csrfTokenRepository.setCookiePath("/"); + httpSecurity.csrf().csrfTokenRepository(csrfTokenRepository); + } else { + httpSecurity.csrf().disable().httpBasic(); + } if (devMode) { http.headers() diff --git a/rest/taskana-rest-spring-example-boot/src/main/resources/application.properties b/rest/taskana-rest-spring-example-boot/src/main/resources/application.properties index 896fd605a..5b2ba888e 100644 --- a/rest/taskana-rest-spring-example-boot/src/main/resources/application.properties +++ b/rest/taskana-rest-spring-example-boot/src/main/resources/application.properties @@ -31,6 +31,9 @@ taskana.schemaName=TASKANA ####### property that control rest api security deploy use true for no security. devMode=false +# This property enables the support of XSRF tokens. This will not work together with devMode. +enableCsrf=true + ####### property that control if the database is cleaned and sample data is generated generateSampleData=true diff --git a/web/src/app/app.module.ts b/web/src/app/app.module.ts index 355c8e3ff..7744aa8fa 100644 --- a/web/src/app/app.module.ts +++ b/web/src/app/app.module.ts @@ -4,7 +4,7 @@ import { BrowserModule } from '@angular/platform-browser'; import { APP_INITIALIZER, NgModule } from '@angular/core'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; -import { HttpClientModule } from '@angular/common/http'; +import { HttpClientModule, HttpClientXsrfModule, HttpXsrfTokenExtractor } from '@angular/common/http'; import { NgxsModule } from '@ngxs/store'; import { NgxsReduxDevtoolsPluginModule } from '@ngxs/devtools-plugin'; import { AlertModule } from 'ngx-bootstrap'; @@ -82,7 +82,8 @@ const MODULES = [ MatIconModule, MatSelectModule, NgxsModule.forRoot(STATES, { developmentMode: !environment.production }), - NgxsReduxDevtoolsPluginModule.forRoot({ disabled: environment.production, maxAge: 25 }) + NgxsReduxDevtoolsPluginModule.forRoot({ disabled: environment.production, maxAge: 25 }), + HttpClientXsrfModule ]; const DECLARATIONS = [AppComponent, NavBarComponent, UserInformationComponent, NoAccessComponent, SidenavListComponent]; diff --git a/web/src/app/shared/services/http-client-interceptor/http-client-interceptor.service.ts b/web/src/app/shared/services/http-client-interceptor/http-client-interceptor.service.ts index 47a6eb716..00ddc305d 100644 --- a/web/src/app/shared/services/http-client-interceptor/http-client-interceptor.service.ts +++ b/web/src/app/shared/services/http-client-interceptor/http-client-interceptor.service.ts @@ -1,5 +1,12 @@ import { Injectable } from '@angular/core'; -import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest, HttpErrorResponse } from '@angular/common/http'; +import { + HttpErrorResponse, + HttpEvent, + HttpHandler, + HttpInterceptor, + HttpRequest, + HttpXsrfTokenExtractor +} from '@angular/common/http'; import { Observable } from 'rxjs'; import { RequestInProgressService } from 'app/shared/services/request-in-progress/request-in-progress.service'; import { environment } from 'environments/environment'; @@ -9,10 +16,18 @@ import { NOTIFICATION_TYPES } from '../../models/notifications'; @Injectable() export class HttpClientInterceptor implements HttpInterceptor { - constructor(private requestInProgressService: RequestInProgressService, private errorsService: NotificationService) {} + constructor( + private requestInProgressService: RequestInProgressService, + private errorsService: NotificationService, + private tokenExtractor: HttpXsrfTokenExtractor + ) {} intercept(request: HttpRequest, next: HttpHandler): Observable> { - let req = request.clone({ headers: request.headers.set('Content-Type', 'application/hal+json') }); + let req = request.clone({ setHeaders: { 'Content-Type': 'application/hal+json' } }); + let token = this.tokenExtractor.getToken() as string; + if (token !== null) { + req = req.clone({ setHeaders: { 'X-XSRF-TOKEN': token } }); + } if (!environment.production) { req = req.clone({ headers: req.headers.set('Authorization', 'Basic YWRtaW46YWRtaW4=') }); }