1
resposta

[Projeto] Lançar exceção para uma rota protegida por Role

Oi! estou tentando lançar uma exceção para quando o usuário tenta acessar uma rota que não é permitida para o tipo de ROLE dele.

Exemplo: /cursos (POST, PUT e DELETE) só podem ser executados pelos usuários que tenham um ROLE_PROFESSOR

@Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception{
        return http.csrf(AbstractHttpConfigurer::disable)
            .sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
            .authorizeHttpRequests(auth -> {
                        auth.requestMatchers(HttpMethod.POST, "/usuarios/**").permitAll();
                        auth.requestMatchers("/usuarios/**").authenticated();

                        auth.requestMatchers(HttpMethod.POST, "/cursos").hasRole("PROFESSOR");
                        auth.requestMatchers(HttpMethod.PUT, "/cursos").hasRole("PROFESSOR");
                        auth.requestMatchers(HttpMethod.DELETE, "/cursos").hasRole("PROFESSOR");

                        auth.anyRequest().authenticated();
            })
            .addFilterBefore(this.securityFilter, UsernamePasswordAuthenticationFilter.class)
            .build();
    }
@Service
public class CursoService {

    @Autowired
    private CursoRepository cursoRepository;

    @Autowired
    private ValidadorRegistroCurso validadorRegistroCurso;

    @Autowired
    private ValidadorRegistroFeitoPorProfessor registroFeitoPorProfessor;

    public CursoDadosDetalhados registrar(CursoRegistro cursoRegistro, Usuario usuario){
        this.registroFeitoPorProfessor.validar(usuario);
        this.validadorRegistroCurso.validar(cursoRegistro);

        Curso curso = new Curso(cursoRegistro);
        var cursoSalvo = this.cursoRepository.save(curso);

        return new CursoDadosDetalhados(cursoSalvo);
    }

    public CursoDadosDetalhados buscar(Long id){
        var curso = this.cursoRepository.getReferenceById(id);
        return new CursoDadosDetalhados(curso);
    }
}
@Component
public class ValidadorRegistroFeitoPorProfessor {

    public void validar(Usuario usuario){
        boolean possuiPerfilProfessor = usuario.getAuthorities()
                .stream()
                .map(GrantedAuthority::getAuthority)
                .anyMatch(auth -> auth.equals("ROLE_" + Perfil.PROFESSOR.name()));

        if (!possuiPerfilProfessor){
            throw new AccessDeniedException("Somente usuários com perfil PROFESSOR podem realizar ações para curso.");
        }
    }
}

quando tento fazer uma requisição com um token de aluno, ele não permite, mas somente lança um body vazio e o http status 403.

@PostMapping
    public ResponseEntity registrar(@RequestBody @Valid CursoRegistro cursoRegistro, UriComponentsBuilder uriBuilder, Authentication auth){
        var usuario = (Usuario) auth.getPrincipal();
        var curso = this.cursoService.registrar(cursoRegistro, usuario);

        var uri = uriBuilder
                .path("/cursos/{id}")
                .buildAndExpand(curso.id())
                .toUri();

        return ResponseEntity.created(uri).body(curso);
    }
1 resposta

Oi Natalia! Tudo bem?

O comportamento que você está observando — um corpo vazio e um status HTTP 403 — é esperado quando um usuário não autorizado tenta acessar uma rota protegida. Isso significa que o Spring Security está bloqueando corretamente o acesso.

Se você deseja personalizar a resposta quando um usuário não autorizado tenta acessar uma rota, você pode implementar um AccessDeniedHandler personalizado. Isso permitirá que você defina um corpo de resposta mais informativo ou até mesmo logar a tentativa de acesso. Aqui está um exemplo de como você pode fazer isso:

import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component
public class CustomAccessDeniedHandler implements AccessDeniedHandler {

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) 
            throws IOException, ServletException {
        response.setStatus(HttpServletResponse.SC_FORBIDDEN);
        response.getWriter().write("Acesso negado: você não tem permissão para acessar este recurso.");
    }
}

Em seguida, você precisa registrar esse handler no seu SecurityFilterChain:

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    return http.csrf(AbstractHttpConfigurer::disable)
        .sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
        .authorizeHttpRequests(auth -> {
            auth.requestMatchers(HttpMethod.POST, "/usuarios/**").permitAll();
            auth.requestMatchers("/usuarios/**").authenticated();
            auth.requestMatchers(HttpMethod.POST, "/cursos").hasRole("PROFESSOR");
            auth.requestMatchers(HttpMethod.PUT, "/cursos").hasRole("PROFESSOR");
            auth.requestMatchers(HttpMethod.DELETE, "/cursos").hasRole("PROFESSOR");
            auth.anyRequest().authenticated();
        })
        .exceptionHandling(exception -> exception.accessDeniedHandler(new CustomAccessDeniedHandler()))
        .addFilterBefore(this.securityFilter, UsernamePasswordAuthenticationFilter.class)
        .build();
}

Com isso, quando um usuário sem a role necessária tenta acessar a rota, ele receberá a mensagem "Acesso negado: você não tem permissão para acessar este recurso." no corpo da resposta.

Espero ter ajudado e bons estudos!

Caso este post tenha lhe ajudado, por favor, marcar como solucionado ✓.