Solucionado (ver solução)
Solucionado
(ver solução)
17
respostas

CORS

Minha validação de token está funcionando normalmente quando testo via POSTMAN, contudo estou criando uma aplicação para consumir os serviços mas está me retornando o erro "has been blocked by CORS policy: Response to preflight request doesn't pass access control check: It does not have HTTP ok status."

Pelo que pesquisei, como os servidores são diferentes, o CORS barra o acesso e deveria ser feito uma configuração para liberar esse acesso EXTERNO. Seria possível mostrar como funcionaria essa configuração?

Obrigado.

17 respostas

Oi Jose,

Para consumir a API via aplicação JavaScript acessada de um browser vai precisar mesmo configurar o CORS na API.

Basta criar uma classe como a seguinte:

@Configuration
public class CorsConfiguration implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
            .allowedOrigins("http://localhost:3000")
            .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS", "HEAD", "TRACE", "CONNECT");
    }
}

No exemplo anterior o localhost:3000 seria o endereço da aplicação frontend.

Bons estudos!

Entendi.

Mas e se minha aplicação pudesse ser consumida por vários clientes dos quais eu não conheça seus endereços? Teria como configurar para qualquer endereço? Algo como: .allowedOrigins("*").

Obrigado Professor.

Fiz o teste e funcionou com allowedOrigins("*").

Muito obrigado.

solução!

Oi Jose,

Sim, é possível liberar o acesso para qualquer endereço, colocando o * no allowedOrigins.

Mas isso não é uma boa prática, por questões de segurança.

O ideal é liberar cada um dos endereços permitidos manualmente, algo que é mais "chato", porém mais seguro:

@Override
public void addCorsMappings(CorsRegistry registry) {
    //liberando app cliente 1
    registry.addMapping("/**")
         .allowedOrigins("http://localhost:3000")
        .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS", "HEAD", "TRACE", "CONNECT");

    //liberando app cliente 2
    registry.addMapping("/topicos/**")
         .allowedOrigins("http://localhost:4000")
        .allowedMethods("GET", "OPTIONS", "HEAD", "TRACE", "CONNECT");
}

Repare como a configuração é bem flexível, a ponto de você conseguir limitar quais métodos HTTP e URIs cada cliente pode acessar.

Bons estudos!

Professor, queria aproveitar o tema pra perguntar sobre a diferença entre essa configuração de CORS e aquela liberação feita na classe de configuração do Spring Security.

Oi Thiago,

Aquela configuração feita na classe SecurityConfigurations é para o Spring Security não interceptar as requisições de CORS que são feitas automaticamente pelo Browser.

Caso contrário o Spring Security vai devolver erro de usuário não autenticado nas requisições cors.

Mas detalhes aqui: https://docs.spring.io/spring-security/site/docs/current/reference/html5/#cors

Obrigado!

Criei a classe CorsConfiguration e o problema ainda persiste . Retorna o erro: "Access to XMLHttpRequest at 'http://localhost:9000/auth' from origin 'http://localhost:4200' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource."

Preciso fazer mais alguma configuração além de criar essa classe?

Consegui resolver implementando WebMvcConfigurer na classe SecurityConfigurations e sobrescrevendo o método addCorsMappings nela.

Estava com o mesmo problema do José. Contudo precisando liberar apenas o localhost:4000. Com o código que Rodrigo passou parou de dar erro de CORS,

Quando o recurso está liberado na classe SecurityConfigurations, tudo funciona:

.antMatchers(HttpMethod.GET, "/topicos").permitAll()

Porém, quando eu preciso que esteja autenticado, por exemplo excluindo a linha acima, o retorno é vazio.

Debugando a aplicação Spring, o token está sempre null (independente da página estar liberada ou não):

entrou no filtro
token recuperado: null

A chamada JS está assim (a aplicação frontent também pede o CORS) e o token está correto:

return fetch('http://localhost:8080/topicos',{
    method: 'GET',
    headers: {
      'Content-type': 'application/json',
      'Authorization': token,
      'Access-Control-Allow-Origin': 'http://localhost:8080',
      'Access-Control-Allow-Headers': 'Origin, X-Requested-With, Content-Type, Accept'      
      }
  })

Alguma ideia?

Obrigado.

Oi Eduardo,

No código JS qual é o valor dessa variável token ?

Olá Rodrigo! Cara, antes de tudo, quero parabenizar pelo seus cursos, de verdade! Parabéns pela didática, conteúdo, a forma de conduzir... excelente!

Bem, quanto ao conteúdo da variável token:

{"token":"Bearer eyJhbGciOi...."}

É um JSON.

Obrigado pela atenção.

Agora, mesmo com o CORS configurado, está intermitente, agora dá erro mesmo com o endpoint liberado (no frontend)...

Requisição cross-origin bloqueada: A política de mesma origem (Same Origin Policy) impede a leitura do recurso remoto em http://localhost:8080/topicos. (Motivo: a resposta de comprovação de CORS não foi bem-sucedida).

Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://localhost:8080/eventos. (Reason: CORS request did not succeed).

Quanto ao conteúdo do token, ajustei para uma string, e a variável agora ficou assim:

Bearer eyJhbGciOiJI....

Mas continua dando token recuperado: null (no backend spring)

Eduardo, posta aqui o código completo da suas classes SecurityConfigurations e AutenticacaoViaTokenFilter.

SecurityConfigurations:

package br.com.position.eventler.config.security;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import br.com.position.eventler.repository.UsuarioRepository;

@EnableWebSecurity
@Configuration
public class SecurityConfigurations extends WebSecurityConfigurerAdapter {
    @Autowired
    private AutenticacaoService autenticacaoService;
    @Autowired
    private TokenService tokenService;
    @Autowired
    private UsuarioRepository usuarioRepository;
    @Override
    @Bean
    protected AuthenticationManager authenticationManager() throws Exception {
        return super.authenticationManager();
    }
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(autenticacaoService).passwordEncoder(new BCryptPasswordEncoder());
    }
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
        //.antMatchers(HttpMethod.OPTIONS, "/topicos").permitAll()
        .antMatchers(HttpMethod.GET, "/topicos").permitAll()
        //.antMatchers(HttpMethod.GET, "/topicos/*").permitAll()
        .antMatchers(HttpMethod.POST, "/auth").permitAll()
        .antMatchers(HttpMethod.PUT, "/auth").permitAll()
        .antMatchers(HttpMethod.OPTIONS, "/auth").permitAll()
        .antMatchers(HttpMethod.GET, "/actuator/**").permitAll()
        .antMatchers(HttpMethod.GET, "/").permitAll()
        .anyRequest().authenticated() 
        //.and().formLogin() 
        .and().csrf().disable() 
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
        .and().addFilterBefore(new AutenticacaoViaTokenFilter(tokenService, usuarioRepository), UsernamePasswordAuthenticationFilter.class);
    }
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/**.html", "/v2/api-docs", "/webjars/**", "/configuration/**", "/swagger-resources/**");
    }
}

AutenticacaoViaTokenFilter:

package br.com.position.eventler.config.security;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;
import br.com.position.eventler.modelo.Usuario;
import br.com.position.eventler.repository.UsuarioRepository;

public class AutenticacaoViaTokenFilter extends OncePerRequestFilter {
    private TokenService tokenService;
    private UsuarioRepository repository;
    public AutenticacaoViaTokenFilter(TokenService tokenService, UsuarioRepository repository) {
        System.out.println("passando pelo construtor do ViaTokenFilter");
        this.tokenService = tokenService;
        this.repository = repository;
    }
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        String token = recuperarToken(request);
        System.out.println("token recuperado: " + token);
        boolean valido = tokenService.isTokenValido(token);
        if (valido) {
            autenticarCliente(token);
        }else {
            System.out.println("token inválido: "+token);
        }
        filterChain.doFilter(request, response);
    }
    private void autenticarCliente(String token) {
        Long idUsuario = tokenService.getIdUsuario(token);
        Usuario usuario = repository.findById(idUsuario).get();
        UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(usuario, null, usuario.getAuthorities());
               SecurityContextHolder.getContext().setAuthentication(authentication);
    }
    private String recuperarToken(HttpServletRequest request) {
        String token = request.getHeader("Authorization");
        if (token == null || token.isEmpty() || !token.startsWith("Bearer ")) {
            return null;
        }
        return token.substring(7, token.length());
    }
}

Oi Eduardo,

Acho que o Spring Security que deve estar barrando as requisições CORS.

Precisa liberar tais requisições colocando um and().cors() no método configure da classe SecurityConfigurations:

...
.and().csrf().disable().and().cors()
...

Olá Rodrigo,

Agora deu certo! Muito obrigado.

Só tive que inverter, pois o STS não reconhecia o .and após o .disable. Coloquei antes e funcionou...

.and().cors().and().csrf().disable()

Mais uma vez, meu muito obrigado!