diff --git a/pom.xml b/pom.xml
index c65ec1ae..0774bb25 100644
--- a/pom.xml
+++ b/pom.xml
@@ -18,6 +18,7 @@
mandacaru-broker
https://sonarcloud.io
2.0.2
+ 6.2.2
@@ -43,6 +44,22 @@
org.springframework.boot
spring-boot-starter-validation
+
+ org.springframework.security
+ spring-security-core
+ ${spring.security.version}
+
+
+ org.springframework.security
+ spring-security-web
+ ${spring.security.version}
+
+
+ org.springframework.security
+ spring-security-config
+ ${spring.security.version}
+
+
org.flywaydb
flyway-core
@@ -76,12 +93,6 @@
java-jwt
4.4.0
-
- org.springframework.security
- spring-security-core
- 5.0.7.RELEASE
-
-
org.junit.jupiter
@@ -99,7 +110,7 @@
checkstyle
10.13.0
-
+
diff --git a/src/main/java/com/mandacarubroker/config/ApplicationConfig.java b/src/main/java/com/mandacarubroker/config/ApplicationConfig.java
new file mode 100644
index 00000000..efa208a3
--- /dev/null
+++ b/src/main/java/com/mandacarubroker/config/ApplicationConfig.java
@@ -0,0 +1,24 @@
+package com.mandacarubroker.config;
+
+import com.mandacarubroker.service.PasswordHashingService;
+import com.mandacarubroker.service.UserService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.authentication.AuthenticationProvider;
+import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
+
+@Configuration
+@RequiredArgsConstructor
+public class ApplicationConfig {
+ private final UserService userService;
+ private final PasswordHashingService passwordHashingService;
+
+ @Bean
+ public AuthenticationProvider authenticationProvider() {
+ DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
+ authProvider.setUserDetailsService(userService);
+ authProvider.setPasswordEncoder(passwordHashingService);
+ return authProvider;
+ }
+}
diff --git a/src/main/java/com/mandacarubroker/domain/user/User.java b/src/main/java/com/mandacarubroker/domain/user/User.java
index e08e4f05..45ad39db 100644
--- a/src/main/java/com/mandacarubroker/domain/user/User.java
+++ b/src/main/java/com/mandacarubroker/domain/user/User.java
@@ -9,8 +9,11 @@
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.userdetails.UserDetails;
import java.time.LocalDate;
+import java.util.Collection;
@Table(name = "users")
@Entity(name = "users")
@@ -18,8 +21,7 @@
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(of = "id")
-public class User {
-
+public class User implements UserDetails {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private String id;
@@ -40,4 +42,32 @@ public User(final RequestUserDTO requestUserDTO) {
this.birthDate = requestUserDTO.birthDate();
this.balance = requestUserDTO.balance();
}
+
+ public Collection extends GrantedAuthority> getAuthorities() {
+ return null;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+
+ public String getUsername() {
+ return username;
+ }
+
+ public boolean isAccountNonExpired() {
+ return true;
+ }
+
+ public boolean isAccountNonLocked() {
+ return true;
+ }
+
+ public boolean isCredentialsNonExpired() {
+ return true;
+ }
+
+ public boolean isEnabled() {
+ return true;
+ }
}
diff --git a/src/main/java/com/mandacarubroker/security/JwtAuthFilter.java b/src/main/java/com/mandacarubroker/security/JwtAuthFilter.java
new file mode 100644
index 00000000..b92a657a
--- /dev/null
+++ b/src/main/java/com/mandacarubroker/security/JwtAuthFilter.java
@@ -0,0 +1,58 @@
+package com.mandacarubroker.security;
+
+import com.mandacarubroker.service.TokenService;
+import com.mandacarubroker.service.UserService;
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.RequiredArgsConstructor;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
+import org.springframework.stereotype.Component;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+import java.io.IOException;
+
+@Component
+@RequiredArgsConstructor
+public class JwtAuthFilter extends OncePerRequestFilter {
+ private final TokenService tokenService;
+ private final UserService userService;
+
+ @Override
+ protected void doFilterInternal(final HttpServletRequest request, final HttpServletResponse response, final FilterChain filterChain) throws ServletException, IOException {
+ final String authHeader = request.getHeader("Authorization");
+
+ if (authHeader == null || !authHeader.startsWith("Bearer ")) {
+ filterChain.doFilter(request, response);
+ return;
+ }
+
+ final String token = authHeader.substring(7);
+ final String userId = tokenService.getTokenSubject(token);
+
+ if (userId == null) {
+ response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+ filterChain.doFilter(request, response);
+ return;
+ }
+
+ UserDetails user = userService.loadUserByUsername(userId);
+
+ if (user == null) {
+ response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+ filterChain.doFilter(request, response);
+ return;
+ }
+
+ UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(user,null, user.getAuthorities());
+ authToken.setDetails(
+ new WebAuthenticationDetailsSource().buildDetails(request)
+ );
+ SecurityContextHolder.getContext().setAuthentication(authToken);
+ filterChain.doFilter(request, response);
+ }
+}
diff --git a/src/main/java/com/mandacarubroker/security/SecurityConfiguration.java b/src/main/java/com/mandacarubroker/security/SecurityConfiguration.java
new file mode 100644
index 00000000..5a032a22
--- /dev/null
+++ b/src/main/java/com/mandacarubroker/security/SecurityConfiguration.java
@@ -0,0 +1,43 @@
+package com.mandacarubroker.security;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.authentication.AuthenticationProvider;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
+import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
+import org.springframework.security.web.SecurityFilterChain;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+
+import static org.springframework.security.config.http.SessionCreationPolicy.STATELESS;
+
+@Configuration
+@EnableWebSecurity
+@RequiredArgsConstructor
+@EnableMethodSecurity
+public class SecurityConfiguration {
+ private static final String[] WHITE_LIST_URL = {
+ "/",
+ "/auth/login",
+ "/auth/register",
+ };
+
+ private final AuthenticationProvider authenticationProvider;
+ private final JwtAuthFilter jwtAuthFilter;
+
+ @Bean
+ public SecurityFilterChain securityFilterChain(final HttpSecurity http) throws Exception {
+ http.csrf(AbstractHttpConfigurer::disable)
+ .sessionManagement(session -> session.sessionCreationPolicy(STATELESS))
+ .authorizeHttpRequests(req -> {
+ req.requestMatchers(WHITE_LIST_URL).permitAll();
+ req.anyRequest().authenticated();
+ })
+ .authenticationProvider(authenticationProvider)
+ .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class);
+
+ return http.build();
+ }
+}
diff --git a/src/main/java/com/mandacarubroker/service/AuthService.java b/src/main/java/com/mandacarubroker/service/AuthService.java
index 0fed7d12..daefe76a 100644
--- a/src/main/java/com/mandacarubroker/service/AuthService.java
+++ b/src/main/java/com/mandacarubroker/service/AuthService.java
@@ -30,7 +30,7 @@ public Optional login(final RequestAuthUserDTO requestAuthU
return Optional.empty();
}
- String userId = user.get().getId();
+ String userId = user.get().getUsername();
ResponseAuthUserDTO responseAuthUserDTO = tokenService.encodeToken(userId);
return Optional.of(responseAuthUserDTO);
}
diff --git a/src/main/java/com/mandacarubroker/service/PasswordHashingService.java b/src/main/java/com/mandacarubroker/service/PasswordHashingService.java
index 437e1273..3c360123 100644
--- a/src/main/java/com/mandacarubroker/service/PasswordHashingService.java
+++ b/src/main/java/com/mandacarubroker/service/PasswordHashingService.java
@@ -1,17 +1,18 @@
package com.mandacarubroker.service;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
@Service
-public class PasswordHashingService {
+public class PasswordHashingService implements PasswordEncoder {
private final BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
- public String hashPassword(final String plainPassword) {
+ public String encode(final CharSequence plainPassword) {
return passwordEncoder.encode(plainPassword);
}
- public boolean matches(final String plainPassword, final String hashedPassword) {
- return passwordEncoder.matches(plainPassword, hashedPassword);
+ public boolean matches(final CharSequence rawPassword, final String encodedPassword) {
+ return passwordEncoder.matches(rawPassword, encodedPassword);
}
}
diff --git a/src/main/java/com/mandacarubroker/service/TokenService.java b/src/main/java/com/mandacarubroker/service/TokenService.java
index af0370ba..ee70332c 100644
--- a/src/main/java/com/mandacarubroker/service/TokenService.java
+++ b/src/main/java/com/mandacarubroker/service/TokenService.java
@@ -46,6 +46,11 @@ private ResponseAuthUserDTO tryToEncodeToken(final String subject) {
public String getTokenSubject(final String token) {
DecodedJWT decodedToken = decodeUserToken(token);
+
+ if (decodedToken == null) {
+ return null;
+ }
+
return decodedToken.getSubject();
}
diff --git a/src/main/java/com/mandacarubroker/service/UserService.java b/src/main/java/com/mandacarubroker/service/UserService.java
index 3ebbc229..848ca5d9 100644
--- a/src/main/java/com/mandacarubroker/service/UserService.java
+++ b/src/main/java/com/mandacarubroker/service/UserService.java
@@ -4,6 +4,9 @@
import com.mandacarubroker.domain.user.ResponseUserDTO;
import com.mandacarubroker.domain.user.User;
import com.mandacarubroker.domain.user.UserRepository;
+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 java.util.ArrayList;
@@ -13,7 +16,7 @@
import static com.mandacarubroker.validation.RecordValidation.validateRequestDTO;
@Service
-public class UserService {
+public class UserService implements UserDetailsService {
private final UserRepository userRepository;
private final PasswordHashingService passwordHashingService = new PasswordHashingService();
@@ -26,8 +29,7 @@ private ResponseUserDTO userToResponseUserDTO(final User user) {
user.getFirstName(),
user.getLastName(),
user.getBirthDate(),
- user.getBalance()
- );
+ user.getBalance());
}
public List getAllUsers() {
@@ -50,7 +52,7 @@ public Optional getUserById(final String userId) {
private User hashPassword(final User user) {
final String rawPassword = user.getPassword();
- final String hashedPassword = passwordHashingService.hashPassword(rawPassword);
+ final String hashedPassword = passwordHashingService.encode(rawPassword);
user.setPassword(hashedPassword);
return user;
}
@@ -66,7 +68,7 @@ public Optional updateUser(final String userId, final RequestUs
validateRequestDTO(requestUserDTO);
final String rawPassword = requestUserDTO.password();
- final String hashedPassword = passwordHashingService.hashPassword(rawPassword);
+ final String hashedPassword = passwordHashingService.encode(rawPassword);
return userRepository.findById(userId)
.map(user -> {
@@ -85,6 +87,21 @@ public Optional updateUser(final String userId, final RequestUs
public void deleteUser(final String id) {
userRepository.deleteById(id);
}
+
+ public UserDetails loadUserByUsername(final String username) {
+ if (username == null || username.isEmpty()) {
+ throw new UsernameNotFoundException("Username is required");
+ }
+
+ User user = userRepository.findByUsername(username);
+
+ if (user == null) {
+ throw new UsernameNotFoundException("User not found");
+ }
+
+ return user;
+ }
+
public boolean verifyDuplicateUsername(final String userName) {
User alreadyExistingUser = userRepository.findByUsername(userName);
return alreadyExistingUser != null;
diff --git a/src/test/java/com/mandacarubroker/controller/AuthControllerTest.java b/src/test/java/com/mandacarubroker/controller/AuthControllerTest.java
index 60107fb5..5555ceee 100644
--- a/src/test/java/com/mandacarubroker/controller/AuthControllerTest.java
+++ b/src/test/java/com/mandacarubroker/controller/AuthControllerTest.java
@@ -32,7 +32,7 @@ class AuthControllerTest {
private final String invalidUsername = "invalidUsername";
private final String validPassword = "password";
private final String invalidPassword = "invalidPassword";
- private final String validHashedPassword = passwordHashingService.hashPassword(validPassword);
+ private final String validHashedPassword = passwordHashingService.encode(validPassword);
private final String validFirstName = "Lara";
private final String validLastName = "Souza";
private final LocalDate validBirthDate = LocalDate.of(1997,4,5);
diff --git a/src/test/java/com/mandacarubroker/controller/StockControllerIT.java b/src/test/java/com/mandacarubroker/controller/StockControllerIT.java
index 19120898..564168ea 100644
--- a/src/test/java/com/mandacarubroker/controller/StockControllerIT.java
+++ b/src/test/java/com/mandacarubroker/controller/StockControllerIT.java
@@ -27,7 +27,7 @@
import java.util.Optional;
@SpringBootTest
-@AutoConfigureMockMvc
+@AutoConfigureMockMvc(addFilters = false)
class StockControllerIT {
@Autowired
private MockMvc mockMvc;
diff --git a/src/test/java/com/mandacarubroker/controller/UserControllerIT.java b/src/test/java/com/mandacarubroker/controller/UserControllerIT.java
index 75a6fe19..028c5b45 100644
--- a/src/test/java/com/mandacarubroker/controller/UserControllerIT.java
+++ b/src/test/java/com/mandacarubroker/controller/UserControllerIT.java
@@ -1,6 +1,7 @@
package com.mandacarubroker.controller;
import com.fasterxml.jackson.databind.ObjectMapper;
+
import com.mandacarubroker.domain.user.RequestUserDTO;
import com.mandacarubroker.domain.user.ResponseUserDTO;
import com.mandacarubroker.domain.user.User;
@@ -26,11 +27,9 @@
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
-
@SpringBootTest
-@AutoConfigureMockMvc
+@AutoConfigureMockMvc(addFilters = false)
class UserControllerIT {
-
@Autowired
private UserRepository userRepository;
@@ -48,7 +47,7 @@ class UserControllerIT {
private final String validPassword = "#pass555";
private final String validFirstName = "Lara";
private final String validLastName = "Souza";
- private final LocalDate validBirthDate = LocalDate.of(2006,2,28);
+ private final LocalDate validBirthDate = LocalDate.of(2006, 2, 28);
private final double validBalance = 90.50;
private final RequestUserDTO invalidEmailUserDTO = new RequestUserDTO(
"marcosloiola@.yahoo.com",
@@ -57,8 +56,7 @@ class UserControllerIT {
"Marcos",
"Loiola",
LocalDate.of(2002, 2, 26),
- 0.25
- );
+ 0.25);
private final RequestUserDTO invalidPasswordUserDTO = new RequestUserDTO(
"marcosloiola@yahoo.com",
"Marcos23",
@@ -66,8 +64,7 @@ class UserControllerIT {
"Marcos",
"Loiola",
LocalDate.of(2002, 2, 26),
- 0.25
- );
+ 0.25);
private final RequestUserDTO invalidAgeUserDTO = new RequestUserDTO(
"marcosloiola@yahoo.com",
"Marcos23",
@@ -75,8 +72,7 @@ class UserControllerIT {
"Marcos",
"Loiola",
LocalDate.of(2006, 3, 2),
- 0.25
- );
+ 0.25);
private final RequestUserDTO invalidBalanceUserDTO = new RequestUserDTO(
"marcosloiola@yahoo.com",
@@ -85,8 +81,7 @@ class UserControllerIT {
"Marcos",
"Loiola",
LocalDate.of(2001, 3, 2),
- -0.001
- );
+ -0.001);
private final RequestUserDTO validUserDTO = new RequestUserDTO(
validEmail,
@@ -95,8 +90,7 @@ class UserControllerIT {
validFirstName,
validLastName,
validBirthDate,
- validBalance
- );
+ validBalance);
private final String urlRequestInvalidUser = "/users/dummy-user-id";
private User user;
@@ -113,19 +107,18 @@ void setUp() {
user.getFirstName(),
user.getLastName(),
user.getBirthDate(),
- user.getBalance()
- );
+ user.getBalance());
}
@AfterEach
void tearDown() {
User alreadyExistentUser = userRepository.findByUsername(validUsername);
- if(alreadyExistentUser != null){
+ if (alreadyExistentUser != null) {
service.deleteUser(alreadyExistentUser.getId());
}
}
- void assertResponseUserDTO(final RequestUserDTO userRequestDTO, final ResponseUserDTO receivedUser){
+ void assertResponseUserDTO(final RequestUserDTO userRequestDTO, final ResponseUserDTO receivedUser) {
assertEquals(userRequestDTO.firstName(), receivedUser.firstName());
assertEquals(userRequestDTO.lastName(), receivedUser.lastName());
assertEquals(userRequestDTO.birthDate(), receivedUser.birthDate());
diff --git a/src/test/java/com/mandacarubroker/service/AuthServiceTest.java b/src/test/java/com/mandacarubroker/service/AuthServiceTest.java
index 5bf2b7c8..a76a7865 100644
--- a/src/test/java/com/mandacarubroker/service/AuthServiceTest.java
+++ b/src/test/java/com/mandacarubroker/service/AuthServiceTest.java
@@ -70,7 +70,7 @@ void setUp() {
Mockito.when(passwordHashingService.matches(invalidPassword, validHashedPassword)).thenReturn(false);
tokenService = Mockito.mock(TokenService.class);
- Mockito.when(tokenService.encodeToken(validUser.getId())).thenReturn(new ResponseAuthUserDTO(validToken, expiresIn, tokenType));
+ Mockito.when(tokenService.encodeToken(validUser.getUsername())).thenReturn(new ResponseAuthUserDTO(validToken, expiresIn, tokenType));
authService = new AuthService(userRepository, passwordHashingService, tokenService);
}
diff --git a/src/test/java/com/mandacarubroker/service/PasswordHashingServiceTest.java b/src/test/java/com/mandacarubroker/service/PasswordHashingServiceTest.java
index bf7efc33..29371ddb 100644
--- a/src/test/java/com/mandacarubroker/service/PasswordHashingServiceTest.java
+++ b/src/test/java/com/mandacarubroker/service/PasswordHashingServiceTest.java
@@ -9,7 +9,7 @@ class PasswordHashingServiceTest {
@Test
void itShouldBeAbleToHashAndMatchRightPassword() {
final PasswordHashingService underTest = new PasswordHashingService();
- final String hashedPassword = underTest.hashPassword("password");
+ final String hashedPassword = underTest.encode("password");
final boolean matches = underTest.matches("password", hashedPassword);
assertTrue(matches);
}
@@ -17,8 +17,8 @@ void itShouldBeAbleToHashAndMatchRightPassword() {
@Test
void itShouldNotMatchWrongPassword() {
final PasswordHashingService underTest = new PasswordHashingService();
- final String hashedPassword = underTest.hashPassword("password");
+ final String hashedPassword = underTest.encode("password");
final boolean matches = underTest.matches("wrongPassword", hashedPassword);
assertFalse(matches);
}
-}
\ No newline at end of file
+}