Implementation of login endpoint.
This commit is contained in:
6
pom.xml
6
pom.xml
@@ -16,6 +16,7 @@
|
||||
<maven.compiler.source>21</maven.compiler.source>
|
||||
<maven.compiler.target>21</maven.compiler.target>
|
||||
<jakarta.servlet-api.version>6.0.0</jakarta.servlet-api.version>
|
||||
<java-jwt.version>4.4.0</java-jwt.version>
|
||||
</properties>
|
||||
|
||||
<modules>
|
||||
@@ -60,6 +61,11 @@
|
||||
<artifactId>jakarta.servlet-api</artifactId>
|
||||
<version>${jakarta.servlet-api.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.auth0</groupId>
|
||||
<artifactId>java-jwt</artifactId>
|
||||
<version>${java-jwt.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
|
||||
@@ -33,5 +33,9 @@
|
||||
<groupId>jakarta.servlet</groupId>
|
||||
<artifactId>jakarta.servlet-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.auth0</groupId>
|
||||
<artifactId>java-jwt</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
||||
@@ -12,6 +12,9 @@ import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||
|
||||
import static jakarta.servlet.DispatcherType.FORWARD;
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_FORBIDDEN;
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_UNAUTHORIZED;
|
||||
import jakarta.servlet.DispatcherType;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
@@ -26,7 +29,7 @@ public class SecurityConfiguration {
|
||||
.csrf(AbstractHttpConfigurer::disable)
|
||||
.httpBasic(Customizer.withDefaults())
|
||||
.authorizeHttpRequests(requests -> requests
|
||||
.dispatcherTypeMatchers(DispatcherType.FORWARD).permitAll()
|
||||
.dispatcherTypeMatchers(FORWARD).permitAll()
|
||||
.requestMatchers(
|
||||
HttpMethod.GET,
|
||||
"/api/health/check"
|
||||
@@ -39,10 +42,10 @@ public class SecurityConfiguration {
|
||||
)
|
||||
.exceptionHandling(configurer -> configurer
|
||||
.defaultAuthenticationEntryPointFor(
|
||||
(request, response, authException) -> response.sendError(HttpServletResponse.SC_UNAUTHORIZED),
|
||||
(request, response, authException) -> response.sendError(SC_UNAUTHORIZED),
|
||||
new AntPathRequestMatcher("/api/**")
|
||||
).defaultAccessDeniedHandlerFor(
|
||||
(request, response, accessDeniedException) -> response.sendError(HttpServletResponse.SC_FORBIDDEN),
|
||||
(request, response, accessDeniedException) -> response.sendError(SC_FORBIDDEN),
|
||||
new AntPathRequestMatcher("/api/**")
|
||||
)
|
||||
);
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package org.sportshub.application.user;
|
||||
package org.sportshub.application.security;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import org.sportshub.application.security.model.CustomUserDetails;
|
||||
import org.sportshub.application.user.UserUseCases;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
@@ -0,0 +1,46 @@
|
||||
package org.sportshub.application.security;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
|
||||
import org.sportshub.domain.user.model.User;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
import com.auth0.jwt.JWT;
|
||||
import com.auth0.jwt.JWTVerifier;
|
||||
import com.auth0.jwt.algorithms.Algorithm;
|
||||
import com.auth0.jwt.exceptions.JWTVerificationException;
|
||||
|
||||
@Service
|
||||
public class JwtService {
|
||||
private final Algorithm algorithm;
|
||||
private final JWTVerifier jwtVerifier;
|
||||
|
||||
public JwtService(@Value("${application.security.secretKey}") String secretKey) {
|
||||
algorithm = Algorithm.HMAC512(secretKey);
|
||||
jwtVerifier = JWT.require(algorithm).build();
|
||||
}
|
||||
|
||||
public String createJwt(User user) {
|
||||
ZonedDateTime expirationDate = ZonedDateTime.now().plusMinutes(30);
|
||||
|
||||
return JWT.create()
|
||||
.withSubject(user.id().toString())
|
||||
.withExpiresAt(expirationDate.toInstant())
|
||||
.sign(algorithm);
|
||||
}
|
||||
|
||||
public boolean isValid(String token) {
|
||||
boolean result;
|
||||
try {
|
||||
jwtVerifier.verify(token);
|
||||
result = true;
|
||||
} catch (JWTVerificationException exception) {
|
||||
result = false;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public String extractUsername(String token) {
|
||||
return JWT.decode(token).getSubject();
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.sportshub.application.user;
|
||||
package org.sportshub.application.security.model;
|
||||
|
||||
import static java.util.Collections.emptyList;
|
||||
import java.util.Collection;
|
||||
@@ -4,15 +4,26 @@ import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.sportshub.application.security.JwtService;
|
||||
import org.sportshub.domain.exception.LoginFailureException;
|
||||
import org.sportshub.domain.user.model.User;
|
||||
import org.sportshub.domain.user.port.UserPort;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class UserUseCases {
|
||||
private final PasswordEncoder passwordEncoder;
|
||||
private final JwtService jwtService;
|
||||
private final UserPort userPort;
|
||||
|
||||
public UserUseCases(final UserPort userPort) {
|
||||
public UserUseCases(
|
||||
PasswordEncoder passwordEncoder,
|
||||
JwtService jwtService,
|
||||
UserPort userPort
|
||||
) {
|
||||
this.passwordEncoder = passwordEncoder;
|
||||
this.jwtService = jwtService;
|
||||
this.userPort = userPort;
|
||||
}
|
||||
|
||||
@@ -23,4 +34,11 @@ public class UserUseCases {
|
||||
public List<User> findAll() {
|
||||
return userPort.findAll();
|
||||
}
|
||||
|
||||
public String authenticate(final UUID id, final String password) {
|
||||
return userPort.findById(id)
|
||||
.filter(user -> passwordEncoder.matches(password, user.password()))
|
||||
.map(jwtService::createJwt)
|
||||
.orElseThrow(LoginFailureException::new);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
package org.sportshub.domain.exception;
|
||||
|
||||
public abstract class FunctionnalException extends RuntimeException {
|
||||
public FunctionnalException(final String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package org.sportshub.domain.exception;
|
||||
|
||||
public class LoginFailureException extends FunctionnalException {
|
||||
public LoginFailureException() {
|
||||
super("Login or password incorrect.");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package org.sportshub.exposition.configuration;
|
||||
|
||||
import static org.springframework.http.HttpStatus.BAD_REQUEST;
|
||||
import org.sportshub.domain.exception.LoginFailureException;
|
||||
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
|
||||
@ControllerAdvice
|
||||
public class GlobalControllerExceptionHandler {
|
||||
|
||||
@ResponseStatus(BAD_REQUEST)
|
||||
@ExceptionHandler(LoginFailureException.class)
|
||||
public void handleLoginFailureException() {
|
||||
// Do nothing.
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,15 @@
|
||||
package org.sportshub.exposition.user;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.sportshub.application.user.UserUseCases;
|
||||
import org.sportshub.domain.user.model.User;
|
||||
import org.sportshub.exposition.user.model.LoginRequest;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
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;
|
||||
|
||||
@@ -19,8 +23,8 @@ public class UserController {
|
||||
}
|
||||
|
||||
@PostMapping("/login")
|
||||
public String login() {
|
||||
return "";
|
||||
public String login(@RequestBody LoginRequest request) {
|
||||
return userUseCases.authenticate(request.id(), request.password());
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
package org.sportshub.exposition.user.model;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public record LoginRequest(
|
||||
UUID id,
|
||||
String password
|
||||
) {}
|
||||
@@ -1,3 +1,7 @@
|
||||
application:
|
||||
security:
|
||||
secretKey: "secret-key"
|
||||
|
||||
logging:
|
||||
level:
|
||||
org.springframework.security: DEBUG
|
||||
Reference in New Issue
Block a user