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))),
|
@ApiResponse(responseCode = "500", description = "Internal server error", content = @Content(mediaType = APPLICATION_JSON_VALUE, schema = @Schema(implementation = StandardError.class))),
|
||||||
})
|
})
|
||||||
@PostMapping("/login")
|
@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:
|
spring:
|
||||||
application:
|
application:
|
||||||
name: "auth-service-api"
|
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