Skip to content

Commit

Permalink
Merge branch 'main' into dependabot/docker/maven-3.9.8-amazoncorretto-21
Browse files Browse the repository at this point in the history
  • Loading branch information
LauroSilveira authored Dec 4, 2024
2 parents f3a3906 + 592701c commit b1bc82b
Show file tree
Hide file tree
Showing 18 changed files with 273 additions and 112 deletions.
8 changes: 4 additions & 4 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.4</version>
<version>3.4.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>

Expand All @@ -18,12 +18,12 @@

<properties>
<java.version>21</java.version>
<mapstruct.version>1.5.5.Final</mapstruct.version>
<mapstruct.version>1.6.3</mapstruct.version>
<maven-compiler-plugin.version>3.13.0</maven-compiler-plugin.version>
<springdoc.version>2.5.0</springdoc.version>
<springdoc.version>2.7.0</springdoc.version>
<jwt.version>4.4.0</jwt.version>
<jacoco.version>0.8.12</jacoco.version>
<spring-cloud.version>2023.0.3</spring-cloud.version>
<spring-cloud.version>2024.0.0</spring-cloud.version>
</properties>


Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package com.alura.aluraflixapi.controller.authentication;

import com.alura.aluraflixapi.controller.dto.token.RefreshTokenVO;
import com.alura.aluraflixapi.domain.user.User;
import com.alura.aluraflixapi.domain.user.dto.AuthenticationDto;
import com.alura.aluraflixapi.infraestructure.security.TokenService;
import com.alura.aluraflixapi.infraestructure.security.dto.TokenJwtDto;
import com.alura.aluraflixapi.infraestructure.service.token.RefreshTokenService;
import jakarta.validation.Valid;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
Expand All @@ -21,10 +23,12 @@ public class AuthenticationController {

private final AuthenticationManager authenticationManager;
private final TokenService tokenService;
private final RefreshTokenService refreshTokenService;

public AuthenticationController(AuthenticationManager authenticationManager, TokenService tokenService) {
public AuthenticationController(AuthenticationManager authenticationManager, TokenService tokenService, RefreshTokenService refreshTokenService) {
this.authenticationManager = authenticationManager;
this.tokenService = tokenService;
this.refreshTokenService = refreshTokenService;
}

@PostMapping
Expand All @@ -35,9 +39,17 @@ public ResponseEntity<TokenJwtDto> login(@RequestBody @Valid AuthenticationDto d
dto.password());
//authenticationManager compare the password of the request with the password from database.
final var authentication = this.authenticationManager.authenticate(authenticationToken);
final var tokenJWT = tokenService.generateTokenJWT((User) authentication.getPrincipal());
final var tokenJwt = tokenService.generateTokenJwt((User) authentication.getPrincipal());
final var refreshToken = tokenService.generateRefreshToken(dto.username());
log.info("Token Generated with Success!");
return ResponseEntity.ok().body(new TokenJwtDto(tokenJWT));
return ResponseEntity.ok().body(new TokenJwtDto(tokenJwt, refreshToken));
}

@PostMapping("/refresh")
public ResponseEntity<TokenJwtDto> refreshAccessToken(@RequestBody RefreshTokenVO refreshToken) {
log.info("Request to renew refresh accessToken");
final var tokenJwtDto = refreshTokenService.refreshAccessToken(refreshToken.refreshToken());
return ResponseEntity.ok(tokenJwtDto);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,26 @@
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice(assignableTypes = AuthenticationController.class)
@RestControllerAdvice
public class AuthenticationControllerAdvice {

@ExceptionHandler(UsernameNotFoundException.class)
public ResponseEntity<ErrorMessageVO> handlerAuthenticationException(UsernameNotFoundException ex) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(new ErrorMessageVO(ex.getMessage(), HttpStatus.NOT_FOUND));
}

@ExceptionHandler(BadCredentialsException.class)
public ResponseEntity<ErrorMessageVO> handleBadCredentialsException(BadCredentialsException ex) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(new ErrorMessageVO(ex.getMessage(), HttpStatus.BAD_REQUEST));
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(new ErrorMessageVO("Invalid username or password", HttpStatus.BAD_REQUEST));
}

@ExceptionHandler(AuthenticationException.class)
public ResponseEntity<ErrorMessageVO> handleInvalidTokenException(AuthenticationException ex) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(new ErrorMessageVO("Invalid token, please verify expiration", HttpStatus.BAD_REQUEST));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.alura.aluraflixapi.controller.dto.token;

import jakarta.validation.constraints.NotBlank;

public record RefreshTokenVO(@NotBlank String refreshToken) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.alura.aluraflixapi.domain.token;

import com.alura.aluraflixapi.domain.user.User;
import com.auth0.jwt.JWT;
import lombok.*;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;

import java.time.Instant;

@Document
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
@Builder
public class RefreshToken {
@Id
private String id;
private String token;
private String userName;
private Instant expiryDate;

public static RefreshToken buildRefreshTokenEntity(User user, String accessToken) {
return RefreshToken.builder()
.userName(user.getUsername())
.token(accessToken)
.expiryDate(JWT.decode(accessToken).getExpiresAtAsInstant())
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.alura.aluraflixapi.infraestructure.exception;

public class JwtRefreshTokenExpiredException extends RuntimeException {
public JwtRefreshTokenExpiredException(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.alura.aluraflixapi.infraestructure.repository;

import com.alura.aluraflixapi.domain.token.RefreshToken;
import org.springframework.data.mongodb.repository.MongoRepository;

public interface RefreshTokenRepository extends MongoRepository<RefreshToken, String> {
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@


public interface UserRepository extends MongoRepository<User, String> {
UserDetails findByUsername(String username);
User findByUsername(String username);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.alura.aluraflixapi.infraestructure.security;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerExceptionResolver;

/**
* This class handles unsuccessful JWT exceptions
*/
@Slf4j
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Qualifier("handlerExceptionResolver")
private final HandlerExceptionResolver resolver;

public JwtAuthenticationEntryPoint(HandlerExceptionResolver handlerExceptionResolver) {
this.resolver = handlerExceptionResolver;
}

@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) {
resolver.resolveException(request, response, null, authException);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
public class SecurityConfigurations {

private final SecurityFilter securityFilter;
private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
Expand All @@ -39,13 +40,16 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
.sessionManagement(managementConfigurer ->
managementConfigurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(httpRequest -> httpRequest
.requestMatchers(HttpMethod.POST, "/login").permitAll()
.requestMatchers(HttpMethod.POST, "/login/**").permitAll()
.requestMatchers("/v3/api-docs/**", "/swagger-ui.html", "/swagger-ui/**").permitAll()
//any other request has to be authenticated
.anyRequest().authenticated()
)
//tell to spring to user our filter SecurityFilter.class instead their
//tell to spring to use our filter SecurityFilter.class instead their
.addFilterBefore(securityFilter, UsernamePasswordAuthenticationFilter.class)
//tell to spring to use this filter to handle any exception about JWT exception
.exceptionHandling(exceptionHandler ->
exceptionHandler.authenticationEntryPoint(jwtAuthenticationEntryPoint))
.build();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,62 +5,63 @@
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Objects;
import java.util.Optional;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

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

/**
* Spring validate each request received only once
*/
@Slf4j
@Component
public class SecurityFilter extends OncePerRequestFilter {

private static final String PREFIX_LOGGING = "[SecurityFilter]";
public static final String AUTHORIZATION = "Authorization";
private final TokenService tokenService;
private final UserRepository userRepository;
private static final String PREFIX_LOGGING = "[SecurityFilter]";
public static final String AUTHORIZATION = "Authorization";
private final TokenService tokenService;
private final UserRepository userRepository;

public SecurityFilter(final TokenService tokenService, final UserRepository userRepository) {
this.tokenService = tokenService;
this.userRepository = userRepository;
}
public SecurityFilter(final TokenService tokenService, final UserRepository userRepository) {
this.tokenService = tokenService;
this.userRepository = userRepository;
}

//doFilterInternal is called for each request received
//All request to endpoint/login don´t have token in theirs headers
@Override
protected void doFilterInternal(final HttpServletRequest request,
final HttpServletResponse response,
final FilterChain filterChain) throws ServletException, IOException {
//doFilterInternal is called for each request received
//All request to endpoint/login don´t have accessToken in theirs headers
@Override
protected void doFilterInternal(final HttpServletRequest request,
final HttpServletResponse response,
final FilterChain filterChain) throws ServletException, IOException {

log.info("{} request received intercepted by Internal Filter", PREFIX_LOGGING);
final var tokenJWT = this.getTokenJWT(request);
log.info("{} request received intercepted by Internal Filter", PREFIX_LOGGING);
final var tokenJWT = this.getTokenJWT(request);

if (Objects.nonNull(tokenJWT)) {
//Retrieve user from Token JWT
final var subject = this.tokenService.getSubject(tokenJWT);
var user = this.userRepository.findByUsername(subject);
if (Objects.nonNull(tokenJWT)) {
//Retrieve user from Token JWT
final var username = this.tokenService.verifyTokenJWT(tokenJWT);
final var user = this.userRepository.findByUsername(username);

//after retrieve the user we need to tell to Spring framework to authenticate him in the context
//this is done by calling UsernamePasswordAuthenticationToken and SecurityContextHolder methods
log.info("{} Authenticating user: {} ", PREFIX_LOGGING, user.getUsername());
var authentication = new UsernamePasswordAuthenticationToken(user, null,
user.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authentication);
log.info("{} User authenticated: {}", PREFIX_LOGGING, authentication.getPrincipal());
//after retrieve the user we need to tell to Spring framework to authenticate him in the context
//this is done by calling UsernamePasswordAuthenticationToken and SecurityContextHolder methods
log.info("{} Authenticating user: {} ", PREFIX_LOGGING, user.getUsername());
var authentication = new UsernamePasswordAuthenticationToken(user, null,
user.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authentication);
log.info("{} User authenticated: {}", PREFIX_LOGGING, authentication.getPrincipal());
}
//continue the flow
filterChain.doFilter(request, response);
}
//continue the flow
filterChain.doFilter(request, response);
}

private String getTokenJWT(final HttpServletRequest request) {
return Optional.ofNullable(request.getHeader(AUTHORIZATION))
.map(token -> token.replace("Bearer", "").trim())
.orElse(null);
}
private String getTokenJWT(final HttpServletRequest request) {
return Optional.ofNullable(request.getHeader(AUTHORIZATION))
.map(token -> token.replace("Bearer", "").trim())
.orElse(null);
}
}
Loading

0 comments on commit b1bc82b

Please sign in to comment.