diff --git a/build.gradle b/build.gradle index 5c180c6..fb6c93c 100644 --- a/build.gradle +++ b/build.gradle @@ -31,6 +31,12 @@ dependencies { testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' implementation 'jakarta.persistence:jakarta.persistence-api:3.1.0' + implementation 'org.springframework.boot:spring-boot-starter-security' + implementation 'org.springframework.boot:spring-boot-starter-web' + testImplementation 'org.springframework.security:spring-security-test' + implementation 'io.jsonwebtoken:jjwt-api:0.11.5' + runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5' + runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5' testCompileOnly 'org.projectlombok:lombok' testAnnotationProcessor 'org.projectlombok:lombok' diff --git a/src/main/java/fr/eni/demo/bll/ClientService.java b/src/main/java/fr/eni/demo/bll/ClientService.java index b5aed26..be254ae 100644 --- a/src/main/java/fr/eni/demo/bll/ClientService.java +++ b/src/main/java/fr/eni/demo/bll/ClientService.java @@ -11,4 +11,5 @@ public interface ClientService { List findByName(String name); void fullUpdate(Long id, Client client, Adresse adresseDetails); void updateLocation(Long idClient, Adresse adresseDetails); + void delete(Long idClient); } diff --git a/src/main/java/fr/eni/demo/bll/ClientServiceImpl.java b/src/main/java/fr/eni/demo/bll/ClientServiceImpl.java index 35fe26b..25f3150 100644 --- a/src/main/java/fr/eni/demo/bll/ClientServiceImpl.java +++ b/src/main/java/fr/eni/demo/bll/ClientServiceImpl.java @@ -69,6 +69,7 @@ public class ClientServiceImpl implements ClientService { return clients; } + @Override public void fullUpdate(Long id, Client clientDetails, Adresse adresseDetails) { Client client = clientRepository.findById(id) .orElseThrow(() -> new EntityNotFoundException("Client non trouvé avec l'id " + id)); @@ -82,6 +83,7 @@ public class ClientServiceImpl implements ClientService { clientRepository.save(client); } + @Override public void updateLocation(Long idClient, Adresse adresseDetails) { Client client = clientRepository.findById(idClient) .orElseThrow(() -> new EntityNotFoundException("Client non trouvé avec l'id " + idClient)); @@ -89,4 +91,12 @@ public class ClientServiceImpl implements ClientService { client.setAdresse(adresseDetails); clientRepository.save(client); } + + @Override + public void delete(Long idClient) { + Client client = clientRepository.findById(idClient) + .orElseThrow(() -> new EntityNotFoundException("Client non trouvé avec l'id " + idClient)); + + clientRepository.delete(client); + } } diff --git a/src/main/java/fr/eni/demo/bll/JwtService.java b/src/main/java/fr/eni/demo/bll/JwtService.java new file mode 100644 index 0000000..c739dff --- /dev/null +++ b/src/main/java/fr/eni/demo/bll/JwtService.java @@ -0,0 +1,40 @@ +package fr.eni.demo.bll; + +import io.jsonwebtoken.JwtException; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.security.Keys; +import org.springframework.stereotype.Service; + +import java.security.Key; +import java.util.Date; + +@Service +public class JwtService { + + private static final long EXPIRATION_TIME = 86400000; // 1j + private final Key key = Keys.secretKeyFor(SignatureAlgorithm.HS256); + + public String generateToken(String username) { + return Jwts.builder() + .setSubject(username) + .setIssuedAt(new Date()) + .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME)) + .signWith(key) + .compact(); + } + + public String extractUsername(String token) { + return Jwts.parserBuilder().setSigningKey(key).build() + .parseClaimsJws(token).getBody().getSubject(); + } + + public boolean isTokenValid(String token) { + try { + Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token); + return true; + } catch (JwtException e) { + return false; + } + } +} diff --git a/src/main/java/fr/eni/demo/bo/User.java b/src/main/java/fr/eni/demo/bo/User.java new file mode 100644 index 0000000..df73ac9 --- /dev/null +++ b/src/main/java/fr/eni/demo/bo/User.java @@ -0,0 +1,25 @@ +package fr.eni.demo.bo; + +import lombok.*; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; +import org.springframework.data.mongodb.core.mapping.Field; + +@AllArgsConstructor +@NoArgsConstructor +@Getter +@Setter +@Builder +@Document(collection = "stock") +public class User { + + @Id + private String id; + + @Field(name = "USERNAME") + private String username; + + @Field(name = "PASSWORD") + private String pasword; + +} diff --git a/src/main/java/fr/eni/demo/controller/AuthenticationController.java b/src/main/java/fr/eni/demo/controller/AuthenticationController.java new file mode 100644 index 0000000..1e1754a --- /dev/null +++ b/src/main/java/fr/eni/demo/controller/AuthenticationController.java @@ -0,0 +1,43 @@ +package fr.eni.demo.controller; + +import fr.eni.demo.bll.JwtService; +import fr.eni.demo.bo.User; +import lombok.Getter; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +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; + +@RestController +@RequestMapping("/api/auth") +public class AuthenticationController { + + private final AuthenticationManager authManager; + private final JwtService jwtService; + + public AuthenticationController(AuthenticationManager authManager, JwtService jwtService) { + this.authManager = authManager; + this.jwtService = jwtService; + } + + @PostMapping("/login") + public ResponseEntity login(@RequestBody User user) { + try { + Authentication auth = authManager.authenticate( + new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPasword()) + ); + + String token = jwtService.generateToken(user.getUsername()); + return ResponseEntity.ok(token); + } catch (AuthenticationException e) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); + } + } +} \ No newline at end of file diff --git a/src/main/java/fr/eni/demo/controller/ClientController.java b/src/main/java/fr/eni/demo/controller/ClientController.java index 5526b0f..9d364d0 100644 --- a/src/main/java/fr/eni/demo/controller/ClientController.java +++ b/src/main/java/fr/eni/demo/controller/ClientController.java @@ -60,4 +60,15 @@ public class ClientController { response.put("data", data); return ResponseEntity.ok(response); } + + @DeleteMapping("/{id]") + public ResponseEntity> delete(@PathVariable Long id) { + clientService.delete(id); + Map response = new HashMap<>(); + response.put("message", "Client deleted successfully"); + response.put("status", 200); + response.put("data", new HashMap<>()); + return ResponseEntity.ok(response); + } + } diff --git a/src/main/java/fr/eni/demo/security/JwtAuthFilter.java b/src/main/java/fr/eni/demo/security/JwtAuthFilter.java new file mode 100644 index 0000000..7374cb7 --- /dev/null +++ b/src/main/java/fr/eni/demo/security/JwtAuthFilter.java @@ -0,0 +1,50 @@ +package fr.eni.demo.security; + +import fr.eni.demo.bll.JwtService; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; + +public class JwtAuthFilter extends OncePerRequestFilter { + + private final JwtService jwtService; + private final UserDetailsService userDetailsService; + + public JwtAuthFilter(JwtService jwtService, UserDetailsService userDetailsService) { + this.jwtService = jwtService; + this.userDetailsService = userDetailsService; + } + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) + throws IOException, ServletException { + + final String authHeader = request.getHeader("Authorization"); + if (authHeader == null || !authHeader.startsWith("Bearer ")) { + chain.doFilter(request, response); + return; + } + + String token = authHeader.substring(7); + String username = jwtService.extractUsername(token); + + if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { + UserDetails userDetails = userDetailsService.loadUserByUsername(username); + if (jwtService.isTokenValid(token)) { + UsernamePasswordAuthenticationToken authToken = + new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); + SecurityContextHolder.getContext().setAuthentication(authToken); + } + } + + chain.doFilter(request, response); + } +} diff --git a/src/main/java/fr/eni/demo/security/SecurityConfig.java b/src/main/java/fr/eni/demo/security/SecurityConfig.java new file mode 100644 index 0000000..c0b3ec8 --- /dev/null +++ b/src/main/java/fr/eni/demo/security/SecurityConfig.java @@ -0,0 +1,63 @@ +package fr.eni.demo.security; + +import fr.eni.demo.bll.JwtService; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.Customizer; +import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.provisioning.InMemoryUserDetailsManager; +import org.springframework.security.web.SecurityFilterChain; + +@Configuration +@EnableWebSecurity +public class SecurityConfig { + + private final JwtService jwtService; + + public SecurityConfig(JwtService jwtService) { + this.jwtService = jwtService; + } + + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + http + .authorizeHttpRequests(auth -> auth + .requestMatchers("/api/auth").permitAll() + .requestMatchers("/api/**").hasAnyRole("USER") + .anyRequest().denyAll() + ) + .formLogin(Customizer.withDefaults()); + + return http.build(); + } + + @Bean + public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception { + return config.getAuthenticationManager(); + } + + @Bean + public PasswordEncoder encoder() { + return new BCryptPasswordEncoder(); + } + + @Bean + public UserDetailsService userDetailsService(PasswordEncoder encoder) { + UserDetails user = User.builder() + .username("user") + .password(encoder.encode("password")) + .roles("USER") + .build(); + + return new InMemoryUserDetailsManager(user); + } + +}