diff --git a/pom.xml b/pom.xml
index b8e53eb..4f2f005 100644
--- a/pom.xml
+++ b/pom.xml
@@ -42,7 +42,11 @@
org.springframework.boot
spring-boot-starter-jdbc
-
+
+ com.auth0
+ java-jwt
+ 4.4.0
+
org.postgresql
postgresql
diff --git a/src/main/java/net/hackyourfuture/security/JwtService.java b/src/main/java/net/hackyourfuture/security/JwtService.java
new file mode 100644
index 0000000..8cee30b
--- /dev/null
+++ b/src/main/java/net/hackyourfuture/security/JwtService.java
@@ -0,0 +1,49 @@
+package net.hackyourfuture.security;
+
+import com.auth0.jwt.JWT;
+import org.springframework.beans.factory.annotation.Value;
+import com.auth0.jwt.algorithms.Algorithm;
+import com.auth0.jwt.interfaces.DecodedJWT;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.stereotype.Service;
+import java.util.Date;
+
+@Service
+public class JwtService {
+
+ private final Algorithm algorithm;
+
+ public JwtService(@Value("${JWT_SECRET_KEY}") String secretKey) {
+ this.algorithm = Algorithm.HMAC256(secretKey);
+ }
+
+
+ // Generating a new JWT token valid for 24 hours.
+ public String generateToken(UserDetails userDetails) {
+ return JWT.create()
+ .withSubject(userDetails.getUsername())
+ .withIssuedAt(new Date())
+ .withExpiresAt(new Date(System.currentTimeMillis() + 24 * 60 * 60 * 1000))
+ .sign(algorithm);
+ }
+
+ // Extracting the usernme from the token.
+ public String extractUsername(String token) {
+ DecodedJWT decodedJWT = JWT.require(algorithm)
+ .build()
+ .verify(token);
+ return decodedJWT.getSubject();
+ }
+
+
+ // Validating if the token belongs to the user and isnt expired.
+ public boolean isTokenValid(String token, UserDetails userDetails) {
+ String username = extractUsername(token);
+ return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
+ }
+
+ private boolean isTokenExpired(String token) {
+ DecodedJWT decodedJWT = JWT.require(algorithm).build().verify(token);
+ return decodedJWT.getExpiresAt().before(new Date());
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/net/hackyourfuture/security/MyUserDetailsService.java b/src/main/java/net/hackyourfuture/security/MyUserDetailsService.java
new file mode 100644
index 0000000..26b16b1
--- /dev/null
+++ b/src/main/java/net/hackyourfuture/security/MyUserDetailsService.java
@@ -0,0 +1,33 @@
+package net.hackyourfuture.security;
+
+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 net.hackyourfuture.security.user.User;
+import net.hackyourfuture.security.user.UserRepository;
+
+@Service
+public class MyUserDetailsService implements UserDetailsService {
+
+ private final UserRepository userRepository;
+
+ public MyUserDetailsService(UserRepository userRepository) {
+ this.userRepository = userRepository;
+ }
+
+ @Override
+ public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
+ User user = userRepository.findByUsername(username);
+ if (user == null) {
+ throw new UsernameNotFoundException("User not found: " + username);
+ }
+
+ // Converting the User into Spring Security's UserDetails
+ return org.springframework.security.core.userdetails.User.builder()
+ .username(user.getUsername())
+ .password(user.getPassword())
+ .roles("USER")
+ .build();
+ }
+}
diff --git a/src/main/java/net/hackyourfuture/security/authentication/AuthenticationService.java b/src/main/java/net/hackyourfuture/security/authentication/AuthenticationService.java
index 2472a13..0ecdb5f 100644
--- a/src/main/java/net/hackyourfuture/security/authentication/AuthenticationService.java
+++ b/src/main/java/net/hackyourfuture/security/authentication/AuthenticationService.java
@@ -4,16 +4,32 @@
import net.hackyourfuture.security.authentication.dto.LoginRequest;
import net.hackyourfuture.security.authentication.dto.LoginResponse;
import org.springframework.stereotype.Service;
+import net.hackyourfuture.security.JwtService;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.userdetails.UserDetails;
@Service
@AllArgsConstructor
public class AuthenticationService {
+ private final AuthenticationManager authenticationManager;
+ private final JwtService jwtService;
public LoginResponse login(LoginRequest request) {
- throw new UnsupportedOperationException("TODO: implement login");
+ Authentication authentication = authenticationManager.authenticate(
+ new UsernamePasswordAuthenticationToken(request.username(), request.password())
+ );
+
+ // get the authenticated details
+ UserDetails user = (UserDetails) authentication.getPrincipal();
+
+ // Generating a token using JwtService
+ String token = jwtService.generateToken(user);
+
+ return new LoginResponse(token);
}
public void logout() {
- throw new UnsupportedOperationException("TODO: implement logout");
}
}
diff --git a/src/main/java/net/hackyourfuture/security/config/MyUserAuthenticationFilter.java b/src/main/java/net/hackyourfuture/security/config/MyUserAuthenticationFilter.java
new file mode 100644
index 0000000..c878d88
--- /dev/null
+++ b/src/main/java/net/hackyourfuture/security/config/MyUserAuthenticationFilter.java
@@ -0,0 +1,50 @@
+package net.hackyourfuture.security.config;
+
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.ServletRequest;
+import jakarta.servlet.ServletResponse;
+import jakarta.servlet.http.HttpServletRequest;
+import lombok.AllArgsConstructor;
+import net.hackyourfuture.security.JwtService;
+import net.hackyourfuture.security.MyUserDetailsService;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+import java.io.IOException;
+
+@AllArgsConstructor
+public class MyUserAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
+
+ private final JwtService jwtService;
+ private final MyUserDetailsService myUserDetailsService;
+
+ @Override
+ public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
+ throws IOException, ServletException {
+
+ HttpServletRequest httpRequest = (HttpServletRequest) request;
+ String authHeader = httpRequest.getHeader("Authorization");
+
+ // Looking for Bearer token in the headers
+ if (authHeader != null && authHeader.startsWith("Bearer ")) {
+ String token = authHeader.substring(7);
+ try {
+ // Extracting username
+ String username = jwtService.extractUsername(token);
+ var userDetails = myUserDetailsService.loadUserByUsername(username);
+
+ // Verifing token validty
+ if (jwtService.isTokenValid(token, userDetails)) {
+ UsernamePasswordAuthenticationToken authentication =
+ new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
+
+ SecurityContextHolder.getContext().setAuthentication(authentication);
+ }
+ } catch (Exception e) {
+ }
+ }
+
+ chain.doFilter(request, response);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/net/hackyourfuture/security/config/SecurityConfig.java b/src/main/java/net/hackyourfuture/security/config/SecurityConfig.java
index d3ac764..cc84267 100644
--- a/src/main/java/net/hackyourfuture/security/config/SecurityConfig.java
+++ b/src/main/java/net/hackyourfuture/security/config/SecurityConfig.java
@@ -1,18 +1,56 @@
package net.hackyourfuture.security.config;
+import net.hackyourfuture.security.JwtService;
+import net.hackyourfuture.security.MyUserDetailsService;
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;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.http.SessionCreationPolicy;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
public class SecurityConfig {
+ private final JwtService jwtService;
+ private final MyUserDetailsService myUserDetailsService;
+
+ public SecurityConfig(JwtService jwtService, MyUserDetailsService myUserDetailsService) {
+ this.jwtService = jwtService;
+ this.myUserDetailsService = myUserDetailsService;
+ }
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
- .authorizeHttpRequests(auth -> auth.anyRequest().permitAll());
+ .sessionManagement(session ->
+ session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
+ .authorizeHttpRequests(auth -> auth
+ // public endpoints
+ .requestMatchers("/auth/login", "/users/register").permitAll()
+ // private endpoints
+ .anyRequest().authenticated()
+ )
+ .addFilterBefore(
+ new MyUserAuthenticationFilter(jwtService, myUserDetailsService),
+ UsernamePasswordAuthenticationFilter.class
+ );
return http.build();
}
+ // BCrypt for hashing
+ @Bean
+ public PasswordEncoder passwordEncoder() {
+ return new BCryptPasswordEncoder();
+ }
+
+ // to handl the login authentication checks
+ @Bean
+ public AuthenticationManager authenticationManager(AuthenticationConfiguration config)
+ throws Exception {
+ return config.getAuthenticationManager();
+ }
}
diff --git a/src/main/java/net/hackyourfuture/security/user/UserController.java b/src/main/java/net/hackyourfuture/security/user/UserController.java
index 9309baf..ea72240 100644
--- a/src/main/java/net/hackyourfuture/security/user/UserController.java
+++ b/src/main/java/net/hackyourfuture/security/user/UserController.java
@@ -4,12 +4,15 @@
import net.hackyourfuture.security.user.dto.UserRequest;
import net.hackyourfuture.security.user.dto.UserResponse;
import org.springframework.http.HttpStatus;
+import org.springframework.security.core.annotation.AuthenticationPrincipal;
+import org.springframework.security.core.userdetails.UserDetails;
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.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.server.ResponseStatusException;
@RestController
@RequestMapping("/users")
@@ -25,7 +28,10 @@ public UserResponse register(@RequestBody UserRequest request) {
}
@GetMapping("/profile")
- public UserResponse profile() {
- return userService.getProfile("REPLACE WITH CURRENTLY LOGGED IN USER ID");
+ public UserResponse profile(@AuthenticationPrincipal UserDetails userDetails) {
+ if (userDetails == null) {
+ throw new ResponseStatusException(HttpStatus.UNAUTHORIZED);
+ }
+ return userService.getProfile(userDetails.getUsername());
}
}
diff --git a/src/main/java/net/hackyourfuture/security/user/UserService.java b/src/main/java/net/hackyourfuture/security/user/UserService.java
index 15afd1e..c9deda0 100644
--- a/src/main/java/net/hackyourfuture/security/user/UserService.java
+++ b/src/main/java/net/hackyourfuture/security/user/UserService.java
@@ -4,17 +4,28 @@
import net.hackyourfuture.security.user.dto.UserRequest;
import net.hackyourfuture.security.user.dto.UserResponse;
import org.springframework.http.HttpStatus;
+import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.web.server.ResponseStatusException;
+import java.util.UUID;
+
@Service
@AllArgsConstructor
public class UserService {
private final UserRepository userRepository;
+ private final PasswordEncoder passwordEncoder;
public UserResponse register(UserRequest request) {
- throw new UnsupportedOperationException("TODO: implement registration");
+ String hashedPassword = passwordEncoder.encode(request.password());
+
+ String userId = UUID.randomUUID().toString();
+ User newUser = new User(userId, request.username(), hashedPassword);
+ userRepository.createUser(newUser);
+
+ return new UserResponse(newUser.getId(), newUser.getUsername());
+
}
public UserResponse getProfile(String username) {
diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml
index 098cbeb..0d73a55 100644
--- a/src/main/resources/application.yaml
+++ b/src/main/resources/application.yaml
@@ -8,3 +8,7 @@ spring:
sql:
init:
mode: always
+
+ jwt:
+ secret:
+ key: '${JWT_SECRET_KEY}'