security project with jwt
This commit is contained in:
parent
9a36d3f1a8
commit
ba691f850f
12 changed files with 320 additions and 2 deletions
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue