💻 Exemplos: Capítulo 09: Spring Security 6 Architecture, SecurityFilterChain e BCrypt
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);
}
}