Angular / Spring Boot con Keycloak lancia 403

Sep 23 2020

Ho protetto la mia applicazione Spring Boot con Keycloak 11.0.2 e Spring Security seguendo questa documentazione .

Ho usato la configurazione Keycloak di base in application.properties:

    keycloak.auth-server-url=http://localhost:8085/auth
    keycloak.realm=cirta
    keycloak.resource=cirta-api
    keycloak.public-client=false

Ho un'app Angular frontend separata, configurata come client diverso in Keylocak; ma nello stesso regno dell'app Spring Boot. Dall'app Angular invio il token fornito da Keycloak nelle intestazioni HTTP con:

'Authorization' : 'Bearer ' + this.securityService.kc.token

Quando accedo a una pagina Angular che chiama GETun'API, ottengo un blocked by CORS policyerrore:

Access to XMLHttpRequest at 'http://localhost:8080/api/modePaiements' from origin 'http://localhost:4200' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: Redirect is not allowed for a preflight request.

Quindi ho provato ad aggiungere la keycloak.cors=trueproprietà a application.properties. Con questa proprietà aggiunta, le GETchiamate funzionano. Ma ora quando chiamo POST/PUTun'API ricevo un Failed to load resource: the server responded with a status of 403 ()errore.

KeycloakWebSecurityConfigurerAdapter:

@Override
protected void configure(HttpSecurity http) throws Exception {
    super.configure(http);
    http.authorizeRequests().antMatchers("/api/*").hasRole("app-manager").anyRequest().permitAll();
}

Applicazione campione di primavera: https://github.com/bilaldekar/kc

Applicazione campione angolare: https://github.com/bilaldekar/kc-ang

Intestazioni richiesta:

Risposte

GSSwain Oct 13 2020 at 12:39

Mi sembra un problema di CSRF.

Aggiungi quanto segue alla configurazione della sicurezza

http.authorizeRequests().antMatchers("/api/*").hasRole("app-manager").anyRequest().permitAll()
                .and().csrf()
                .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());

Il codice precedente imposterà il token CSRF nel cookie denominato XSRF-TOKEN. Angular legge questo cookie per i token CSRF per impostazione predefinita e lo aggiunge nelle richieste successive. Per assicurarci che Angular possa leggerlo, impostiamo il file Http onlya False. Ciò ha le sue implicazioni sulla sicurezza poiché ora è possibile accedere al token CSRF tramite script. Se non si preferisce farlo, l'altro modo sarebbe leggere il X-XSRF-TOKENtoken dall'intestazione della risposta e inviarlo nell'intestazione della richiesta successiva utilizzando Angular Interceptor.

Aggiornare:

Durante il test in locale, dove Angular è in esecuzione su 4200, l'app Spring Boot, il server Keycloak sulle porte 8080, 8085.

Utilizzare il proxy del server Webpack Dev con i seguenti passaggi. (Non hai più bisogno di questa configurazione keycloak.cors=true)

in environment.tsaggiornamentoapiUrl

apiUrl: '', //Spring Boot API

quindi aggiungi un proxy.conf.jsonsotto la srccartella con il seguente contenuto

{
    "/api": {
        "target": "http://localhost:8080",
        "secure": false
    },
    "logLevel": "debug"
}

Aggiungi la configurazione del proxy nel angular.jsoncomando serve

"options": {
    "browserTarget": "kc-ang:build",
    "proxyConfig": "src/proxy.conf.json"
}

Ora noterai che le richieste api andrebbero a localhost: 4200 / api / * e che verrebbero indirizzate all'app di avvio primaverile tramite la configurazione sopra.

Con questo i cookie relativi a XSRF e l'intestazione sarebbero parte della richiesta e non dovresti riscontrare alcun problema.

[! [Versione di lavoro di esempio] [1]] [1]

Aggiornamento 2: supporto CORS

Se il tuo frontend e backend si trovano su domini diversi e se devi supportare CORS, allora questo è ciò che devi fare

La configurazione di sicurezza su Spring dovrebbe consentire CORS per l'origine del frontend (in questo caso http: // localhost: 4200):

protected void configure(HttpSecurity http) throws Exception {
    super.configure(http);
    http.authorizeRequests().antMatchers("/api/*").hasRole("app-manager").anyRequest()
            .permitAll()
    .and().csrf()
            .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
    .and().cors();
}

@Bean
CorsConfigurationSource corsConfigurationSource() {
    CorsConfiguration configuration = new CorsConfiguration();
    configuration.setAllowedOrigins(Arrays.asList("http://localhost:4200"));
    configuration.setAllowedMethods(Arrays.asList("GET","POST"));
    configuration.setAllowCredentials(Boolean.TRUE);
    configuration.addAllowedHeader("*");

    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration("/api/*", configuration);
    return source;
}

Su Angular, aggiorna l'apiUrl nel environment.tsfile a

apiUrl: '//localhost:8085'

Durante l'esecuzione di qualsiasi operazione di scrittura (POST / PUT / DELETE) utilizzando il client http Angular, impostare l' withCredentialsopzione su true. (Assicurati anche che il metodo sia supportato per CORS nella configurazione lato server. Nella configurazione CORS sopra abbiamo consentito solo GET e POST. Se devi supportare PUT / DELETE assicurati di aggiornare la configurazione CORS di conseguenza)

In questo caso, aggiorna il seguente metodo in fournisseur.service.ts

addFournisseur(fournisseur: Fournisseur): Observable<Fournisseur> {
    return this.httpClient.post<Fournisseur>(this.envUrl + '/api/fournisseurs', fournisseur, {withCredentials: true});
  }