diff --git a/pom.xml b/pom.xml
index 9c14b26..a90be1a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -31,14 +31,14 @@
- org.postgresql
- postgresql
- runtime
+ org.jetbrains
+ annotations
+ 24.0.1
- org.projectlombok
- lombok
- true
+ com.mysql
+ mysql-connector-j
+ runtime
io.jsonwebtoken
@@ -65,16 +65,6 @@
spring-boot-starter-validation
-
- org.springframework.boot
- spring-boot-starter-test
- test
-
-
- org.springframework.security
- spring-security-test
- test
-
diff --git a/src/main/java/com/alibou/security/Application.java b/src/main/java/com/alibou/security/Application.java
new file mode 100644
index 0000000..b2c93d9
--- /dev/null
+++ b/src/main/java/com/alibou/security/Application.java
@@ -0,0 +1,13 @@
+package com.alibou.security;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
+
+@SpringBootApplication
+@EnableJpaAuditing(auditorAwareRef = "auditorAware")
+public class Application {
+ public static void main(String[] args) {
+ SpringApplication.run(Application.class, args);
+ }
+}
diff --git a/src/main/java/com/alibou/security/SecurityApplication.java b/src/main/java/com/alibou/security/SecurityApplication.java
deleted file mode 100644
index 1448cff..0000000
--- a/src/main/java/com/alibou/security/SecurityApplication.java
+++ /dev/null
@@ -1,48 +0,0 @@
-package com.alibou.security;
-
-import com.alibou.security.auth.AuthenticationService;
-import com.alibou.security.auth.RegisterRequest;
-import com.alibou.security.user.Role;
-import org.springframework.boot.CommandLineRunner;
-import org.springframework.boot.SpringApplication;
-import org.springframework.boot.autoconfigure.SpringBootApplication;
-import org.springframework.context.annotation.Bean;
-import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
-
-import static com.alibou.security.user.Role.ADMIN;
-import static com.alibou.security.user.Role.MANAGER;
-
-@SpringBootApplication
-@EnableJpaAuditing(auditorAwareRef = "auditorAware")
-public class SecurityApplication {
-
- public static void main(String[] args) {
- SpringApplication.run(SecurityApplication.class, args);
- }
-
- @Bean
- public CommandLineRunner commandLineRunner(
- AuthenticationService service
- ) {
- return args -> {
- var admin = RegisterRequest.builder()
- .firstname("Admin")
- .lastname("Admin")
- .email("admin@mail.com")
- .password("password")
- .role(ADMIN)
- .build();
- System.out.println("Admin token: " + service.register(admin).getAccessToken());
-
- var manager = RegisterRequest.builder()
- .firstname("Admin")
- .lastname("Admin")
- .email("manager@mail.com")
- .password("password")
- .role(MANAGER)
- .build();
- System.out.println("Manager token: " + service.register(manager).getAccessToken());
-
- };
- }
-}
diff --git a/src/main/java/com/alibou/security/auth/AuthenticationController.java b/src/main/java/com/alibou/security/auth/AuthenticationController.java
deleted file mode 100644
index e1d5107..0000000
--- a/src/main/java/com/alibou/security/auth/AuthenticationController.java
+++ /dev/null
@@ -1,43 +0,0 @@
-package com.alibou.security.auth;
-
-import jakarta.servlet.http.HttpServletRequest;
-import jakarta.servlet.http.HttpServletResponse;
-import lombok.RequiredArgsConstructor;
-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 java.io.IOException;
-
-@RestController
-@RequestMapping("/api/v1/auth")
-@RequiredArgsConstructor
-public class AuthenticationController {
-
- private final AuthenticationService service;
-
- @PostMapping("/register")
- public ResponseEntity register(
- @RequestBody RegisterRequest request
- ) {
- return ResponseEntity.ok(service.register(request));
- }
- @PostMapping("/authenticate")
- public ResponseEntity authenticate(
- @RequestBody AuthenticationRequest request
- ) {
- return ResponseEntity.ok(service.authenticate(request));
- }
-
- @PostMapping("/refresh-token")
- public void refreshToken(
- HttpServletRequest request,
- HttpServletResponse response
- ) throws IOException {
- service.refreshToken(request, response);
- }
-
-
-}
diff --git a/src/main/java/com/alibou/security/auth/AuthenticationRequest.java b/src/main/java/com/alibou/security/auth/AuthenticationRequest.java
deleted file mode 100644
index 6d72722..0000000
--- a/src/main/java/com/alibou/security/auth/AuthenticationRequest.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package com.alibou.security.auth;
-
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-
-@Data
-@Builder
-@AllArgsConstructor
-@NoArgsConstructor
-public class AuthenticationRequest {
-
- private String email;
- String password;
-}
diff --git a/src/main/java/com/alibou/security/auth/AuthenticationResponse.java b/src/main/java/com/alibou/security/auth/AuthenticationResponse.java
deleted file mode 100644
index c10bbb6..0000000
--- a/src/main/java/com/alibou/security/auth/AuthenticationResponse.java
+++ /dev/null
@@ -1,19 +0,0 @@
-package com.alibou.security.auth;
-
-import com.fasterxml.jackson.annotation.JsonProperty;
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-
-@Data
-@Builder
-@AllArgsConstructor
-@NoArgsConstructor
-public class AuthenticationResponse {
-
- @JsonProperty("access_token")
- private String accessToken;
- @JsonProperty("refresh_token")
- private String refreshToken;
-}
diff --git a/src/main/java/com/alibou/security/auth/AuthenticationService.java b/src/main/java/com/alibou/security/auth/AuthenticationService.java
deleted file mode 100644
index 53193a7..0000000
--- a/src/main/java/com/alibou/security/auth/AuthenticationService.java
+++ /dev/null
@@ -1,120 +0,0 @@
-package com.alibou.security.auth;
-
-import com.alibou.security.config.JwtService;
-import com.alibou.security.token.Token;
-import com.alibou.security.token.TokenRepository;
-import com.alibou.security.token.TokenType;
-import com.alibou.security.user.Role;
-import com.alibou.security.user.User;
-import com.alibou.security.user.UserRepository;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import jakarta.servlet.http.HttpServletRequest;
-import jakarta.servlet.http.HttpServletResponse;
-import lombok.RequiredArgsConstructor;
-import org.springframework.http.HttpHeaders;
-import org.springframework.security.authentication.AuthenticationManager;
-import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
-import org.springframework.security.core.context.SecurityContextHolder;
-import org.springframework.security.core.userdetails.UserDetails;
-import org.springframework.security.crypto.password.PasswordEncoder;
-import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
-import org.springframework.stereotype.Service;
-
-import java.io.IOException;
-
-@Service
-@RequiredArgsConstructor
-public class AuthenticationService {
- private final UserRepository repository;
- private final TokenRepository tokenRepository;
- private final PasswordEncoder passwordEncoder;
- private final JwtService jwtService;
- private final AuthenticationManager authenticationManager;
-
- public AuthenticationResponse register(RegisterRequest request) {
- var user = User.builder()
- .firstname(request.getFirstname())
- .lastname(request.getLastname())
- .email(request.getEmail())
- .password(passwordEncoder.encode(request.getPassword()))
- .role(request.getRole())
- .build();
- var savedUser = repository.save(user);
- var jwtToken = jwtService.generateToken(user);
- var refreshToken = jwtService.generateRefreshToken(user);
- saveUserToken(savedUser, jwtToken);
- return AuthenticationResponse.builder()
- .accessToken(jwtToken)
- .refreshToken(refreshToken)
- .build();
- }
-
- public AuthenticationResponse authenticate(AuthenticationRequest request) {
- authenticationManager.authenticate(
- new UsernamePasswordAuthenticationToken(
- request.getEmail(),
- request.getPassword()
- )
- );
- var user = repository.findByEmail(request.getEmail())
- .orElseThrow();
- var jwtToken = jwtService.generateToken(user);
- var refreshToken = jwtService.generateRefreshToken(user);
- revokeAllUserTokens(user);
- saveUserToken(user, jwtToken);
- return AuthenticationResponse.builder()
- .accessToken(jwtToken)
- .refreshToken(refreshToken)
- .build();
- }
-
- private void saveUserToken(User user, String jwtToken) {
- var token = Token.builder()
- .user(user)
- .token(jwtToken)
- .tokenType(TokenType.BEARER)
- .expired(false)
- .revoked(false)
- .build();
- tokenRepository.save(token);
- }
-
- private void revokeAllUserTokens(User user) {
- var validUserTokens = tokenRepository.findAllValidTokenByUser(user.getId());
- if (validUserTokens.isEmpty())
- return;
- validUserTokens.forEach(token -> {
- token.setExpired(true);
- token.setRevoked(true);
- });
- tokenRepository.saveAll(validUserTokens);
- }
-
- public void refreshToken(
- HttpServletRequest request,
- HttpServletResponse response
- ) throws IOException {
- final String authHeader = request.getHeader(HttpHeaders.AUTHORIZATION);
- final String refreshToken;
- final String userEmail;
- if (authHeader == null ||!authHeader.startsWith("Bearer ")) {
- return;
- }
- refreshToken = authHeader.substring(7);
- userEmail = jwtService.extractUsername(refreshToken);
- if (userEmail != null) {
- var user = this.repository.findByEmail(userEmail)
- .orElseThrow();
- if (jwtService.isTokenValid(refreshToken, user)) {
- var accessToken = jwtService.generateToken(user);
- revokeAllUserTokens(user);
- saveUserToken(user, accessToken);
- var authResponse = AuthenticationResponse.builder()
- .accessToken(accessToken)
- .refreshToken(refreshToken)
- .build();
- new ObjectMapper().writeValue(response.getOutputStream(), authResponse);
- }
- }
- }
-}
diff --git a/src/main/java/com/alibou/security/auth/RegisterRequest.java b/src/main/java/com/alibou/security/auth/RegisterRequest.java
deleted file mode 100644
index 4f51665..0000000
--- a/src/main/java/com/alibou/security/auth/RegisterRequest.java
+++ /dev/null
@@ -1,20 +0,0 @@
-package com.alibou.security.auth;
-
-import com.alibou.security.user.Role;
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-
-@Data
-@Builder
-@AllArgsConstructor
-@NoArgsConstructor
-public class RegisterRequest {
-
- private String firstname;
- private String lastname;
- private String email;
- private String password;
- private Role role;
-}
diff --git a/src/main/java/com/alibou/security/auth/controller/AuthenticationController.java b/src/main/java/com/alibou/security/auth/controller/AuthenticationController.java
new file mode 100644
index 0000000..4c3a7fd
--- /dev/null
+++ b/src/main/java/com/alibou/security/auth/controller/AuthenticationController.java
@@ -0,0 +1,47 @@
+package com.alibou.security.auth.controller;
+
+import com.alibou.security.auth.model.AuthenticationRequest;
+import com.alibou.security.auth.model.AuthenticationResponse;
+import com.alibou.security.auth.service.AuthenticationService;
+import com.alibou.security.auth.model.RegisterRequest;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+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 java.io.IOException;
+
+@RestController
+@RequestMapping("/api/v1/auth")
+public class AuthenticationController {
+
+ private final AuthenticationService service;
+
+ public AuthenticationController(AuthenticationService service) {
+ this.service = service;
+ }
+
+ @PostMapping("/register")
+ public ResponseEntity register(
+ @RequestBody RegisterRequest request
+ ) {
+ return ResponseEntity.ok(service.register(request));
+ }
+
+ @PostMapping("/authenticate")
+ public ResponseEntity authenticate(
+ @RequestBody AuthenticationRequest request
+ ) {
+ return ResponseEntity.ok(service.authenticate(request));
+ }
+
+ @PostMapping("/refresh-token")
+ public void refreshToken(HttpServletRequest request, HttpServletResponse response) throws IOException {
+ service.refreshToken(request, response);
+ }
+
+
+}
diff --git a/src/main/java/com/alibou/security/auth/model/AuthenticationRequest.java b/src/main/java/com/alibou/security/auth/model/AuthenticationRequest.java
new file mode 100644
index 0000000..cab7c01
--- /dev/null
+++ b/src/main/java/com/alibou/security/auth/model/AuthenticationRequest.java
@@ -0,0 +1,7 @@
+package com.alibou.security.auth.model;
+
+
+import org.jetbrains.annotations.NotNull;
+
+public record AuthenticationRequest(@NotNull String email, @NotNull String password) {
+}
diff --git a/src/main/java/com/alibou/security/auth/model/AuthenticationResponse.java b/src/main/java/com/alibou/security/auth/model/AuthenticationResponse.java
new file mode 100644
index 0000000..be9213e
--- /dev/null
+++ b/src/main/java/com/alibou/security/auth/model/AuthenticationResponse.java
@@ -0,0 +1,8 @@
+package com.alibou.security.auth.model;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+
+public record AuthenticationResponse(@JsonProperty("access_token") String accessToken,
+ @JsonProperty("refresh_token") String refreshToken) {
+}
diff --git a/src/main/java/com/alibou/security/auth/model/RegisterRequest.java b/src/main/java/com/alibou/security/auth/model/RegisterRequest.java
new file mode 100644
index 0000000..d95ccd6
--- /dev/null
+++ b/src/main/java/com/alibou/security/auth/model/RegisterRequest.java
@@ -0,0 +1,13 @@
+package com.alibou.security.auth.model;
+
+import com.alibou.security.user.model.Role;
+import org.jetbrains.annotations.NotNull;
+
+
+public record RegisterRequest(@NotNull String firstname,
+ @NotNull String lastname,
+ @NotNull String email,
+ @NotNull String password,
+ @NotNull Role role) {
+
+}
diff --git a/src/main/java/com/alibou/security/auth/model/Token.java b/src/main/java/com/alibou/security/auth/model/Token.java
new file mode 100644
index 0000000..062b748
--- /dev/null
+++ b/src/main/java/com/alibou/security/auth/model/Token.java
@@ -0,0 +1,92 @@
+package com.alibou.security.auth.model;
+
+import com.alibou.security.user.model.User;
+import jakarta.persistence.*;
+import org.hibernate.annotations.GenericGenerator;
+
+import java.util.Objects;
+
+@Entity
+@Table(name = "token")
+public class Token {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.AUTO, generator = "native")
+ @GenericGenerator(name = "native")
+ public Long id;
+
+ @Column(unique = true)
+ public String token;
+
+ @Enumerated(EnumType.STRING)
+ public TokenType tokenType = TokenType.BEARER;
+
+ public boolean revoked;
+
+ public boolean expired;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "user_id")
+ public User user;
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public String getToken() {
+ return token;
+ }
+
+ public void setToken(String token) {
+ this.token = token;
+ }
+
+ public TokenType getTokenType() {
+ return tokenType;
+ }
+
+ public void setTokenType(TokenType tokenType) {
+ this.tokenType = tokenType;
+ }
+
+ public boolean isRevoked() {
+ return revoked;
+ }
+
+ public void setRevoked(boolean revoked) {
+ this.revoked = revoked;
+ }
+
+ public boolean isExpired() {
+ return expired;
+ }
+
+ public void setExpired(boolean expired) {
+ this.expired = expired;
+ }
+
+ public User getUser() {
+ return user;
+ }
+
+ public void setUser(User user) {
+ this.user = user;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ Token token1 = (Token) o;
+ return revoked == token1.revoked && expired == token1.expired && Objects.equals(id, token1.id) && Objects.equals(token, token1.token) && tokenType == token1.tokenType && Objects.equals(user, token1.user);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(id, token, tokenType, revoked, expired, user);
+ }
+}
diff --git a/src/main/java/com/alibou/security/auth/model/TokenType.java b/src/main/java/com/alibou/security/auth/model/TokenType.java
new file mode 100644
index 0000000..b685f59
--- /dev/null
+++ b/src/main/java/com/alibou/security/auth/model/TokenType.java
@@ -0,0 +1,5 @@
+package com.alibou.security.auth.model;
+
+public enum TokenType {
+ BEARER
+}
diff --git a/src/main/java/com/alibou/security/token/TokenRepository.java b/src/main/java/com/alibou/security/auth/repository/TokenRepository.java
similarity index 77%
rename from src/main/java/com/alibou/security/token/TokenRepository.java
rename to src/main/java/com/alibou/security/auth/repository/TokenRepository.java
index 48235d8..a3710be 100644
--- a/src/main/java/com/alibou/security/token/TokenRepository.java
+++ b/src/main/java/com/alibou/security/auth/repository/TokenRepository.java
@@ -1,7 +1,9 @@
-package com.alibou.security.token;
+package com.alibou.security.auth.repository;
import java.util.List;
import java.util.Optional;
+
+import com.alibou.security.auth.model.Token;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
@@ -12,7 +14,7 @@ public interface TokenRepository extends JpaRepository {
on t.user.id = u.id\s
where u.id = :id and (t.expired = false or t.revoked = false)\s
""")
- List findAllValidTokenByUser(Integer id);
+ List findAllValidTokenByUser(Long id);
Optional findByToken(String token);
}
diff --git a/src/main/java/com/alibou/security/auditing/ApplicationAuditAware.java b/src/main/java/com/alibou/security/auth/service/ApplicationAuditAware.java
similarity index 75%
rename from src/main/java/com/alibou/security/auditing/ApplicationAuditAware.java
rename to src/main/java/com/alibou/security/auth/service/ApplicationAuditAware.java
index 3f8172f..68339d0 100644
--- a/src/main/java/com/alibou/security/auditing/ApplicationAuditAware.java
+++ b/src/main/java/com/alibou/security/auth/service/ApplicationAuditAware.java
@@ -1,6 +1,6 @@
-package com.alibou.security.auditing;
+package com.alibou.security.auth.service;
-import com.alibou.security.user.User;
+import com.alibou.security.user.model.User;
import org.springframework.data.domain.AuditorAware;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.core.Authentication;
@@ -8,15 +8,16 @@
import java.util.Optional;
-public class ApplicationAuditAware implements AuditorAware {
+public class ApplicationAuditAware implements AuditorAware {
+
@Override
- public Optional getCurrentAuditor() {
+ public Optional getCurrentAuditor() {
Authentication authentication =
SecurityContextHolder
.getContext()
.getAuthentication();
if (authentication == null ||
- !authentication.isAuthenticated() ||
+ !authentication.isAuthenticated() ||
authentication instanceof AnonymousAuthenticationToken
) {
return Optional.empty();
diff --git a/src/main/java/com/alibou/security/auth/service/AuthenticationService.java b/src/main/java/com/alibou/security/auth/service/AuthenticationService.java
new file mode 100644
index 0000000..423da9c
--- /dev/null
+++ b/src/main/java/com/alibou/security/auth/service/AuthenticationService.java
@@ -0,0 +1,126 @@
+package com.alibou.security.auth.service;
+
+import com.alibou.security.auth.model.RegisterRequest;
+import com.alibou.security.auth.model.AuthenticationRequest;
+import com.alibou.security.auth.model.AuthenticationResponse;
+import com.alibou.security.config.JwtService;
+import com.alibou.security.auth.model.Token;
+import com.alibou.security.auth.repository.TokenRepository;
+import com.alibou.security.auth.model.TokenType;
+import com.alibou.security.user.model.User;
+import com.alibou.security.user.repository.UserRepository;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import jakarta.persistence.EntityExistsException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.jetbrains.annotations.NotNull;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.HttpHeaders;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.stereotype.Service;
+
+import java.io.IOException;
+import java.util.Optional;
+
+@Service
+public class AuthenticationService {
+ private final UserRepository repository;
+ private final TokenRepository tokenRepository;
+ private final PasswordEncoder passwordEncoder;
+ private final JwtService jwtService;
+ private final AuthenticationManager authenticationManager;
+ private final UserRepository userRepository;
+
+ public AuthenticationService(UserRepository repository, TokenRepository tokenRepository, PasswordEncoder passwordEncoder, JwtService jwtService, AuthenticationManager authenticationManager, UserRepository userRepository) {
+ this.repository = repository;
+ this.tokenRepository = tokenRepository;
+ this.passwordEncoder = passwordEncoder;
+ this.jwtService = jwtService;
+ this.authenticationManager = authenticationManager;
+ this.userRepository = userRepository;
+ }
+
+ @NotNull
+ public AuthenticationResponse register(@NotNull RegisterRequest request) {
+ final Optional existUser = userRepository.findByEmail(request.email());
+ if (existUser.isPresent()) {
+ final String errorMessage = String.format("User with given email = %s already exists", request.email());
+ throw new EntityExistsException(errorMessage);
+ }
+ final User user = new User();
+ user.setFirstname(request.firstname());
+ user.setLastname(request.lastname());
+ user.setEmail(request.email());
+ user.setPassword(passwordEncoder.encode(request.password()));
+ user.setRole(request.role());
+ var savedUser = repository.save(user);
+ var jwtToken = jwtService.generateToken(user);
+ var refreshToken = jwtService.generateRefreshToken(user);
+ saveUserToken(savedUser, jwtToken);
+ return new AuthenticationResponse(jwtToken, refreshToken);
+ }
+
+ @NotNull
+ public AuthenticationResponse authenticate(@NotNull AuthenticationRequest request) {
+ authenticationManager.authenticate(
+ new UsernamePasswordAuthenticationToken(
+ request.email(),
+ request.password()
+ )
+ );
+ var user = repository.findByEmail(request.email())
+ .orElseThrow();
+ var jwtToken = jwtService.generateToken(user);
+ var refreshToken = jwtService.generateRefreshToken(user);
+ revokeAllUserTokens(user);
+ saveUserToken(user, jwtToken);
+ return new AuthenticationResponse(jwtToken, refreshToken);
+ }
+
+ private void saveUserToken(@NotNull User user, @NotNull String jwtToken) {
+ final Token token = new Token();
+ token.setUser(user);
+ token.setToken(jwtToken);
+ token.setTokenType(TokenType.BEARER);
+ token.setExpired(false);
+ token.setRevoked(false);
+ tokenRepository.save(token);
+ }
+
+ private void revokeAllUserTokens(@NotNull User user) {
+ var validUserTokens = tokenRepository.findAllValidTokenByUser(user.getId());
+ if (validUserTokens.isEmpty())
+ return;
+ validUserTokens.forEach(token -> {
+ token.setExpired(true);
+ token.setRevoked(true);
+ });
+ tokenRepository.saveAll(validUserTokens);
+ }
+
+ public void refreshToken(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response
+ ) throws IOException {
+ final String authHeader = request.getHeader(HttpHeaders.AUTHORIZATION);
+ final String refreshToken;
+ final String userEmail;
+ if (authHeader == null || !authHeader.startsWith("Bearer ")) {
+ return;
+ }
+ refreshToken = authHeader.substring(7);
+ userEmail = jwtService.extractUsername(refreshToken);
+ if (userEmail != null) {
+ var user = this.repository.findByEmail(userEmail)
+ .orElseThrow();
+ if (jwtService.isTokenValid(refreshToken, user)) {
+ var accessToken = jwtService.generateToken(user);
+ revokeAllUserTokens(user);
+ saveUserToken(user, accessToken);
+ var authResponse = new AuthenticationResponse(accessToken, refreshToken);
+ new ObjectMapper().writeValue(response.getOutputStream(), authResponse);
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/alibou/security/book/Book.java b/src/main/java/com/alibou/security/book/Book.java
deleted file mode 100644
index 3f041af..0000000
--- a/src/main/java/com/alibou/security/book/Book.java
+++ /dev/null
@@ -1,56 +0,0 @@
-package com.alibou.security.book;
-
-import jakarta.persistence.Column;
-import jakarta.persistence.Entity;
-import jakarta.persistence.EntityListeners;
-import jakarta.persistence.GeneratedValue;
-import jakarta.persistence.Id;
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-import org.springframework.data.annotation.CreatedBy;
-import org.springframework.data.annotation.CreatedDate;
-import org.springframework.data.annotation.LastModifiedBy;
-import org.springframework.data.annotation.LastModifiedDate;
-import org.springframework.data.jpa.domain.support.AuditingEntityListener;
-
-import java.time.LocalDateTime;
-
-@Data
-@Builder
-@NoArgsConstructor
-@AllArgsConstructor
-@Entity
-@EntityListeners(AuditingEntityListener.class)
-public class Book {
-
- @Id
- @GeneratedValue
- private Integer id;
- private String author;
- private String isbn;
-
- @CreatedDate
- @Column(
- nullable = false,
- updatable = false
- )
- private LocalDateTime createDate;
-
- @LastModifiedDate
- @Column(insertable = false)
- private LocalDateTime lastModified;
-
-
- @CreatedBy
- @Column(
- nullable = false,
- updatable = false
- )
- private Integer createdBy;
-
- @LastModifiedBy
- @Column(insertable = false)
- private Integer lastModifiedBy;
-}
diff --git a/src/main/java/com/alibou/security/book/BookController.java b/src/main/java/com/alibou/security/book/BookController.java
deleted file mode 100644
index 4c45728..0000000
--- a/src/main/java/com/alibou/security/book/BookController.java
+++ /dev/null
@@ -1,32 +0,0 @@
-package com.alibou.security.book;
-
-import lombok.RequiredArgsConstructor;
-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;
-
-import java.util.List;
-
-@RestController
-@RequestMapping("/api/v1/books")
-@RequiredArgsConstructor
-public class BookController {
-
- private final BookService service;
-
- @PostMapping
- public ResponseEntity> save(
- @RequestBody BookRequest request
- ) {
- service.save(request);
- return ResponseEntity.accepted().build();
- }
-
- @GetMapping
- public ResponseEntity> findAllBooks() {
- return ResponseEntity.ok(service.findAll());
- }
-}
diff --git a/src/main/java/com/alibou/security/book/BookRepository.java b/src/main/java/com/alibou/security/book/BookRepository.java
deleted file mode 100644
index 21ca467..0000000
--- a/src/main/java/com/alibou/security/book/BookRepository.java
+++ /dev/null
@@ -1,6 +0,0 @@
-package com.alibou.security.book;
-
-import org.springframework.data.jpa.repository.JpaRepository;
-
-public interface BookRepository extends JpaRepository {
-}
diff --git a/src/main/java/com/alibou/security/book/BookRequest.java b/src/main/java/com/alibou/security/book/BookRequest.java
deleted file mode 100644
index dcf6765..0000000
--- a/src/main/java/com/alibou/security/book/BookRequest.java
+++ /dev/null
@@ -1,15 +0,0 @@
-package com.alibou.security.book;
-
-import lombok.Builder;
-import lombok.Getter;
-import lombok.Setter;
-
-@Getter
-@Setter
-@Builder
-public class BookRequest {
-
- private Integer id;
- private String author;
- private String isbn;
-}
diff --git a/src/main/java/com/alibou/security/book/BookService.java b/src/main/java/com/alibou/security/book/BookService.java
deleted file mode 100644
index c09ded8..0000000
--- a/src/main/java/com/alibou/security/book/BookService.java
+++ /dev/null
@@ -1,26 +0,0 @@
-package com.alibou.security.book;
-
-import lombok.RequiredArgsConstructor;
-import org.springframework.stereotype.Service;
-
-import java.util.List;
-
-@Service
-@RequiredArgsConstructor
-public class BookService {
-
- private final BookRepository repository;
-
- public void save(BookRequest request) {
- var book = Book.builder()
- .id(request.getId())
- .author(request.getAuthor())
- .isbn(request.getIsbn())
- .build();
- repository.save(book);
- }
-
- public List findAll() {
- return repository.findAll();
- }
-}
diff --git a/src/main/java/com/alibou/security/config/ApplicationConfig.java b/src/main/java/com/alibou/security/config/ApplicationConfig.java
index ae71abf..b911888 100644
--- a/src/main/java/com/alibou/security/config/ApplicationConfig.java
+++ b/src/main/java/com/alibou/security/config/ApplicationConfig.java
@@ -1,9 +1,7 @@
package com.alibou.security.config;
-import com.alibou.security.auditing.ApplicationAuditAware;
-import com.alibou.security.user.UserRepository;
-import jakarta.persistence.criteria.CriteriaBuilder;
-import lombok.RequiredArgsConstructor;
+import com.alibou.security.auth.service.ApplicationAuditAware;
+import com.alibou.security.user.repository.UserRepository;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.domain.AuditorAware;
@@ -17,38 +15,41 @@
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
-@RequiredArgsConstructor
public class ApplicationConfig {
- private final UserRepository repository;
-
- @Bean
- public UserDetailsService userDetailsService() {
- return username -> repository.findByEmail(username)
- .orElseThrow(() -> new UsernameNotFoundException("User not found"));
- }
-
- @Bean
- public AuthenticationProvider authenticationProvider() {
- DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
- authProvider.setUserDetailsService(userDetailsService());
- authProvider.setPasswordEncoder(passwordEncoder());
- return authProvider;
- }
-
- @Bean
- public AuditorAware auditorAware() {
- return new ApplicationAuditAware();
- }
-
- @Bean
- public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
- return config.getAuthenticationManager();
- }
-
- @Bean
- public PasswordEncoder passwordEncoder() {
- return new BCryptPasswordEncoder();
- }
+ private final UserRepository repository;
+
+ public ApplicationConfig(UserRepository repository) {
+ this.repository = repository;
+ }
+
+ @Bean
+ public UserDetailsService userDetailsService() {
+ return username -> repository.findByEmail(username)
+ .orElseThrow(() -> new UsernameNotFoundException("User not found"));
+ }
+
+ @Bean
+ public AuthenticationProvider authenticationProvider() {
+ DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
+ authProvider.setUserDetailsService(userDetailsService());
+ authProvider.setPasswordEncoder(passwordEncoder());
+ return authProvider;
+ }
+
+ @Bean
+ public AuditorAware auditorAware() {
+ return new ApplicationAuditAware();
+ }
+
+ @Bean
+ public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
+ return config.getAuthenticationManager();
+ }
+
+ @Bean
+ public PasswordEncoder passwordEncoder() {
+ return new BCryptPasswordEncoder();
+ }
}
diff --git a/src/main/java/com/alibou/security/config/GlobalExceptionHandler.java b/src/main/java/com/alibou/security/config/GlobalExceptionHandler.java
new file mode 100644
index 0000000..3f3bb20
--- /dev/null
+++ b/src/main/java/com/alibou/security/config/GlobalExceptionHandler.java
@@ -0,0 +1,19 @@
+package com.alibou.security.config;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.ErrorResponse;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.context.request.WebRequest;
+
+@ControllerAdvice
+public class GlobalExceptionHandler {
+
+ @ExceptionHandler(Exception.class)
+ public ResponseEntity> handleGlobalException(Exception ex, WebRequest request) {
+ final ErrorResponse errorDetails = ErrorResponse.create(ex, HttpStatus.INTERNAL_SERVER_ERROR, ex.getMessage());
+ return new ResponseEntity<>(errorDetails, HttpStatus.INTERNAL_SERVER_ERROR);
+ }
+
+}
diff --git a/src/main/java/com/alibou/security/config/JwtAuthenticationFilter.java b/src/main/java/com/alibou/security/config/JwtAuthenticationFilter.java
index d6e55d1..143c4a2 100644
--- a/src/main/java/com/alibou/security/config/JwtAuthenticationFilter.java
+++ b/src/main/java/com/alibou/security/config/JwtAuthenticationFilter.java
@@ -1,18 +1,13 @@
package com.alibou.security.config;
-import com.alibou.security.token.TokenRepository;
+import com.alibou.security.auth.repository.TokenRepository;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
-import java.beans.Transient;
import java.io.IOException;
-import java.security.Security;
-import jakarta.transaction.TransactionScoped;
-import jakarta.transaction.Transactional;
-import lombok.RequiredArgsConstructor;
import org.springframework.lang.NonNull;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
@@ -23,49 +18,56 @@
import org.springframework.web.filter.OncePerRequestFilter;
@Component
-@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {
- private final JwtService jwtService;
- private final UserDetailsService userDetailsService;
- private final TokenRepository tokenRepository;
+ private final JwtService jwtService;
+ private final UserDetailsService userDetailsService;
+ private final TokenRepository tokenRepository;
- @Override
- protected void doFilterInternal(
- @NonNull HttpServletRequest request,
- @NonNull HttpServletResponse response,
- @NonNull FilterChain filterChain
- ) throws ServletException, IOException {
- if (request.getServletPath().contains("/api/v1/auth")) {
- filterChain.doFilter(request, response);
- return;
+ public JwtAuthenticationFilter(JwtService jwtService,
+ UserDetailsService userDetailsService,
+ TokenRepository tokenRepository) {
+ this.jwtService = jwtService;
+ this.userDetailsService = userDetailsService;
+ this.tokenRepository = tokenRepository;
}
- final String authHeader = request.getHeader("Authorization");
- final String jwt;
- final String userEmail;
- if (authHeader == null ||!authHeader.startsWith("Bearer ")) {
- filterChain.doFilter(request, response);
- return;
- }
- jwt = authHeader.substring(7);
- userEmail = jwtService.extractUsername(jwt);
- if (userEmail != null && SecurityContextHolder.getContext().getAuthentication() == null) {
- UserDetails userDetails = this.userDetailsService.loadUserByUsername(userEmail);
- var isTokenValid = tokenRepository.findByToken(jwt)
- .map(t -> !t.isExpired() && !t.isRevoked())
- .orElse(false);
- if (jwtService.isTokenValid(jwt, userDetails) && isTokenValid) {
- UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
- userDetails,
- null,
- userDetails.getAuthorities()
- );
- authToken.setDetails(
- new WebAuthenticationDetailsSource().buildDetails(request)
- );
- SecurityContextHolder.getContext().setAuthentication(authToken);
- }
+
+ @Override
+ protected void doFilterInternal(
+ @NonNull HttpServletRequest request,
+ @NonNull HttpServletResponse response,
+ @NonNull FilterChain filterChain
+ ) throws ServletException, IOException {
+ if (request.getServletPath().contains("/api/v1/auth")) {
+ filterChain.doFilter(request, response);
+ return;
+ }
+ final String authHeader = request.getHeader("Authorization");
+ final String jwt;
+ final String userEmail;
+ if (authHeader == null || !authHeader.startsWith("Bearer ")) {
+ filterChain.doFilter(request, response);
+ return;
+ }
+ jwt = authHeader.substring(7);
+ userEmail = jwtService.extractUsername(jwt);
+ if (userEmail != null && SecurityContextHolder.getContext().getAuthentication() == null) {
+ UserDetails userDetails = this.userDetailsService.loadUserByUsername(userEmail);
+ var isTokenValid = tokenRepository.findByToken(jwt)
+ .map(t -> !t.isExpired() && !t.isRevoked())
+ .orElse(false);
+ if (jwtService.isTokenValid(jwt, userDetails) && isTokenValid) {
+ UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
+ userDetails,
+ null,
+ userDetails.getAuthorities()
+ );
+ authToken.setDetails(
+ new WebAuthenticationDetailsSource().buildDetails(request)
+ );
+ SecurityContextHolder.getContext().setAuthentication(authToken);
+ }
+ }
+ filterChain.doFilter(request, response);
}
- filterChain.doFilter(request, response);
- }
}
diff --git a/src/main/java/com/alibou/security/config/LogoutService.java b/src/main/java/com/alibou/security/config/LogoutService.java
index 0784565..932cc3c 100644
--- a/src/main/java/com/alibou/security/config/LogoutService.java
+++ b/src/main/java/com/alibou/security/config/LogoutService.java
@@ -1,39 +1,41 @@
package com.alibou.security.config;
-import com.alibou.security.token.TokenRepository;
+import com.alibou.security.auth.repository.TokenRepository;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
-import lombok.RequiredArgsConstructor;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.stereotype.Service;
@Service
-@RequiredArgsConstructor
public class LogoutService implements LogoutHandler {
- private final TokenRepository tokenRepository;
+ private final TokenRepository tokenRepository;
- @Override
- public void logout(
- HttpServletRequest request,
- HttpServletResponse response,
- Authentication authentication
- ) {
- final String authHeader = request.getHeader("Authorization");
- final String jwt;
- if (authHeader == null ||!authHeader.startsWith("Bearer ")) {
- return;
+ public LogoutService(TokenRepository tokenRepository) {
+ this.tokenRepository = tokenRepository;
}
- jwt = authHeader.substring(7);
- var storedToken = tokenRepository.findByToken(jwt)
- .orElse(null);
- if (storedToken != null) {
- storedToken.setExpired(true);
- storedToken.setRevoked(true);
- tokenRepository.save(storedToken);
- SecurityContextHolder.clearContext();
+
+ @Override
+ public void logout(
+ HttpServletRequest request,
+ HttpServletResponse response,
+ Authentication authentication
+ ) {
+ final String authHeader = request.getHeader("Authorization");
+ final String jwt;
+ if (authHeader == null || !authHeader.startsWith("Bearer ")) {
+ return;
+ }
+ jwt = authHeader.substring(7);
+ var storedToken = tokenRepository.findByToken(jwt)
+ .orElse(null);
+ if (storedToken != null) {
+ storedToken.setExpired(true);
+ storedToken.setRevoked(true);
+ tokenRepository.save(storedToken);
+ SecurityContextHolder.clearContext();
+ }
}
- }
}
diff --git a/src/main/java/com/alibou/security/config/SecurityConfiguration.java b/src/main/java/com/alibou/security/config/SecurityConfiguration.java
index e4aefe6..1ca2cd7 100644
--- a/src/main/java/com/alibou/security/config/SecurityConfiguration.java
+++ b/src/main/java/com/alibou/security/config/SecurityConfiguration.java
@@ -1,6 +1,5 @@
package com.alibou.security.config;
-import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationProvider;
@@ -13,16 +12,12 @@
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutHandler;
-import static com.alibou.security.user.Permission.ADMIN_CREATE;
-import static com.alibou.security.user.Permission.ADMIN_DELETE;
-import static com.alibou.security.user.Permission.ADMIN_READ;
-import static com.alibou.security.user.Permission.ADMIN_UPDATE;
-import static com.alibou.security.user.Permission.MANAGER_CREATE;
-import static com.alibou.security.user.Permission.MANAGER_DELETE;
-import static com.alibou.security.user.Permission.MANAGER_READ;
-import static com.alibou.security.user.Permission.MANAGER_UPDATE;
-import static com.alibou.security.user.Role.ADMIN;
-import static com.alibou.security.user.Role.MANAGER;
+import static com.alibou.security.user.model.Permission.ADMIN_CREATE;
+import static com.alibou.security.user.model.Permission.ADMIN_DELETE;
+import static com.alibou.security.user.model.Permission.ADMIN_READ;
+import static com.alibou.security.user.model.Permission.ADMIN_UPDATE;
+
+import static com.alibou.security.user.model.Role.ADMIN;
import static org.springframework.http.HttpMethod.DELETE;
import static org.springframework.http.HttpMethod.GET;
import static org.springframework.http.HttpMethod.POST;
@@ -31,7 +26,6 @@
@Configuration
@EnableWebSecurity
-@RequiredArgsConstructor
@EnableMethodSecurity
public class SecurityConfiguration {
@@ -50,6 +44,14 @@ public class SecurityConfiguration {
private final AuthenticationProvider authenticationProvider;
private final LogoutHandler logoutHandler;
+ public SecurityConfiguration(JwtAuthenticationFilter jwtAuthFilter,
+ AuthenticationProvider authenticationProvider,
+ LogoutHandler logoutHandler) {
+ this.jwtAuthFilter = jwtAuthFilter;
+ this.authenticationProvider = authenticationProvider;
+ this.logoutHandler = logoutHandler;
+ }
+
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
@@ -57,11 +59,11 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti
.authorizeHttpRequests(req ->
req.requestMatchers(WHITE_LIST_URL)
.permitAll()
- .requestMatchers("/api/v1/management/**").hasAnyRole(ADMIN.name(), MANAGER.name())
- .requestMatchers(GET, "/api/v1/management/**").hasAnyAuthority(ADMIN_READ.name(), MANAGER_READ.name())
- .requestMatchers(POST, "/api/v1/management/**").hasAnyAuthority(ADMIN_CREATE.name(), MANAGER_CREATE.name())
- .requestMatchers(PUT, "/api/v1/management/**").hasAnyAuthority(ADMIN_UPDATE.name(), MANAGER_UPDATE.name())
- .requestMatchers(DELETE, "/api/v1/management/**").hasAnyAuthority(ADMIN_DELETE.name(), MANAGER_DELETE.name())
+ .requestMatchers("/api/v1/management/**").hasAnyRole(ADMIN.name())
+ .requestMatchers(GET, "/api/v1/management/**").hasAnyAuthority(ADMIN_READ.name())
+ .requestMatchers(POST, "/api/v1/management/**").hasAnyAuthority(ADMIN_CREATE.name())
+ .requestMatchers(PUT, "/api/v1/management/**").hasAnyAuthority(ADMIN_UPDATE.name())
+ .requestMatchers(DELETE, "/api/v1/management/**").hasAnyAuthority(ADMIN_DELETE.name())
.anyRequest()
.authenticated()
)
diff --git a/src/main/java/com/alibou/security/demo/AdminController.java b/src/main/java/com/alibou/security/demo/AdminController.java
deleted file mode 100644
index 18ede65..0000000
--- a/src/main/java/com/alibou/security/demo/AdminController.java
+++ /dev/null
@@ -1,40 +0,0 @@
-package com.alibou.security.demo;
-
-import io.swagger.v3.oas.annotations.Hidden;
-import org.springframework.security.access.prepost.PreAuthorize;
-import org.springframework.web.bind.annotation.DeleteMapping;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.PutMapping;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
-
-@RestController
-@RequestMapping("/api/v1/admin")
-@PreAuthorize("hasRole('ADMIN')")
-public class AdminController {
-
- @GetMapping
- @PreAuthorize("hasAuthority('admin:read')")
- public String get() {
- return "GET:: admin controller";
- }
- @PostMapping
- @PreAuthorize("hasAuthority('admin:create')")
- @Hidden
- public String post() {
- return "POST:: admin controller";
- }
- @PutMapping
- @PreAuthorize("hasAuthority('admin:update')")
- @Hidden
- public String put() {
- return "PUT:: admin controller";
- }
- @DeleteMapping
- @PreAuthorize("hasAuthority('admin:delete')")
- @Hidden
- public String delete() {
- return "DELETE:: admin controller";
- }
-}
diff --git a/src/main/java/com/alibou/security/demo/DemoController.java b/src/main/java/com/alibou/security/demo/DemoController.java
deleted file mode 100644
index ee2c380..0000000
--- a/src/main/java/com/alibou/security/demo/DemoController.java
+++ /dev/null
@@ -1,19 +0,0 @@
-package com.alibou.security.demo;
-
-import io.swagger.v3.oas.annotations.Hidden;
-import org.springframework.http.ResponseEntity;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
-
-@RestController
-@RequestMapping("/api/v1/demo-controller")
-@Hidden
-public class DemoController {
-
- @GetMapping
- public ResponseEntity sayHello() {
- return ResponseEntity.ok("Hello from secured endpoint");
- }
-
-}
diff --git a/src/main/java/com/alibou/security/demo/ManagementController.java b/src/main/java/com/alibou/security/demo/ManagementController.java
deleted file mode 100644
index a214a9b..0000000
--- a/src/main/java/com/alibou/security/demo/ManagementController.java
+++ /dev/null
@@ -1,50 +0,0 @@
-package com.alibou.security.demo;
-
-import io.swagger.v3.oas.annotations.Operation;
-import io.swagger.v3.oas.annotations.responses.ApiResponse;
-import io.swagger.v3.oas.annotations.tags.Tag;
-import org.springframework.web.bind.annotation.DeleteMapping;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.PutMapping;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
-
-@RestController
-@RequestMapping("/api/v1/management")
-@Tag(name = "Management")
-public class ManagementController {
-
-
- @Operation(
- description = "Get endpoint for manager",
- summary = "This is a summary for management get endpoint",
- responses = {
- @ApiResponse(
- description = "Success",
- responseCode = "200"
- ),
- @ApiResponse(
- description = "Unauthorized / Invalid Token",
- responseCode = "403"
- )
- }
-
- )
- @GetMapping
- public String get() {
- return "GET:: management controller";
- }
- @PostMapping
- public String post() {
- return "POST:: management controller";
- }
- @PutMapping
- public String put() {
- return "PUT:: management controller";
- }
- @DeleteMapping
- public String delete() {
- return "DELETE:: management controller";
- }
-}
diff --git a/src/main/java/com/alibou/security/token/Token.java b/src/main/java/com/alibou/security/token/Token.java
deleted file mode 100644
index 71f3571..0000000
--- a/src/main/java/com/alibou/security/token/Token.java
+++ /dev/null
@@ -1,42 +0,0 @@
-package com.alibou.security.token;
-
-import com.alibou.security.user.User;
-import jakarta.persistence.Column;
-import jakarta.persistence.Entity;
-import jakarta.persistence.EnumType;
-import jakarta.persistence.Enumerated;
-import jakarta.persistence.FetchType;
-import jakarta.persistence.GeneratedValue;
-import jakarta.persistence.Id;
-import jakarta.persistence.JoinColumn;
-import jakarta.persistence.ManyToOne;
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-
-@Data
-@Builder
-@NoArgsConstructor
-@AllArgsConstructor
-@Entity
-public class Token {
-
- @Id
- @GeneratedValue
- public Integer id;
-
- @Column(unique = true)
- public String token;
-
- @Enumerated(EnumType.STRING)
- public TokenType tokenType = TokenType.BEARER;
-
- public boolean revoked;
-
- public boolean expired;
-
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "user_id")
- public User user;
-}
diff --git a/src/main/java/com/alibou/security/token/TokenType.java b/src/main/java/com/alibou/security/token/TokenType.java
deleted file mode 100644
index 82a8cff..0000000
--- a/src/main/java/com/alibou/security/token/TokenType.java
+++ /dev/null
@@ -1,5 +0,0 @@
-package com.alibou.security.token;
-
-public enum TokenType {
- BEARER
-}
diff --git a/src/main/java/com/alibou/security/user/ChangePasswordRequest.java b/src/main/java/com/alibou/security/user/ChangePasswordRequest.java
deleted file mode 100644
index 70bca36..0000000
--- a/src/main/java/com/alibou/security/user/ChangePasswordRequest.java
+++ /dev/null
@@ -1,15 +0,0 @@
-package com.alibou.security.user;
-
-import lombok.Builder;
-import lombok.Getter;
-import lombok.Setter;
-
-@Getter
-@Setter
-@Builder
-public class ChangePasswordRequest {
-
- private String currentPassword;
- private String newPassword;
- private String confirmationPassword;
-}
diff --git a/src/main/java/com/alibou/security/user/Permission.java b/src/main/java/com/alibou/security/user/Permission.java
deleted file mode 100644
index 16ae8b4..0000000
--- a/src/main/java/com/alibou/security/user/Permission.java
+++ /dev/null
@@ -1,22 +0,0 @@
-package com.alibou.security.user;
-
-import lombok.Getter;
-import lombok.RequiredArgsConstructor;
-
-@RequiredArgsConstructor
-public enum Permission {
-
- ADMIN_READ("admin:read"),
- ADMIN_UPDATE("admin:update"),
- ADMIN_CREATE("admin:create"),
- ADMIN_DELETE("admin:delete"),
- MANAGER_READ("management:read"),
- MANAGER_UPDATE("management:update"),
- MANAGER_CREATE("management:create"),
- MANAGER_DELETE("management:delete")
-
- ;
-
- @Getter
- private final String permission;
-}
diff --git a/src/main/java/com/alibou/security/user/Role.java b/src/main/java/com/alibou/security/user/Role.java
deleted file mode 100644
index 0ff9bd1..0000000
--- a/src/main/java/com/alibou/security/user/Role.java
+++ /dev/null
@@ -1,59 +0,0 @@
-package com.alibou.security.user;
-
-import lombok.Getter;
-import lombok.RequiredArgsConstructor;
-import org.springframework.security.core.authority.SimpleGrantedAuthority;
-
-import java.util.Collections;
-import java.util.List;
-import java.util.Set;
-import java.util.stream.Collectors;
-
-import static com.alibou.security.user.Permission.ADMIN_CREATE;
-import static com.alibou.security.user.Permission.ADMIN_DELETE;
-import static com.alibou.security.user.Permission.ADMIN_READ;
-import static com.alibou.security.user.Permission.ADMIN_UPDATE;
-import static com.alibou.security.user.Permission.MANAGER_CREATE;
-import static com.alibou.security.user.Permission.MANAGER_DELETE;
-import static com.alibou.security.user.Permission.MANAGER_READ;
-import static com.alibou.security.user.Permission.MANAGER_UPDATE;
-
-@RequiredArgsConstructor
-public enum Role {
-
- USER(Collections.emptySet()),
- ADMIN(
- Set.of(
- ADMIN_READ,
- ADMIN_UPDATE,
- ADMIN_DELETE,
- ADMIN_CREATE,
- MANAGER_READ,
- MANAGER_UPDATE,
- MANAGER_DELETE,
- MANAGER_CREATE
- )
- ),
- MANAGER(
- Set.of(
- MANAGER_READ,
- MANAGER_UPDATE,
- MANAGER_DELETE,
- MANAGER_CREATE
- )
- )
-
- ;
-
- @Getter
- private final Set permissions;
-
- public List getAuthorities() {
- var authorities = getPermissions()
- .stream()
- .map(permission -> new SimpleGrantedAuthority(permission.getPermission()))
- .collect(Collectors.toList());
- authorities.add(new SimpleGrantedAuthority("ROLE_" + this.name()));
- return authorities;
- }
-}
diff --git a/src/main/java/com/alibou/security/user/User.java b/src/main/java/com/alibou/security/user/User.java
deleted file mode 100644
index bc4e086..0000000
--- a/src/main/java/com/alibou/security/user/User.java
+++ /dev/null
@@ -1,77 +0,0 @@
-package com.alibou.security.user;
-
-import com.alibou.security.token.Token;
-import jakarta.persistence.Entity;
-import jakarta.persistence.EnumType;
-import jakarta.persistence.Enumerated;
-import jakarta.persistence.GeneratedValue;
-import jakarta.persistence.Id;
-import jakarta.persistence.OneToMany;
-import jakarta.persistence.Table;
-import java.util.Collection;
-import java.util.List;
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-import org.springframework.security.core.GrantedAuthority;
-import org.springframework.security.core.authority.SimpleGrantedAuthority;
-import org.springframework.security.core.userdetails.UserDetails;
-
-@Data
-@Builder
-@NoArgsConstructor
-@AllArgsConstructor
-@Entity
-@Table(name = "_user")
-public class User implements UserDetails {
-
- @Id
- @GeneratedValue
- private Integer id;
- private String firstname;
- private String lastname;
- private String email;
- private String password;
-
- @Enumerated(EnumType.STRING)
- private Role role;
-
- @OneToMany(mappedBy = "user")
- private List tokens;
-
- @Override
- public Collection extends GrantedAuthority> getAuthorities() {
- return role.getAuthorities();
- }
-
- @Override
- public String getPassword() {
- return password;
- }
-
- @Override
- public String getUsername() {
- return email;
- }
-
- @Override
- public boolean isAccountNonExpired() {
- return true;
- }
-
- @Override
- public boolean isAccountNonLocked() {
- return true;
- }
-
- @Override
- public boolean isCredentialsNonExpired() {
- return true;
- }
-
- @Override
- public boolean isEnabled() {
- return true;
- }
-}
diff --git a/src/main/java/com/alibou/security/user/UserService.java b/src/main/java/com/alibou/security/user/UserService.java
deleted file mode 100644
index a17181d..0000000
--- a/src/main/java/com/alibou/security/user/UserService.java
+++ /dev/null
@@ -1,35 +0,0 @@
-package com.alibou.security.user;
-
-import lombok.RequiredArgsConstructor;
-import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
-import org.springframework.security.crypto.password.PasswordEncoder;
-import org.springframework.stereotype.Service;
-
-import java.security.Principal;
-
-@Service
-@RequiredArgsConstructor
-public class UserService {
-
- private final PasswordEncoder passwordEncoder;
- private final UserRepository repository;
- public void changePassword(ChangePasswordRequest request, Principal connectedUser) {
-
- var user = (User) ((UsernamePasswordAuthenticationToken) connectedUser).getPrincipal();
-
- // check if the current password is correct
- if (!passwordEncoder.matches(request.getCurrentPassword(), user.getPassword())) {
- throw new IllegalStateException("Wrong password");
- }
- // check if the two new passwords are the same
- if (!request.getNewPassword().equals(request.getConfirmationPassword())) {
- throw new IllegalStateException("Password are not the same");
- }
-
- // update the password
- user.setPassword(passwordEncoder.encode(request.getNewPassword()));
-
- // save the new password
- repository.save(user);
- }
-}
diff --git a/src/main/java/com/alibou/security/user/UserController.java b/src/main/java/com/alibou/security/user/controller/UserController.java
similarity index 74%
rename from src/main/java/com/alibou/security/user/UserController.java
rename to src/main/java/com/alibou/security/user/controller/UserController.java
index 415be48..49bc961 100644
--- a/src/main/java/com/alibou/security/user/UserController.java
+++ b/src/main/java/com/alibou/security/user/controller/UserController.java
@@ -1,6 +1,7 @@
-package com.alibou.security.user;
+package com.alibou.security.user.controller;
-import lombok.RequiredArgsConstructor;
+import com.alibou.security.user.service.UserService;
+import com.alibou.security.user.model.ChangePasswordRequest;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.RequestBody;
@@ -11,11 +12,14 @@
@RestController
@RequestMapping("/api/v1/users")
-@RequiredArgsConstructor
public class UserController {
private final UserService service;
+ public UserController(UserService service) {
+ this.service = service;
+ }
+
@PatchMapping
public ResponseEntity> changePassword(
@RequestBody ChangePasswordRequest request,
diff --git a/src/main/java/com/alibou/security/user/model/ChangePasswordRequest.java b/src/main/java/com/alibou/security/user/model/ChangePasswordRequest.java
new file mode 100644
index 0000000..0097f66
--- /dev/null
+++ b/src/main/java/com/alibou/security/user/model/ChangePasswordRequest.java
@@ -0,0 +1,9 @@
+package com.alibou.security.user.model;
+
+
+import org.jetbrains.annotations.NotNull;
+
+public record ChangePasswordRequest(@NotNull String currentPassword,
+ @NotNull String newPassword,
+ @NotNull String confirmationPassword) {
+}
diff --git a/src/main/java/com/alibou/security/user/model/Permission.java b/src/main/java/com/alibou/security/user/model/Permission.java
new file mode 100644
index 0000000..3602a7f
--- /dev/null
+++ b/src/main/java/com/alibou/security/user/model/Permission.java
@@ -0,0 +1,19 @@
+package com.alibou.security.user.model;
+
+public enum Permission {
+
+ ADMIN_READ("admin:read"),
+ ADMIN_UPDATE("admin:update"),
+ ADMIN_CREATE("admin:create"),
+ ADMIN_DELETE("admin:delete");
+
+ private final String permission;
+
+ Permission(String permission) {
+ this.permission = permission;
+ }
+
+ public String getPermission() {
+ return permission;
+ }
+}
diff --git a/src/main/java/com/alibou/security/user/model/Role.java b/src/main/java/com/alibou/security/user/model/Role.java
new file mode 100644
index 0000000..86b3443
--- /dev/null
+++ b/src/main/java/com/alibou/security/user/model/Role.java
@@ -0,0 +1,41 @@
+package com.alibou.security.user.model;
+
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static com.alibou.security.user.model.Permission.ADMIN_CREATE;
+import static com.alibou.security.user.model.Permission.ADMIN_DELETE;
+import static com.alibou.security.user.model.Permission.ADMIN_READ;
+import static com.alibou.security.user.model.Permission.ADMIN_UPDATE;
+
+public enum Role {
+
+ USER(Collections.emptySet()),
+ ADMIN(Set.of(ADMIN_READ,
+ ADMIN_UPDATE,
+ ADMIN_DELETE,
+ ADMIN_CREATE));
+
+ private final Set permissions;
+
+ Role(Set permissions) {
+ this.permissions = permissions;
+ }
+
+ public List getAuthorities() {
+ var authorities = getPermissions()
+ .stream()
+ .map(permission -> new SimpleGrantedAuthority(permission.getPermission()))
+ .collect(Collectors.toList());
+ authorities.add(new SimpleGrantedAuthority("ROLE_" + this.name()));
+ return authorities;
+ }
+
+ public Set getPermissions() {
+ return permissions;
+ }
+}
diff --git a/src/main/java/com/alibou/security/user/model/User.java b/src/main/java/com/alibou/security/user/model/User.java
new file mode 100644
index 0000000..1c64404
--- /dev/null
+++ b/src/main/java/com/alibou/security/user/model/User.java
@@ -0,0 +1,136 @@
+package com.alibou.security.user.model;
+
+import com.alibou.security.auth.model.Token;
+import jakarta.persistence.*;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Objects;
+
+import org.hibernate.annotations.GenericGenerator;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.userdetails.UserDetails;
+
+@Entity
+@Table(name = "_user")
+public class User implements UserDetails {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.AUTO, generator = "native")
+ @GenericGenerator(name = "native")
+ private Long id;
+
+ private String firstname;
+
+ private String lastname;
+
+ private String email;
+
+ private String password;
+
+ @Enumerated(EnumType.STRING)
+ private Role role;
+
+ @OneToMany(mappedBy = "user")
+ private List tokens;
+
+ @Override
+ public Collection extends GrantedAuthority> getAuthorities() {
+ return role.getAuthorities();
+ }
+
+ @Override
+ public String getPassword() {
+ return password;
+ }
+
+ @Override
+ public String getUsername() {
+ return email;
+ }
+
+ @Override
+ public boolean isAccountNonExpired() {
+ return true;
+ }
+
+ @Override
+ public boolean isAccountNonLocked() {
+ return true;
+ }
+
+ @Override
+ public boolean isCredentialsNonExpired() {
+ return true;
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return true;
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public String getFirstname() {
+ return firstname;
+ }
+
+ public void setFirstname(String firstname) {
+ this.firstname = firstname;
+ }
+
+ public String getLastname() {
+ return lastname;
+ }
+
+ public void setLastname(String lastname) {
+ this.lastname = lastname;
+ }
+
+ public String getEmail() {
+ return email;
+ }
+
+ public void setEmail(String email) {
+ this.email = email;
+ }
+
+ public void setPassword(String password) {
+ this.password = password;
+ }
+
+ public Role getRole() {
+ return role;
+ }
+
+ public void setRole(Role role) {
+ this.role = role;
+ }
+
+ public List getTokens() {
+ return tokens;
+ }
+
+ public void setTokens(List tokens) {
+ this.tokens = tokens;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ User user = (User) o;
+ return Objects.equals(id, user.id) && Objects.equals(firstname, user.firstname) && Objects.equals(lastname, user.lastname) && Objects.equals(email, user.email) && Objects.equals(password, user.password) && role == user.role && Objects.equals(tokens, user.tokens);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(id, firstname, lastname, email, password, role, tokens);
+ }
+}
diff --git a/src/main/java/com/alibou/security/user/UserRepository.java b/src/main/java/com/alibou/security/user/repository/UserRepository.java
similarity index 70%
rename from src/main/java/com/alibou/security/user/UserRepository.java
rename to src/main/java/com/alibou/security/user/repository/UserRepository.java
index a979ad6..403ccbd 100644
--- a/src/main/java/com/alibou/security/user/UserRepository.java
+++ b/src/main/java/com/alibou/security/user/repository/UserRepository.java
@@ -1,6 +1,8 @@
-package com.alibou.security.user;
+package com.alibou.security.user.repository;
import java.util.Optional;
+
+import com.alibou.security.user.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository {
diff --git a/src/main/java/com/alibou/security/user/service/UserService.java b/src/main/java/com/alibou/security/user/service/UserService.java
new file mode 100644
index 0000000..d3f02ca
--- /dev/null
+++ b/src/main/java/com/alibou/security/user/service/UserService.java
@@ -0,0 +1,10 @@
+package com.alibou.security.user.service;
+
+import com.alibou.security.user.model.ChangePasswordRequest;
+import org.jetbrains.annotations.NotNull;
+
+import java.security.Principal;
+
+public interface UserService {
+ void changePassword(@NotNull ChangePasswordRequest request, @NotNull Principal connectedUser);
+}
diff --git a/src/main/java/com/alibou/security/user/service/impl/UserServiceImpl.java b/src/main/java/com/alibou/security/user/service/impl/UserServiceImpl.java
new file mode 100644
index 0000000..7f3b8ad
--- /dev/null
+++ b/src/main/java/com/alibou/security/user/service/impl/UserServiceImpl.java
@@ -0,0 +1,46 @@
+package com.alibou.security.user.service.impl;
+
+import com.alibou.security.user.model.ChangePasswordRequest;
+import com.alibou.security.user.model.User;
+import com.alibou.security.user.repository.UserRepository;
+import com.alibou.security.user.service.UserService;
+import org.jetbrains.annotations.NotNull;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.stereotype.Service;
+
+import java.security.Principal;
+
+@Service
+public class UserServiceImpl implements UserService {
+
+ private final PasswordEncoder passwordEncoder;
+ private final UserRepository repository;
+
+ public UserServiceImpl(PasswordEncoder passwordEncoder,
+ UserRepository repository) {
+ this.passwordEncoder = passwordEncoder;
+ this.repository = repository;
+ }
+
+ @Override
+ public void changePassword(@NotNull ChangePasswordRequest request, @NotNull Principal connectedUser) {
+
+ var user = (User) ((UsernamePasswordAuthenticationToken) connectedUser).getPrincipal();
+
+ // check if the current password is correct
+ if (!passwordEncoder.matches(request.currentPassword(), user.getPassword())) {
+ throw new IllegalStateException("Wrong password");
+ }
+ // check if the two new passwords are the same
+ if (!request.newPassword().equals(request.confirmationPassword())) {
+ throw new IllegalStateException("Password are not the same");
+ }
+
+ // update the password
+ user.setPassword(passwordEncoder.encode(request.newPassword()));
+
+ // save the new password
+ repository.save(user);
+ }
+}
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index 71b71d1..e060b4d 100644
--- a/src/main/resources/application.yml
+++ b/src/main/resources/application.yml
@@ -1,18 +1,19 @@
spring:
datasource:
- url: jdbc:postgresql://localhost:5432/jwt_security
- username: username
- password: password
- driver-class-name: org.postgresql.Driver
+ url: jdbc:mysql://localhost:3306/warehouse
+ username: root
+ password: Mert121217
+ driver-class-name: com.mysql.cj.jdbc.Driver
+
jpa:
hibernate:
- ddl-auto: create-drop
- show-sql: false
+ ddl-auto: none
+ show-sql: true
properties:
hibernate:
format_sql: true
- database: postgresql
- database-platform: org.hibernate.dialect.PostgreSQLDialect
+ database: mysql
+ database-platform: org.hibernate.dialect.MySQLDialect
application:
security: