Angular / Spring Boot com Keycloak lança 403

Sep 23 2020

Protegi meu aplicativo Spring Boot com Keycloak 11.0.2 e Spring Security seguindo esta documentação .

Usei a configuração básica do Keycloak em application.properties:

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

Eu tenho um aplicativo Angular de front-end separado, que é configurado como um cliente diferente no Keylocak; mas no mesmo domínio do aplicativo Spring Boot. No aplicativo Angular, estou enviando o token fornecido pelo Keycloak nos cabeçalhos HTTP com:

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

Quando eu acesso uma página Angular que chama uma GETAPI, obtenho um blocked by CORS policyerro:

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.

Tentei adicionar a keycloak.cors=truepropriedade a application.properties. Com essa propriedade adicionada, as GETchamadas estão funcionando. Mas agora, quando chamo uma POST/PUTAPI, recebo um Failed to load resource: the server responded with a status of 403 ()erro.

KeycloakWebSecurityConfigurerAdapter:

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

Aplicação de amostra de primavera: https://github.com/bilaldekar/kc

Aplicação de amostra angular: https://github.com/bilaldekar/kc-ang

Solicitar cabeçalhos:

Respostas

GSSwain Oct 13 2020 at 12:39

Parece-me um problema de csrf.

Adicione o seguinte à configuração de segurança

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

O código acima definirá o token CSRF no cookie nomeado XSRF-TOKEN. O Angular lê esse cookie para tokens CSRF por padrão e adiciona isso nas solicitações subsequentes. Para garantir que o Angular possa lê-lo, estamos configurando o Http onlypara False. Isso tem suas próprias implicações de segurança, pois o token CSRF pode ser acessado via script agora. Se você não preferir fazer isso, a outra maneira seria ler o X-XSRF-TOKENtoken do cabeçalho da resposta e enviá-lo no cabeçalho da solicitação subsequente usando o interceptor Angular.

Atualizar:

Ao testar localmente, onde o Angular está sendo executado em 4200, o aplicativo Spring Boot, o servidor Keycloak em 8080, 8085 portas.

Use o proxy do servidor Webpack Dev com as seguintes etapas. (Você não precisa mais desta configuração keycloak.cors=true)

na environment.tsatualização doapiUrl

apiUrl: '', //Spring Boot API

em seguida, adicione um proxy.conf.jsonna srcpasta com o seguinte conteúdo

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

Adicione a configuração do proxy no angular.jsoncomando serve

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

Agora você notaria que as solicitações da API iriam para localhost: 4200 / api / * e seriam roteadas para o aplicativo de inicialização do Spring por meio da configuração acima.

Com isso, os cookies relacionados ao XSRF e o cabeçalho fariam parte da solicitação e você não deverá obter nenhum problema.

[! [Versão de trabalho de exemplo] [1]] [1]

Atualização 2: Suporte CORS

Se o seu front-end e back-end estiverem em domínios diferentes e se você precisar oferecer suporte a CORS, isso é o que precisa ser feito

A configuração de segurança no Spring deve permitir CORS para a origem do front-end (neste 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;
}

No Angular, atualize a apiUrl no environment.tsarquivo para

apiUrl: '//localhost:8085'

Ao fazer qualquer operação de gravação (POST / PUT / DELETE) usando o cliente http Angular, defina a withCredentialsopção para true. (Certifique-se também de que o método seja compatível com CORS na configuração do servidor. Na configuração CORS acima, permitimos apenas GET e POST. Se precisar de suporte para PUT / DELETE, certifique-se de atualizar a configuração CORS de acordo)

Neste caso, atualize o seguinte método em fournisseur.service.ts

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