security project with jwt

This commit is contained in:
rayankonecny 2025-12-14 17:39:17 +00:00
parent 9a36d3f1a8
commit ba691f850f
12 changed files with 320 additions and 2 deletions

View file

@ -0,0 +1,17 @@
package br.com.rayankonecny.authserviceapi.configs;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
@Configuration
public class AuthenticationConfig {
@Bean
public AuthenticationManager authenticationManager(
AuthenticationConfiguration config
) throws Exception {
return config.getAuthenticationManager();
}
}

View file

@ -30,6 +30,6 @@ public interface AuthController {
@ApiResponse(responseCode = "500", description = "Internal server error", content = @Content(mediaType = APPLICATION_JSON_VALUE, schema = @Schema(implementation = StandardError.class))),
})
@PostMapping("/login")
ResponseEntity<AuthenticationResponse> authenticate(@Valid @RequestBody final AuthenticateRequest requests);
ResponseEntity<AuthenticationResponse> authenticate(@Valid @RequestBody final AuthenticateRequest requests) throws Exception;
}

View file

@ -0,0 +1,48 @@
package br.com.rayankonecny.authserviceapi.controllers.exceptions;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import jakarta.servlet.http.HttpServletRequest;
import br.com.rayankonecny.hdcommoslib.models.exceptions.ValidationException;
import br.com.rayankonecny.hdcommoslib.models.exceptions.ResourceNotFoundException;
import br.com.rayankonecny.hdcommoslib.models.exceptions.StandardError;
import static java.time.LocalDateTime.now;
import static org.springframework.http.HttpStatus.BAD_REQUEST;
import static org.springframework.http.HttpStatus.NOT_FOUND;
import java.util.ArrayList;
@ControllerAdvice
public class ControllerExceptionHandler {
@ExceptionHandler(UsernameNotFoundException.class)
ResponseEntity<StandardError> handleNotFoundException(final ResourceNotFoundException ex,
final HttpServletRequest request) {
return ResponseEntity.status(NOT_FOUND).body(
StandardError.builder().timestamp(now()).status(NOT_FOUND.value()).error(NOT_FOUND.getReasonPhrase())
.message(ex.getMessage()).path(request.getRequestURI()).build());
}
@ExceptionHandler(MethodArgumentNotValidException.class)
ResponseEntity<StandardError> handleMethodArgumentNotValidException(final MethodArgumentNotValidException ex,
final HttpServletRequest request) {
var error = ValidationException.builder().timestamp(now()).status(BAD_REQUEST.value()).error("Validation Exception")
.message("Exception in validation attributes").path(request.getRequestURI()).errors(new ArrayList<>()).build();
for (FieldError fieldError : ex.getBindingResult().getFieldErrors()) {
error.addError(fieldError.getField(), fieldError.getDefaultMessage());
}
return ResponseEntity.badRequest().body(error);
}
}

View file

@ -0,0 +1,27 @@
package br.com.rayankonecny.authserviceapi.controllers.impl;
import org.springframework.http.ResponseEntity;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.web.bind.annotation.RestController;
import br.com.rayankonecny.authserviceapi.controllers.AuthController;
import br.com.rayankonecny.authserviceapi.security.JWTAuthenticationImpl;
import br.com.rayankonecny.authserviceapi.utils.JWTUtils;
import br.com.rayankonecny.hdcommoslib.models.requests.AuthenticateRequest;
import br.com.rayankonecny.hdcommoslib.models.responses.AuthenticationResponse;
import lombok.RequiredArgsConstructor;
@RestController
@RequiredArgsConstructor
public class AuthControllerImpl implements AuthController {
private final JWTUtils jwtUtils;
private final AuthenticationConfiguration authenticationConfiguration;
@Override
public ResponseEntity<AuthenticationResponse> authenticate(final AuthenticateRequest requests) throws Exception {
return ResponseEntity.ok().body(
new JWTAuthenticationImpl(jwtUtils, authenticationConfiguration.getAuthenticationManager()).authentication(requests));
}
}

View file

@ -0,0 +1,20 @@
package br.com.rayankonecny.authserviceapi.models;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Set;
import br.com.rayankonecny.hdcommoslib.models.enums.ProfileEnum;
@Getter
@AllArgsConstructor
public class User {
private String id;
private String name;
private String email;
private String password;
private Set<ProfileEnum> profiles;
}

View file

@ -0,0 +1,13 @@
package br.com.rayankonecny.authserviceapi.repository;
import java.util.Optional;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.stereotype.Repository;
import br.com.rayankonecny.authserviceapi.models.User;
@Repository
public interface UserRepository extends MongoRepository<User, String> {
Optional<User> findByEmail(final String email);
}

View file

@ -0,0 +1,40 @@
package br.com.rayankonecny.authserviceapi.security;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import br.com.rayankonecny.authserviceapi.utils.JWTUtils;
import br.com.rayankonecny.hdcommoslib.models.requests.AuthenticateRequest;
import br.com.rayankonecny.hdcommoslib.models.responses.AuthenticationResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
@Log4j2
@RequiredArgsConstructor
public class JWTAuthenticationImpl {
private final JWTUtils jwtUtils;
private final AuthenticationManager authenticationManager;
public AuthenticationResponse authentication(final AuthenticateRequest request) {
try {
log.info("Authenticating user {}:", request.email());
final var authResult = authenticationManager
.authenticate(new UsernamePasswordAuthenticationToken(request.email(), request.password()));
return buildAuthenticationResponse((UserDetailsDTO) authResult.getPrincipal());
} catch (BadCredentialsException e) {
log.error("Error no authenticate user: {}", request.email());
throw new BadCredentialsException("Email or password invalid");
}
}
protected AuthenticationResponse buildAuthenticationResponse(UserDetailsDTO dto) {
log.info("Successfuly authenticated user: {}", dto.getUsername());
final var token = jwtUtils.generateToken(dto);
return AuthenticationResponse.builder().type("Bearer " + token).token(token).build();
}
}

View file

@ -0,0 +1,55 @@
package br.com.rayankonecny.authserviceapi.security;
import java.io.Serial;
import java.util.Collection;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import lombok.Builder;
import lombok.Getter;
@Getter
@Builder
public class UserDetailsDTO implements UserDetails {
@Serial
private static final long serialVersionUID = 1L;
private String id;
private String name;
private String username;
private String password;
private Collection<? extends GrantedAuthority> authorities;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return this.authorities;
}
@Override
public String getPassword() {
return this.password;
}
@Override
public String getUsername() {
return this.username;
}
public String getName() {
return this.name;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
}

View file

@ -0,0 +1,36 @@
package br.com.rayankonecny.authserviceapi.services;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import br.com.rayankonecny.authserviceapi.repository.UserRepository;
import br.com.rayankonecny.authserviceapi.security.UserDetailsDTO;
import lombok.RequiredArgsConstructor;
@Service
@RequiredArgsConstructor
public class UserDetailsServiceImpl implements UserDetailsService {
private final UserRepository repository;
@Override
public UserDetails loadUserByUsername(final String email) throws UsernameNotFoundException {
final var entity = repository.findByEmail(email)
.orElseThrow(() -> new UsernameNotFoundException("User not found: " + email));
return UserDetailsDTO.builder()
.id(entity.getId())
.name(entity.getName())
.username(entity.getEmail())
.password(entity.getPassword())
.authorities(entity.getProfiles().stream().map(x -> new SimpleGrantedAuthority(x.getDescription())).collect(Collectors.toSet()))
.build();
}
}

View file

@ -0,0 +1,33 @@
package br.com.rayankonecny.authserviceapi.utils;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import br.com.rayankonecny.authserviceapi.security.UserDetailsDTO;
@Component
public class JWTUtils {
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expiration}")
private Long expiration;
public String generateToken(final UserDetailsDTO dto) {
return Jwts.builder()
.claim("id", dto.getId())
.claim("name", dto.getName())
.claim("authorities", dto.getAuthorities())
.setSubject(dto.getUsername())
.signWith(SignatureAlgorithm.HS512, secret.getBytes())
.setExpiration(new Date(System.currentTimeMillis()+ expiration))
.compact();
}
}

View file

@ -0,0 +1,23 @@
package br.com.rayankonecny.authserviceapi.utils;
import io.jsonwebtoken.security.Keys;
import javax.crypto.SecretKey;
import java.util.Base64;
public final class JwtKeyProvider {
// 🔥 REGRA DE OURO:
// Isso NÃO deve ficar hardcoded em produção.
// Use ENV VAR, Vault, Kubernetes Secret, etc.
private static final String BASE64_SECRET = "c3VwZXItc2VndXJhLWNoYXZlLWp3dC1jb20tMzItYnl0ZXM=";
private JwtKeyProvider() {
// evita instância acidental
}
public static SecretKey getKey() {
byte[] keyBytes = Base64.getDecoder().decode(BASE64_SECRET);
return Keys.hmacShaKeyFor(keyBytes);
}
}

View file

@ -1,4 +1,10 @@
spring:
application:
name: "auth-service-api"
data:
mongodb:
uri: mongodb://175.15.15.6:27017/olympdb
auto-index-creation: true
jwt.secret: "IHf3Yua/byvtA+iIcGWmkrLvpKEXTb5ClkXaZ0VDmYbr/6b1otCs38x68bidvZLAOB7anUtVQlCid6YDULO5XA=="
jwt.expiration: 120000