Pular para o conteúdo principal

Capítulo 5: Security Avançada - Módulos 11 a 18

Objetivos​

  • Dominar Spring Security de forma aprofundada
  • Implementar uma segurança robusta passo a passo
  • Compreender cada componente da cadeia de segurança

Recurso Principal​

Anexos Security

Este capítulo é baseado nos annexes-security-1 do repositório, um excelente guia passo a passo:

** Pasta annexes-security-1**

Módulos 11 a 18: Um projeto completo de segurança Spring Boot.


Plano dos Módulos Security​

MóduloTítuloConteúdo
11Introdução ao Spring SecurityConceitos básicos, arquitetura
12Configuração inicialSecurityFilterChain, HttpSecurity
13AutenticaçãoUserDetails, UserDetailsService
14Codificação de senhasBCryptPasswordEncoder
15JWT - TeoriaEstrutura, claims, assinatura
16JWT - ImplementaçãoJwtService, geração, validação
17Filtros personalizadosJwtAuthenticationFilter
18AutorizaçãoPapéis, @PreAuthorize, segurança de método

Módulo 11: Introdução ao Spring Security​

O que é Spring Security?​

Spring Security é o framework padrão de segurança para aplicações Spring. Ele fornece:

  • Autenticação: Verifica a identidade do usuário
  • Autorização: Controla o acesso aos recursos
  • Proteção contra ataques: CSRF, XSS, etc.

Arquitetura do Spring Security​

┌───────────────────────────────────────────────────────────┐
│ SecurityFilterChain │
│ │
│ Request ──► Filter 1 ──► Filter 2 ──► ... ──► Controller
│ │
│ Cada filtro pode: │
│ - Deixar passar a requisição │
│ - Bloquear a requisição (401/403) │
│ - Modificar a requisição │
└───────────────────────────────────────────────────────────┘

Dependência Maven​

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>

Módulo 12: Configuração Inicial​

SecurityConfig básica​

@Configuration
@EnableWebSecurity
public class SecurityConfig {

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.csrf(csrf -> csrf.disable()) // Desativar CSRF para API REST
.authorizeHttpRequests(auth -> auth
.anyRequest().permitAll() // Permitir tudo para começar
)
.build();
}
}

Configuração progressiva​

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth
// Endpoints públicos
.requestMatchers("/api/contact").permitAll()
.requestMatchers("/api/auth/**").permitAll()
.requestMatchers("/h2-console/**").permitAll()

// Endpoints protegidos
.requestMatchers("/api/admin/**").authenticated()

// Todo o resto
.anyRequest().authenticated()
)
.headers(headers -> headers.frameOptions(f -> f.disable())) // Para H2 console
.build();
}

Módulo 13: Autenticação​

Entidade User​

@Entity
@Table(name = "users")
public class User implements UserDetails {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(unique = true)
private String email;

private String password;

@Enumerated(EnumType.STRING)
private Role role;

@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return List.of(new SimpleGrantedAuthority("ROLE_" + role.name()));
}

@Override
public String getUsername() {
return email;
}

@Override
public boolean isAccountNonExpired() { return true; }

@Override
public boolean isAccountNonLocked() { return true; }

@Override
public boolean isCredentialsNonExpired() { return true; }

@Override
public boolean isEnabled() { return true; }
}

UserDetailsService​

@Service
@RequiredArgsConstructor
public class UserDetailsServiceImpl implements UserDetailsService {

private final UserRepository userRepository;

@Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
return userRepository.findByEmail(email)
.orElseThrow(() -> new UsernameNotFoundException("Usuário não encontrado: " + email));
}
}

Configuração do AuthenticationProvider​

@Configuration
public class SecurityConfig {

@Bean
public AuthenticationProvider authenticationProvider(
UserDetailsService userDetailsService,
PasswordEncoder passwordEncoder) {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(userDetailsService);
provider.setPasswordEncoder(passwordEncoder);
return provider;
}

@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration config)
throws Exception {
return config.getAuthenticationManager();
}
}

Módulo 14: Codificação de Senhas​

BCryptPasswordEncoder​

@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}

Utilização no cadastro​

@Service
@RequiredArgsConstructor
public class AuthService {

private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;

public User register(RegisterRequest request) {
User user = new User();
user.setEmail(request.getEmail());
user.setPassword(passwordEncoder.encode(request.getPassword())); // Codificação!
user.setRole(Role.USER);
return userRepository.save(user);
}
}

Verificação no login​

public String login(LoginRequest request) {
// O AuthenticationManager gerencia automaticamente a verificação da senha
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
request.getEmail(),
request.getPassword()
)
);

// Se chegou aqui, a autenticação foi bem-sucedida
User user = (User) authentication.getPrincipal();
return jwtService.generateToken(user);
}

Módulo 15: JWT - Teoria​

Estrutura de um JWT​

Um JWT (JSON Web Token) é composto por três partes separadas por pontos:

xxxxx.yyyyy.zzzzz
│ │ │
│ │ └── Assinatura
│ └── Payload (Claims)
└── Header
{
"alg": "HS256",
"typ": "JWT"
}

Payload (Claims)​

{
"sub": "user@example.com", // Subject (identificador)
"iat": 1609459200, // Issued At (data de criação)
"exp": 1609545600, // Expiração
"roles": ["USER"] // Claims personalizados
}

Assinatura​

HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret_key
)

Módulo 16: JWT - Implementação​

Dependência​

<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.12.3</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.12.3</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.12.3</version>
<scope>runtime</scope>
</dependency>

JwtService​

@Service
public class JwtService {

@Value("${jwt.secret}")
private String secretKey;

@Value("${jwt.expiration}")
private long expiration; // Em milissegundos

// Gerar um token
public String generateToken(UserDetails userDetails) {
return Jwts.builder()
.subject(userDetails.getUsername())
.claim("roles", userDetails.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.toList())
.issuedAt(new Date())
.expiration(new Date(System.currentTimeMillis() + expiration))
.signWith(getSigningKey())
.compact();
}

// Extrair o username
public String extractUsername(String token) {
return extractClaim(token, Claims::getSubject);
}

// Validar o token
public boolean isTokenValid(String token, UserDetails userDetails) {
String username = extractUsername(token);
return username.equals(userDetails.getUsername()) && !isTokenExpired(token);
}

private boolean isTokenExpired(String token) {
return extractClaim(token, Claims::getExpiration).before(new Date());
}

private <T> T extractClaim(String token, Function<Claims, T> resolver) {
Claims claims = Jwts.parser()
.verifyWith(getSigningKey())
.build()
.parseSignedClaims(token)
.getPayload();
return resolver.apply(claims);
}

private SecretKey getSigningKey() {
byte[] keyBytes = Decoders.BASE64.decode(secretKey);
return Keys.hmacShaKeyFor(keyBytes);
}
}

Configuração application.yml​

jwt:
secret: "sua-chave-secreta-bem-grande-com-pelo-menos-256-bits-codificada-em-base64"
expiration: 86400000 # 24 horas em milissegundos

Módulo 17: Filtros Personalizados​

JwtAuthenticationFilter​

@Component
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {

private final JwtService jwtService;
private final UserDetailsService userDetailsService;

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

// 1. Recuperar o header Authorization
String authHeader = request.getHeader("Authorization");

// 2. Verificar se o header está presente e começa com "Bearer "
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
filterChain.doFilter(request, response);
return;
}

// 3. Extrair o token
String token = authHeader.substring(7);
String username = jwtService.extractUsername(token);

// 4. Se username encontrado e ainda não autenticado
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {

// 5. Carregar o usuário
UserDetails userDetails = userDetailsService.loadUserByUsername(username);

// 6. Validar o token
if (jwtService.isTokenValid(token, userDetails)) {

// 7. Criar a autenticação
UsernamePasswordAuthenticationToken authToken =
new UsernamePasswordAuthenticationToken(
userDetails,
null,
userDetails.getAuthorities()
);
authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));

// 8. Colocar no SecurityContext
SecurityContextHolder.getContext().setAuthentication(authToken);
}
}

// 9. Continuar a cadeia de filtros
filterChain.doFilter(request, response);
}
}

Integração no SecurityConfig​

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/contact").permitAll()
.requestMatchers("/api/auth/**").permitAll()
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.sessionManagement(session ->
session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)
.authenticationProvider(authenticationProvider)
// Adicionar nosso filtro ANTES do UsernamePasswordAuthenticationFilter
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
.build();
}

Módulo 18: Autorização​

Anotações de Segurança​

@Configuration
@EnableMethodSecurity // Ativa @PreAuthorize, @PostAuthorize, etc.
public class SecurityConfig {
// ...
}

@PreAuthorize​

@RestController
@RequestMapping("/api/admin")
public class AdminController {

// Acessível apenas para ADMIN
@GetMapping("/leads")
@PreAuthorize("hasRole('ADMIN')")
public List<LeadDto> getAllLeads() {
return leadService.getAllLeads();
}

// Acessível para ADMIN e MANAGER
@GetMapping("/leads/stats")
@PreAuthorize("hasAnyRole('ADMIN', 'MANAGER')")
public StatsDto getStats() {
return leadService.getStats();
}

// Verificação dinâmica
@GetMapping("/users/{id}")
@PreAuthorize("#id == principal.id or hasRole('ADMIN')")
public UserDto getUser(@PathVariable Long id) {
return userService.getUser(id);
}
}

@PostAuthorize​

@GetMapping("/{id}")
@PostAuthorize("returnObject.email == principal.username or hasRole('ADMIN')")
public LeadDto getLead(@PathVariable Long id) {
return leadService.getLead(id);
}

Segurança no nível de Service​

@Service
public class LeadService {

@PreAuthorize("hasRole('ADMIN')")
public void deleteLead(Long id) {
leadRepository.deleteById(id);
}

@PreAuthorize("isAuthenticated()")
public List<LeadDto> getMyLeads() {
// Recuperar o usuário logado
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String email = auth.getName();
return leadRepository.findByCreatedBy(email);
}
}

Arquitetura Final Completa​

┌─────────────────────────────────────────────────────────────────┐
│ Client Request │
│ Authorization: Bearer <token> │
└───────────────────────────────┬─────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ SecurityFilterChain │
│ │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ 1. CorsFilter │ │
│ │ - Gerencia requisições cross-origin │ │
│ └───────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ 2. JwtAuthenticationFilter │ │
│ │ - Extrai o token do header │ │
│ │ - Valida o token │ │
│ │ - Carrega UserDetails │ │
│ │ - Cria Authentication │ │
│ │ - Coloca no SecurityContext │ │
│ └───────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ 3. AuthorizationFilter │ │
│ │ - Verifica as autorizações │ │
│ │ - hasRole(), hasAuthority() │ │
│ │ - Retorna 403 se não autorizado │ │
│ └───────────────────────────────────────────────────────────┘ │
└───────────────────────────────┬─────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Controller │
│ @PreAuthorize verificado │
└─────────────────────────────────────────────────────────────────┘

Checklist de Domínio​

Após estudar estes módulos, você deve ser capaz de:

  • Configurar SecurityFilterChain
  • Implementar UserDetails e UserDetailsService
  • Codificar senhas com BCrypt
  • Gerar e validar tokens JWT
  • Criar um filtro de autenticação personalizado
  • Utilizar @PreAuthorize para autorização
  • Escrever testes de segurança

Recurso Completo​

Acesso Direto

Para o detalhamento completo de cada módulo com todos os exemplos de código:

** Anexos Security - Módulos 11 a 18**

É um excelente projeto de segurança para estudar a fundo!


Parabéns!​

Você concluiu o Módulo 16 do projeto e-contact-backend. Agora você:

  • Configurou um ambiente com GitHub Codespaces
  • Dominou troubleshooting no Spring Boot
  • Integração de frontend com backend
  • Aprofundou conceitos de segurança
  • Compreendeu Spring Security de A a Z

⏭️ Próximos Passos​

  1. Praticar: Clone o repositório e experimente
  2. Modificar: Adicione novas funcionalidades
  3. Testar: Escreva seus próprios testes de segurança
  4. Deploy: Coloque em produção em um cloud provider

Voltar ao índice do módulo | Voltar para o início