Skip to content
Snippets Groups Projects
Commit 281f51da authored by hugcubi's avatar hugcubi
Browse files

Servicio de auth como middleware

parent c699e53f
No related branches found
No related tags found
2 merge requests!36Develop,!31Dev/refactor hotels booking
Showing
with 379 additions and 166 deletions
......@@ -53,7 +53,7 @@ public class UserAPI {
*/
public User registerUser(RegisterRequest registerRequest) {
String url = USER_API_URL;
System.out.println(registerRequest + " " + registerRequest.getPassword());
System.out.println(url + " " + registerRequest);
ResponseEntity<User> userResponse = restTemplate.postForEntity(url, registerRequest, User.class);
if (!userResponse.getStatusCode().is2xxSuccessful()) {
String errorMessage = "Failed to register user: " + userResponse.getStatusCode() + ". " + userResponse.getBody();
......@@ -80,4 +80,10 @@ public class UserAPI {
restTemplate.put(url, body, id);
}
public void deleteUser(User user) {
String url = USER_API_URL + "/{id}";
restTemplate.delete(url, user.getId());
}
}
......@@ -7,19 +7,16 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
import com.uva.authentication.interceptor.AuthHttpInterceptor;
@Configuration
public class RestTemplateConfig {
@Autowired
private AuthHttpInterceptor jwtInterceptor;
private RestTemplateInterceptor interceptor;
@Bean
RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
restTemplate.setInterceptors(List.of(jwtInterceptor));
restTemplate.setInterceptors(List.of(interceptor));
return restTemplate;
}
......
......
package com.uva.authentication.interceptor;
package com.uva.authentication.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.stereotype.Component;
import com.uva.authentication.models.remote.User;
import com.uva.authentication.models.remote.UserRol;
import com.uva.authentication.utils.JwtUtil;
import java.io.IOException;
@Component
public class AuthHttpInterceptor implements ClientHttpRequestInterceptor {
public class RestTemplateInterceptor implements ClientHttpRequestInterceptor {
@Autowired
private JwtUtil jwtUtil;
private String token;
private final User USER = new User(-1, "auth", "auth@dev.com", null, UserRol.ADMIN);
private String getAccessToken() {
if (token == null || token.isEmpty()) {
// TODO cambiar también si el token ha caducado
token = jwtUtil.generateToken(USER);
}
return token;
}
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
throws IOException {
// Generar o cargar el JWT token desde el bean JwtUtil
String jwtToken = getAccessToken();
// System.out.println("Using token " + jwtToken);
// Agregar el token al encabezado Authorization
request.getHeaders().add("Authorization", "Bearer " + jwtToken);
// Añadir el encabezado "Authorization" con el valor "Bearer <token>"
HttpHeaders headers = request.getHeaders();
headers.add("Authorization",
"Bearer " + jwtUtil.getOwnInternalToken());
// Continuar con la ejecución de la solicitud
// Continuar con la solicitud
return execution.execute(request, body);
}
}
......@@ -14,6 +14,7 @@ import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
@RestController
@RequestMapping("auth")
@CrossOrigin(origins = "*")
public class AuthController {
......@@ -23,55 +24,52 @@ public class AuthController {
@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody LoginRequest loginRequest) {
try {
String token = authService.login(loginRequest);
return ResponseEntity.ok(new JwtAuthResponse(token));
return authService.login(loginRequest);
} catch (HttpClientErrorException e) {
if (e.getStatusCode() == HttpStatus.FORBIDDEN) {
return new ResponseEntity<String>(e.getMessage(), HttpStatus.FORBIDDEN);
return new ResponseEntity<>(e.getMessage(), HttpStatus.FORBIDDEN);
}
}
return new ResponseEntity<String>("Algo no fue bien", HttpStatus.UNAUTHORIZED);
return new ResponseEntity<>("Algo no fue bien", HttpStatus.UNAUTHORIZED);
}
@PostMapping("/register")
public ResponseEntity<?> register(@RequestBody RegisterRequest registerRequest) {
try {
String token = authService.register(registerRequest);
return ResponseEntity.ok(new JwtAuthResponse(token));
return authService.register(registerRequest);
} catch (HttpClientErrorException e) {
if (e.getStatusCode() == HttpStatus.CONFLICT) {
return new ResponseEntity<String>(e.getMessage(), HttpStatus.CONFLICT);
}
e.printStackTrace(System.err);
}
return new ResponseEntity<String>("Algo no fue bien", HttpStatus.UNAUTHORIZED);
if (e.getStatusCode() == HttpStatus.CONFLICT)
return new ResponseEntity<>(e.getMessage(), HttpStatus.CONFLICT);
}
private boolean validStrings(String... args) {
for (String arg : args) {
if (arg == null || arg.isBlank())
return false;
}
return true;
return new ResponseEntity<>("Algo no fue bien", HttpStatus.UNAUTHORIZED);
}
@PostMapping("/password")
public ResponseEntity<?> postMethodName(@RequestBody Map<String, String> json) {
// TODO adaptar a comportamiento de admin
String email = json.get("email");
String actualPassword = json.get("actual");
String newPassword = json.get("new");
public ResponseEntity<?> changePassword(@RequestBody Map<String, String> json,
@RequestHeader(value = "Authorization", required = false) String authorization) {
if (authorization == null || !authorization.startsWith("Bearer "))
return new ResponseEntity<>(HttpStatus.FORBIDDEN);
if (!validStrings(email, actualPassword, newPassword))
return new ResponseEntity<Void>(HttpStatus.BAD_REQUEST);
String token = authorization.substring(7);
try {
// TODO extraer información del token?
String token = authService.changePassword(email, actualPassword, newPassword);
return ResponseEntity.ok(new JwtAuthResponse(token));
} catch (Exception e) {
return new ResponseEntity<Void>(HttpStatus.BAD_REQUEST);
String actualPassword = json.get("password");
String newPassword = json.get("newPassword");
return authService.changePassword(token, actualPassword, newPassword);
}
@PostMapping("/delete/{id}")
public Object postMethodName(@PathVariable int id, @RequestBody Map<String, String> json,
@RequestHeader(value = "Authorization", required = false) String authorization) {
if (authorization == null || !authorization.startsWith("Bearer "))
return new ResponseEntity<>(HttpStatus.FORBIDDEN);
String token = authorization.substring(7);
String actualPassword = json.get("password");
return authService.deleteUser(token, id, actualPassword);
}
}
package com.uva.authentication.controllers;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.fasterxml.jackson.databind.JsonNode;
import com.uva.authentication.models.JwtAuth;
import com.uva.authentication.services.TokenService;
@RestController
@RequestMapping("/token")
public class TokenController {
@Autowired
private TokenService tokenService;
@PostMapping("/validate")
public ResponseEntity<?> validateToken(@RequestBody JwtAuth tokenRequest) {
boolean isValid = tokenService.validateToken(tokenRequest.getToken());
if (isValid) {
return ResponseEntity.ok("Token is valid");
} else {
return new ResponseEntity<>("Token not valid or expired", HttpStatus.UNAUTHORIZED);
}
}
@PostMapping("/info")
public ResponseEntity<?> getTokenInfo(@RequestBody JwtAuth tokenRequest) {
return tokenService.getTokenInf(tokenRequest.getToken());
}
@PostMapping("/service")
public ResponseEntity<?> identifyService(@RequestBody JsonNode request) {
JsonNode name = request.get("service");
if (name == null)
return new ResponseEntity<>("Missing required fields", HttpStatus.BAD_REQUEST);
return tokenService.identifyService(name.asText());
}
}
package com.uva.authentication.exceptions;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(HotelNotFoundException.class)
public ResponseEntity<Map<String, Object>> handleHotelNotFound(HotelNotFoundException ex) {
Map<String, Object> body = new HashMap<>();
body.put("timestamp", LocalDateTime.now());
body.put("message", ex.getMessage());
return new ResponseEntity<>(body, HttpStatus.NOT_FOUND);
}
@ExceptionHandler(InvalidRequestException.class)
public ResponseEntity<Map<String, Object>> handleInvalidRequest(InvalidRequestException ex) {
Map<String, Object> body = new HashMap<>();
body.put("timestamp", LocalDateTime.now());
body.put("message", ex.getMessage());
return new ResponseEntity<>(body, HttpStatus.BAD_REQUEST);
}
@ExceptionHandler(InvalidDateRangeException.class)
public ResponseEntity<Map<String, Object>> handleInvalidDateRange(InvalidDateRangeException ex) {
Map<String, Object> body = new HashMap<>();
body.put("timestamp", LocalDateTime.now());
body.put("message", ex.getMessage());
return new ResponseEntity<>(body, HttpStatus.BAD_REQUEST);
}
// @ExceptionHandler(Exception.class)
// public ResponseEntity<Map<String, Object>> handleGeneralException(Exception
// ex) {
// Map<String, Object> body = new HashMap<>();
// body.put("timestamp", LocalDateTime.now());
// body.put("message", "An unexpected error occurred: " + ex.getMessage());
// return new ResponseEntity<>(body, HttpStatus.INTERNAL_SERVER_ERROR);
// }
}
package com.uva.authentication.exceptions;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(HttpStatus.NOT_FOUND) // Devuelve un 404 cuando se lanza la excepción
public class HotelNotFoundException extends RuntimeException {
public HotelNotFoundException(int id) {
super("Hotel not found with id: " + id);
}
}
package com.uva.authentication.exceptions;
public class InvalidDateRangeException extends RuntimeException {
public InvalidDateRangeException(String message) {
super(message);
}
}
package com.uva.authentication.exceptions;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(HttpStatus.BAD_REQUEST)
public class InvalidRequestException extends RuntimeException {
public InvalidRequestException(String message) {
super(message);
}
}
package com.uva.authentication.models;
import com.uva.authentication.models.remote.UserRol;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@AllArgsConstructor
@NoArgsConstructor
@Setter
@Getter
public class AuthResponse {
private int id;
private String name;
private String email;
private String password;
private UserRol rol;
@Setter
@Data
@NoArgsConstructor
@AllArgsConstructor
public class JwtAuth {
private String token;
}
package com.uva.authentication.models;
public class JwtAuthResponse {
private String token;
public JwtAuthResponse(String token) {
this.token = token;
}
// Getter
public String getToken() {
return token;
}
}
package com.uva.authentication.models;
import java.lang.reflect.Field;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@Data
public class TokenData {
private Integer id;
private String name;
private String email;
private String rol;
private String service;
private String subject;
private String audience;
private Long ttl;
public TokenData(DecodedJWT decoded, long ttl) {
subject = decoded.getSubject();
audience = decoded.getAudience().get(0);
this.ttl = ttl;
for (Field field : this.getClass().getDeclaredFields()) {
field.setAccessible(true);
// Verificamos si el campo está en el mapa y asignamos el valor
Claim claim = decoded.getClaim(field.getName());
if (!claim.isMissing()) {
try {
// Dependiendo del tipo de campo, asignamos el valor
if (field.getType() == Integer.class) {
field.set(this, Integer.parseInt(claim.asString()));
} else if (field.getType() == String.class) {
field.set(this, claim.asString());
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
public boolean isAdmin() {
return rol != null && rol == "ADMIN";
}
}
\ No newline at end of file
package com.uva.authentication.models;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@Data
@NoArgsConstructor
@AllArgsConstructor
public class TokenRequest {
private String token;
}
......@@ -16,6 +16,7 @@ public class User extends RegisterRequest {
public User(int id, String email, String password, String name, UserRol rol) {
super();
this.id = id;
setEmail(email);
setName(name);
setPassword(password);
......
......
package com.uva.authentication.models.remote;
public enum UserRol {
ADMIN, AUTH, HOTEL_ADMIN, CLIENT
ADMIN, HOTEL_ADMIN, CLIENT
}
......@@ -3,12 +3,16 @@ package com.uva.authentication.services;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.servlet.function.EntityResponse;
import com.uva.authentication.api.UserAPI;
import com.uva.authentication.models.JwtAuth;
import com.uva.authentication.models.LoginRequest;
import com.uva.authentication.models.RegisterRequest;
import com.uva.authentication.models.TokenData;
import com.uva.authentication.models.remote.User;
import com.uva.authentication.utils.JwtUtil;
import com.uva.authentication.utils.SecurityUtils;
......@@ -35,17 +39,17 @@ public class AuthService {
* @return token for identify the user
* @throws HttpClientErrorException(FORBIDDEN) if the credentials are invalid
*/
public String login(LoginRequest loginRequest) {
public ResponseEntity<?> login(LoginRequest loginRequest) {
User user = userAPI.getUserByEmail(loginRequest.getEmail());
if (!authenticateUser(loginRequest, user)) {
if (!authenticateUser(loginRequest, user))
throw new HttpClientErrorException(HttpStatus.FORBIDDEN, "Invalid credentials");
}
return jwtUtil.generateToken(user);
String token = jwtUtil.generateToken(user);
return ResponseEntity.ok(new JwtAuth(token));
}
public String register(RegisterRequest registerRequest) {
public ResponseEntity<?> register(RegisterRequest registerRequest) {
String plainTextPassword = registerRequest.getPassword();
// Ciframos la contraseña
String hashPass = SecurityUtils.encrypt(plainTextPassword);
......@@ -56,21 +60,66 @@ public class AuthService {
BeanUtils.copyProperties(user, logReq);
// Recuperamos la contraseña y lo loggeamos
logReq.setPassword(plainTextPassword);
System.err.println(logReq);
return login(logReq);
}
public String changePassword(String email, String actualPass, String newPass) {
private boolean validStrings(String... args) {
for (String arg : args) {
if (arg == null || arg.isBlank())
return false;
}
return true;
}
private User getUser(String email, String password) {
User user = userAPI.getUserByEmail(email);
// Validamos la anterior contraseña
if (SecurityUtils.checkPassword(actualPass, user.getPassword())) {
boolean correctPassword = SecurityUtils.checkPassword(password, user.getPassword());
return correctPassword ? user : null;
}
public ResponseEntity<?> changePassword(String token, String actualPass, String newPass) {
TokenData decoded = jwtUtil.decodeToken(token);
if (decoded == null)
return new ResponseEntity<>(HttpStatus.FORBIDDEN);
String email = decoded.getEmail();
User user = getUser(email, actualPass);
boolean changePasswordAllowed = decoded.isAdmin() || user != null;
if (user != null && !validStrings(actualPass, newPass))
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
if (changePasswordAllowed) {
// Actualizamos la nueva
String hashPass = SecurityUtils.encrypt(newPass);
userAPI.changePassword(user, hashPass);
// Hacemos un login con los nuevos datos
return login(new LoginRequest(email, newPass));
} else {
throw new HttpClientErrorException(HttpStatus.FORBIDDEN, "Invalid credentials");
return new ResponseEntity<>("Invalid credentials", HttpStatus.FORBIDDEN);
}
}
public ResponseEntity<?> deleteUser(String token, int id, String password) {
TokenData decoded = jwtUtil.decodeToken(token);
if (decoded == null)
return new ResponseEntity<>(HttpStatus.FORBIDDEN);
String email = decoded.getEmail();
User user = getUser(email, password);
boolean changePasswordAllowed = decoded.isAdmin()
|| (user != null && user.getId() == id);
if (user != null && !validStrings(password))
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
if (changePasswordAllowed) {
userAPI.deleteUser(user);
return new ResponseEntity<>(HttpStatus.ACCEPTED);
} else {
return new ResponseEntity<>("Invalid credentials", HttpStatus.FORBIDDEN);
}
}
}
package com.uva.authentication.services;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import com.uva.authentication.models.JwtAuth;
import com.uva.authentication.models.TokenData;
import com.uva.authentication.utils.JwtUtil;
@Service
public class TokenService {
@Autowired
private JwtUtil jwtUtil;
public boolean validateToken(String token) {
return jwtUtil.validate(token) != null;
}
public ResponseEntity<?> identifyService(String name) {
if (name == null)
return new ResponseEntity<>("Token has expire or is malformed", HttpStatus.FORBIDDEN);
String token = jwtUtil.generateInternalToken(name);
return ResponseEntity.ok(new JwtAuth(token));
}
public ResponseEntity<?> getTokenInf(String token) {
TokenData decoded = jwtUtil.decodeToken(token);
if (decoded == null)
return new ResponseEntity<>("Token has expire or is malformed", HttpStatus.FORBIDDEN);
return ResponseEntity.ok(decoded);
}
}
package com.uva.authentication.utils;
import java.util.Date;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.client.HttpClientErrorException;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.uva.authentication.models.TokenData;
import com.uva.authentication.models.remote.User;
import com.auth0.jwt.interfaces.DecodedJWT;
import java.time.Instant;
@Component
public class JwtUtil {
@Value("${security.jwt.kid}")
private String kid;
@Value("${security.jwt.secret-key}")
private String secretKey;
@Value("${security.jwt.kid}")
private String kid;
@Value("${security.jwt.internal.expiration}")
private long intJwtExpiration;
@Value("${security.jwt.external.expiration}")
private long extJwtExpiration;
private String token;
private static final String SERVICE = "AUTH_SERVICES";
@Value("${security.jwt.expiration-time}")
private long jwtExpiration;
public String getOwnInternalToken() {
public long getExpirationTime() {
return jwtExpiration;
// Si no hay token, no es valido o quedan 10 seg para caducar se genera otro
if (token == null || validate(token) == null ||
decodeToken(token).getTtl() <= 10) {
token = generateInternalToken(SERVICE);
}
return token;
}
public String generateInternalToken(String service) {
String email = service.toLowerCase() + "@internal.com";
Algorithm algorithm = Algorithm.HMAC256(secretKey);
return JWT
.create()
.withKeyId(kid)
.withIssuedAt(new Date())
.withExpiresAt(new Date(System.currentTimeMillis() + intJwtExpiration * 1000))
.withSubject(service)
.withAudience("INTERNAL")
// DATA
.withClaim("service", service)
.withClaim("email", email)
.sign(algorithm);
}
public String generateToken(User user) {
Algorithm algorithm = Algorithm.HMAC256(secretKey);
return JWT
.create()
.withKeyId(kid)
.withIssuedAt(new Date())
.withExpiresAt(new Date(System.currentTimeMillis() + extJwtExpiration * 1000))
.withSubject(SERVICE)
.withAudience("EXTERNAL")
// DATA
.withClaim("id", user.getId())
.withClaim("name", user.getName())
.withClaim("email", user.getEmail())
.withClaim("rol", user.getRol().toString())
.withIssuedAt(new Date())
.withExpiresAt(new Date(System.currentTimeMillis() + jwtExpiration * 1000))
.sign(algorithm);
}
// TODO estaría guapo recuperar métodos de validación para el token de petición
// para este servicio
public DecodedJWT validate(String token) {
try {
return JWT.require(Algorithm.HMAC256(secretKey)).build().verify(token);
} catch (Exception e) {
return null;
}
}
public TokenData decodeToken(String token) {
DecodedJWT decoded = validate(token);
if (decoded == null)
return null;
return new TokenData(decoded, calculateTTL(decoded));
}
private long calculateTTL(DecodedJWT decodedJWT) {
if (decodedJWT == null)
throw new HttpClientErrorException(HttpStatus.FORBIDDEN);
long exp = decodedJWT.getExpiresAt().toInstant().getEpochSecond();
long now = Instant.now().getEpochSecond();
return exp - now;
}
}
......@@ -3,7 +3,8 @@ server.port=8101
security.jwt.secret-key=MiClaveDeSeguridadMuyLargaParaQueNoFalleSpringBoot
# 1h in millisecond
security.jwt.expiration-time=3600000
security.jwt.external.expiration=3600
security.jwt.internal.expiration=20
security.jwt.kid=cYz3kNRLAirxVhHXQ5rh5xKrOwHwZVui
external.services.users.url=http://localhost:8201/users
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment