Especialização em Backend com Java & Spring Boot • Demonstrações Práticas Compiláveis


💻 Código de Exemplo 01 — SecurityFilterChain com Lambda DSL e BCryptPasswordEncoder

package com.empresa.api.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        return http
            .csrf(csrf -> csrf.disable())
            .sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/api/v1/auth/**", "/actuator/health").permitAll()
                .anyRequest().authenticated()
            )
            .build();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder(12);
    }
}
package com.empresa.api.service;

import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

@Service
public class CadastroUsuarioService {

    private final PasswordEncoder passwordEncoder;

    public CadastroUsuarioService(PasswordEncoder passwordEncoder) {
        this.passwordEncoder = passwordEncoder;
    }

    public String prepararSenhaParaArmazenar(String senhaEmTextoPuro) {
        // Cada chamada gera um hash diferente (salt aleatório embutido).
        return passwordEncoder.encode(senhaEmTextoPuro);
    }

    public boolean validarLogin(String senhaDigitada, String hashArmazenado) {
        return passwordEncoder.matches(senhaDigitada, hashArmazenado);
    }
}

💻 Código de Exemplo 02 — Tratamento de Exceções: AuthenticationEntryPoint e AccessDeniedHandler

package com.empresa.api.security;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;

import java.io.IOException;

@Component
public class CustomAuthEntryPoint implements AuthenticationEntryPoint {

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response,
                          AuthenticationException authException) throws IOException {
        response.setContentType("application/json");
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        response.getWriter().write("{\"erro\": \"Não autorizado\", \"status\": 401}");
    }
}
package com.empresa.api.security;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;

import java.io.IOException;

@Component
public class CustomAccessDeniedHandler implements AccessDeniedHandler {

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response,
                        AccessDeniedException accessDeniedException) throws IOException {
        response.setContentType("application/json");
        response.setStatus(HttpServletResponse.SC_FORBIDDEN);
        response.getWriter().write("{\"erro\": \"Acesso negado\", \"status\": 403}");
    }
}
package com.empresa.api.config;

import com.empresa.api.security.CustomAccessDeniedHandler;
import com.empresa.api.security.CustomAuthEntryPoint;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity
public class ExceptionHandlingSecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http, CustomAuthEntryPoint entryPoint,
                                                     CustomAccessDeniedHandler deniedHandler) throws Exception {
        return http
            .exceptionHandling(ex -> ex
                .authenticationEntryPoint(entryPoint)
                .accessDeniedHandler(deniedHandler)
            )
            .build();
    }
}

💻 Código de Exemplo 03 — AuthenticationProvider Customizado com Segundo Fator (MFA)

package com.empresa.api.security;

import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;

@Component
public class MfaAuthenticationProvider implements AuthenticationProvider {

    private final UsuarioDetailsService usuarioDetailsService;
    private final PasswordEncoder passwordEncoder;
    private final OtpValidator otpValidator;

    public MfaAuthenticationProvider(UsuarioDetailsService usuarioDetailsService,
                                      PasswordEncoder passwordEncoder,
                                      OtpValidator otpValidator) {
        this.usuarioDetailsService = usuarioDetailsService;
        this.passwordEncoder = passwordEncoder;
        this.otpValidator = otpValidator;
    }

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        String login = authentication.getName();
        String senhaDigitada = authentication.getCredentials().toString();

        var usuario = usuarioDetailsService.loadUserByUsername(login);

        if (!passwordEncoder.matches(senhaDigitada, usuario.getPassword())) {
            throw new BadCredentialsException("Credenciais inválidas");
        }

        // Segundo fator: valida o código OTP enviado junto com a requisição de login.
        String codigoOtp = extrairOtpDaRequisicao(authentication);
        if (!otpValidator.validar(login, codigoOtp)) {
            throw new BadCredentialsException("Código de segundo fator inválido");
        }

        return new UsernamePasswordAuthenticationToken(usuario, null, usuario.getAuthorities());
    }

    private String extrairOtpDaRequisicao(Authentication authentication) {
        if (authentication.getDetails() instanceof String otp) {
            return otp;
        }
        throw new BadCredentialsException("Código OTP ausente");
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
    }
}