Solucionado (ver solução)

Importante

Você está vendo a versão anterior da nova experiência da Alura que estamos preparando para você. Em breve, ela ganha uma identidade visual novinha totalmente pensada em potencializar seus estudos!

Solucionado
(ver solução)
15
respostas

Erro ao testar delete

package br.com.alura.forum.config.swagger;

import java.util.Arrays;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import br.com.alura.forum.modelo.Usuario;
import springfox.documentation.builders.ParameterBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.schema.ModelRef;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;

@Configuration
public class SwaggerConfigurations {

    @Bean
    public Docket forumApi() {
        return new Docket(DocumentationType.SWAGGER_2)
                .select()
                .apis(RequestHandlerSelectors.basePackage("br.com.alura.forum"))
                .paths(PathSelectors.ant("/**"))
                .build()
                .ignoredParameterTypes(Usuario.class)
                .globalOperationParameters(
                        Arrays.asList(
                                new ParameterBuilder()
                                    .name("Authorization")
                                    .description("Header para Token JWT")
                                    .modelRef(new ModelRef("string"))
                                    .parameterType("header")
                                    .required(false)
                                    .build()));

    }

}

Insira aqui a descrição dessa imagem para ajudar na acessibilidade

15 respostas
package br.com.alura.forum.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.authentication.UsernamePasswordAuthenticationToken;
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.alura.forum.repository.UsuarioRepository;

@EnableWebSecurity
@Configuration
public class SecurityConfigurations extends WebSecurityConfigurerAdapter{

    @Override
    @Bean // para trabalhar com autenticacao jwt tem que sobrescrever esse metodo aqui para poder injetar o AuthenticationManager em outra classe
    protected AuthenticationManager authenticationManager() throws Exception {

        return super.authenticationManager();
    }

    @Autowired
    private UsuarioRepository usuarioRepository; 

    @Autowired
    private TokenService token;

    @Autowired
    private AutenticacaoService autenticacaoService;

    @Override    // configuracoes de autenticacao
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(autenticacaoService).passwordEncoder(new BCryptPasswordEncoder()); //vai informar onde ta a classe com a logica de autenticacao

    }
    @Override  // configuracoes de autorizacao
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .antMatchers(HttpMethod.GET, "/topicos").permitAll() //vai permitir o get do endereco /topicos
            .antMatchers(HttpMethod.GET, "/topicos/*").permitAll()
            .antMatchers(HttpMethod.POST, "/auth").permitAll()
            .antMatchers(HttpMethod.GET, "/actuator/**").permitAll()
            .anyRequest().authenticated() // qualquer outra requisicao tem que autenticar; 
            .and().csrf().disable()
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and().addFilterBefore(new AutenticacaoViaTokenFilter(token, usuarioRepository), UsernamePasswordAuthenticationFilter.class);  
    }
    @Override // configuracoes de recursos estaticos(javascript, css, imagens, etc)
    public void configure(WebSecurity web) throws Exception {
         web.ignoring()
            .antMatchers("/**.html", "/v2/api-docs", "/webjars/**", "/configuration/**", "/swagger-resources/**");
    }


}

Oi Artur,

Posta a exception completa aqui. Obs: verificou se existe o topico de id 6 no banco antes de fazer o delete?

Olá, verifiquei sim e tá la no get listar, o a excecption é muito grande dai peguei o começo e o final: Insira aqui a descrição dessa imagem para ajudar na acessibilidade Insira aqui a descrição dessa imagem para ajudar na acessibilidade

"message": "Cannot invoke "Object.hashCode()" because "value" is null", "path": "/topicos/7" }

Alguma novidade ?

Oi Artur,

Desculpe a demora. Parece que o erro está estourando na sua classe AutenticacaoViaTokenFilter.

Posta o código dela aqui e também o código do seu controller e do TokenService.

Sem problema !

package br.com.alura.forum.config.security;

import java.io.IOException;
import java.util.Optional;

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.alura.forum.modelo.Usuario;
import br.com.alura.forum.repository.UsuarioRepository;


public class AutenticacaoViaTokenFilter extends OncePerRequestFilter {


    private UsuarioRepository repository;

    private TokenService tokenService;

    public AutenticacaoViaTokenFilter(TokenService tokenService, UsuarioRepository repository) {
        this.tokenService = tokenService;
        this.repository = repository;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {

        String token = recuperarToken(request);

        boolean valido = tokenService.isTokenValido(token);
        if(valido) {
            autenticarCliente(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());

    }
}
@EnableWebSecurity
@Configuration
public class SecurityConfigurations extends WebSecurityConfigurerAdapter{

    @Override
    @Bean // para trabalhar com autenticacao jwt tem que sobrescrever esse metodo aqui para poder injetar o AuthenticationManager em outra classe
    protected AuthenticationManager authenticationManager() throws Exception {

        return super.authenticationManager();
    }

    @Autowired
    private UsuarioRepository usuarioRepository; 

    @Autowired
    private TokenService token;

    @Autowired
    private AutenticacaoService autenticacaoService;

    @Override    // configuracoes de autenticacao
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(autenticacaoService).passwordEncoder(new BCryptPasswordEncoder()); //vai informar onde ta a classe com a logica de autenticacao

    }
    @Override  // configuracoes de autorizacao
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .antMatchers(HttpMethod.GET, "/topicos").permitAll() //vai permitir o get do endereco /topicos
            .antMatchers(HttpMethod.GET, "/topicos/*").permitAll()
            .antMatchers(HttpMethod.POST, "/auth").permitAll()
            .anyRequest().authenticated() // qualquer outra requisicao tem que autenticar; 
            .and().csrf().disable()
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and().addFilterBefore(new AutenticacaoViaTokenFilter(token, usuarioRepository), UsernamePasswordAuthenticationFilter.class);  
    }
    @Override // configuracoes de recursos estaticos(javascript, css, imagens, etc)
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/**.html", "/v2/api-docs", "/webjars/**","/configuration/**", "/swagger-resources/**");
    }


}

Posta também o TokenService e TopicosController

package br.com.alura.forum.config.security;

import java.util.Date;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Service;

import br.com.alura.forum.modelo.Usuario;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

@Service
public class TokenService {

    @Value("${forum.jwt.expiration}") // injetando uma propriedade do application.properties
    private String expiration;

    @Value("${forum.jwt.secret}") // injetando uma propriedade do application.properties
    private String secret;

    public String gerarToken(Authentication authentication) {
        Usuario logado = (Usuario) authentication.getPrincipal();
        Date hoje = new Date();
        Date dataExpiracao = new Date(hoje.getTime() + Long.parseLong(expiration));

        return Jwts.builder()
                .setIssuer("API do Fórum da Alura")
                .setSubject(logado.getId().toString())
                .setIssuedAt(hoje)
                .setExpiration(dataExpiracao)
                .signWith(SignatureAlgorithm.HS256, secret)
                .compact();
    }

    public boolean isTokenValido(String token) {

        try {
            Jwts.parser().setSigningKey(this.secret).parseClaimsJws(token);
            return true;
        } catch (Exception e) {
            return false;
        }


    }

    public Long getIdUsuario(String token) {
        Claims claims = Jwts.parser().setSigningKey(this.secret).parseClaimsJws(token).getBody();
        return Long.parseLong(claims.getSubject());
    }

}
package br.com.alura.forum.controller;

import java.net.URI;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;

import javax.transaction.Transactional;
import javax.validation.Valid;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.web.PageableDefault;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.util.UriComponentsBuilder;


@RestController
@RequestMapping("/topicos")
public class TopicosController {

    @Autowired
    private TopicoRepository topicoRepository;

    @Autowired
    private CursoRepository cursoRepository;

    @GetMapping        
    @Cacheable(value = "listaDeTopicos") //esse value vai identificar o cache desse metodo         // se nao receber nenhum parametro na url, vai ordenar pelo id decrescente, a partir da pagina 0, 10 por pagina
    public Page<TopicoDto> lista(@RequestParam(required = false) String nomeCurso, @PageableDefault(sort = "id", direction = Direction.DESC, page = 0, size = 10) Pageable paginacao) { //requestparam avisa pro spring que esses parametros sao de url, obrigatorio // required=false diz que o nome do curso eh opcional


        if(nomeCurso == null) {
            Page<Topico> topicos = topicoRepository.findAll(paginacao);

            return TopicoDto.converter(topicos);
        }
        else {    
            Page<Topico> topicos = topicoRepository.findByCursoNome(nomeCurso, paginacao); // utilizando findBy... é só criar o método no repository que o springdata gera a query automaticamente de acordo com o que vem depois do By

            return TopicoDto.converter(topicos);

        }
    }
    @PostMapping
    @Transactional
    @CacheEvict(value = "listaDeTopicos", allEntries = true)  // vai limpar o cache pra poder ser atualizado
    public ResponseEntity<TopicoDto> cadastrar(@RequestBody @Valid TopicoForm form, UriComponentsBuilder uriBuilder) { // quando é Get, o parametro vem na url, quando é Post, tem que avisar ao spring que o parametro vem no corpo
                                                                                                                        // o @valid  vai validar o que vem do topicoform, que a gente anotou com os requisitos
        Topico topico = form.converter(cursoRepository);
        topicoRepository.save(topico);


        URI uri = uriBuilder.path("/topicos/{id}").buildAndExpand(topico.getId()).toUri();
        return ResponseEntity.created(uri).body(new TopicoDto(topico));
    }
    @GetMapping("/{id}")  // parametro dinamico
    public ResponseEntity<DetalhesDoTopicoDto> detalhar(@PathVariable Long id) {   // esse parametro nao vai vir na interrogacao, ele vai vir na /

        Optional<Topico> topico = topicoRepository.findById(id);
        if(topico.isPresent()) {
            return ResponseEntity.ok(new DetalhesDoTopicoDto(topico.get()));
        }
        return ResponseEntity.notFound().build();
    }
    @PutMapping("/{id}")
    @Transactional
    @CacheEvict(value = "listaDeTopicos", allEntries = true)
    public ResponseEntity<TopicoDto> atualizar(@PathVariable Long id, @RequestBody @Valid AtualizacaoTopicoForm form){
        Optional<Topico> optional = topicoRepository.findById(id);
        if(optional.isPresent()) {
            Topico topico = form.atualizar(id, topicoRepository);
            return ResponseEntity.ok(new TopicoDto(topico));
        }
        return ResponseEntity.notFound().build();



    }
    @DeleteMapping("/{id}")
    @Transactional
    @CacheEvict(value = "listaDeTopicos", allEntries = true)
    public ResponseEntity<TopicoDto> remover(@PathVariable Long id) {
        Optional<Topico> optional = topicoRepository.findById(id);
        if(optional.isPresent()) {
            topicoRepository.deleteById(id);

            return ResponseEntity.ok().build();
        }
            return ResponseEntity.notFound().build();


    }
}

Não consegui identificar o problema :(

Consegue compartilhar o seu projeto? Pode ser via GitHub ou link do google drive

Coloquei agora no githug, é o https://github.com/Shadwz1NN/Forum_API Utilizei o mysql

Oi Artur,

Eu testei aqui o seu projeto e pra mim funcionou. Consegui realizar o delete normalmente:

Insira aqui a descrição dessa imagem para ajudar na acessibilidade

Talvez algum problema com a sua versão do Java. Estou utilizando o Java 17 no meu computador.

solução!

Tambem uso o java 17, já tentei tudo que consegui mas não deu, vou pular essa parte. Obrigado !