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>
This commit is contained in:
Chi Nguyen 2021-06-10 11:28:30 +02:00 committed by GitHub
parent dd5bccc62c
commit 9b98b56ce4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 46 additions and 19 deletions

View File

@ -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.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper; import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
import org.springframework.security.ldap.userdetails.LdapAuthoritiesPopulator; 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.jaasapi.JaasApiIntegrationFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
@ -26,15 +27,18 @@ public class BootWebSecurityConfigurer extends WebSecurityConfigurerAdapter {
private final String ldapUserDnPatterns; private final String ldapUserDnPatterns;
private final boolean devMode; private final boolean devMode;
private final boolean enableCsrf;
public BootWebSecurityConfigurer( public BootWebSecurityConfigurer(
@Value("${taskana.ldap.serverUrl:ldap://localhost:10389}") String ldapServerUrl, @Value("${taskana.ldap.serverUrl:ldap://localhost:10389}") String ldapServerUrl,
@Value("${taskana.ldap.baseDn:OU=Test,O=TASKANA}") String ldapBaseDn, @Value("${taskana.ldap.baseDn:OU=Test,O=TASKANA}") String ldapBaseDn,
@Value("${taskana.ldap.groupSearchBase:cn=groups}") String ldapGroupSearchBase, @Value("${taskana.ldap.groupSearchBase:cn=groups}") String ldapGroupSearchBase,
@Value("${taskana.ldap.userDnPatterns:uid={0},cn=users}") String ldapUserDnPatterns, @Value("${taskana.ldap.userDnPatterns:uid={0},cn=users}") String ldapUserDnPatterns,
@Value("${enableCsrf:false}") boolean enableCsrf,
LdapAuthoritiesPopulator ldapAuthoritiesPopulator, LdapAuthoritiesPopulator ldapAuthoritiesPopulator,
GrantedAuthoritiesMapper grantedAuthoritiesMapper, GrantedAuthoritiesMapper grantedAuthoritiesMapper,
@Value("${devMode:false}") boolean devMode) { @Value("${devMode:false}") boolean devMode) {
this.enableCsrf = enableCsrf;
this.ldapAuthoritiesPopulator = ldapAuthoritiesPopulator; this.ldapAuthoritiesPopulator = ldapAuthoritiesPopulator;
this.grantedAuthoritiesMapper = grantedAuthoritiesMapper; this.grantedAuthoritiesMapper = grantedAuthoritiesMapper;
this.ldapServerUrl = ldapServerUrl; this.ldapServerUrl = ldapServerUrl;
@ -60,15 +64,11 @@ public class BootWebSecurityConfigurer extends WebSecurityConfigurerAdapter {
@Override @Override
protected void configure(HttpSecurity http) throws Exception { protected void configure(HttpSecurity http) throws Exception {
HttpSecurity httpSecurity =
http.authorizeRequests() http.authorizeRequests()
.antMatchers("/css/**", "/img/**") .antMatchers("/css/**", "/img/**")
.permitAll() .permitAll()
.and() .and()
.csrf()
.disable()
.httpBasic()
.and()
.authorizeRequests() .authorizeRequests()
.antMatchers(HttpMethod.GET, "/docs/**") .antMatchers(HttpMethod.GET, "/docs/**")
.permitAll() .permitAll()
@ -76,6 +76,14 @@ public class BootWebSecurityConfigurer extends WebSecurityConfigurerAdapter {
.addFilter(jaasApiIntegrationFilter()) .addFilter(jaasApiIntegrationFilter())
.addFilterAfter(new SpringSecurityToJaasFilter(), JaasApiIntegrationFilter.class); .addFilterAfter(new SpringSecurityToJaasFilter(), JaasApiIntegrationFilter.class);
if (enableCsrf) {
CookieCsrfTokenRepository csrfTokenRepository = CookieCsrfTokenRepository.withHttpOnlyFalse();
csrfTokenRepository.setCookiePath("/");
httpSecurity.csrf().csrfTokenRepository(csrfTokenRepository);
} else {
httpSecurity.csrf().disable().httpBasic();
}
if (devMode) { if (devMode) {
http.headers() http.headers()
.frameOptions() .frameOptions()

View File

@ -31,6 +31,9 @@ taskana.schemaName=TASKANA
####### property that control rest api security deploy use true for no security. ####### property that control rest api security deploy use true for no security.
devMode=false 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 ####### property that control if the database is cleaned and sample data is generated
generateSampleData=true generateSampleData=true

View File

@ -4,7 +4,7 @@
import { BrowserModule } from '@angular/platform-browser'; import { BrowserModule } from '@angular/platform-browser';
import { APP_INITIALIZER, NgModule } from '@angular/core'; import { APP_INITIALIZER, NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms'; 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 { NgxsModule } from '@ngxs/store';
import { NgxsReduxDevtoolsPluginModule } from '@ngxs/devtools-plugin'; import { NgxsReduxDevtoolsPluginModule } from '@ngxs/devtools-plugin';
import { AlertModule } from 'ngx-bootstrap'; import { AlertModule } from 'ngx-bootstrap';
@ -82,7 +82,8 @@ const MODULES = [
MatIconModule, MatIconModule,
MatSelectModule, MatSelectModule,
NgxsModule.forRoot(STATES, { developmentMode: !environment.production }), 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]; const DECLARATIONS = [AppComponent, NavBarComponent, UserInformationComponent, NoAccessComponent, SidenavListComponent];

View File

@ -1,5 +1,12 @@
import { Injectable } from '@angular/core'; 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 { Observable } from 'rxjs';
import { RequestInProgressService } from 'app/shared/services/request-in-progress/request-in-progress.service'; import { RequestInProgressService } from 'app/shared/services/request-in-progress/request-in-progress.service';
import { environment } from 'environments/environment'; import { environment } from 'environments/environment';
@ -9,10 +16,18 @@ import { NOTIFICATION_TYPES } from '../../models/notifications';
@Injectable() @Injectable()
export class HttpClientInterceptor implements HttpInterceptor { 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<any>, next: HttpHandler): Observable<HttpEvent<any>> { intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
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) { if (!environment.production) {
req = req.clone({ headers: req.headers.set('Authorization', 'Basic YWRtaW46YWRtaW4=') }); req = req.clone({ headers: req.headers.set('Authorization', 'Basic YWRtaW46YWRtaW4=') });
} }