Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: protect routes #42

Merged
merged 13 commits into from
Mar 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 18 additions & 7 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
<sonar.organization>mandacaru-broker</sonar.organization>
<sonar.host.url>https://sonarcloud.io</sonar.host.url>
<powermock.version>2.0.2</powermock.version>
<spring.security.version>6.2.2</spring.security.version>
</properties>
<dependencies>
<dependency>
Expand All @@ -43,6 +44,22 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
<version>${spring.security.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>${spring.security.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>${spring.security.version}</version>
</dependency>

<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
Expand Down Expand Up @@ -76,12 +93,6 @@
<artifactId>java-jwt</artifactId>
<version>4.4.0</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
<version>5.0.7.RELEASE</version>
</dependency>

<!-- junit 5 -->
<dependency>
<groupId>org.junit.jupiter</groupId>
Expand All @@ -99,7 +110,7 @@
<artifactId>checkstyle</artifactId>
<version>10.13.0</version>
</dependency>
</dependencies>
</dependencies>
<build>
<plugins>
<plugin>
Expand Down
24 changes: 24 additions & 0 deletions src/main/java/com/mandacarubroker/config/ApplicationConfig.java
Original file line number Diff line number Diff line change
@@ -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;
}
}
34 changes: 32 additions & 2 deletions src/main/java/com/mandacarubroker/domain/user/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,19 @@
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")
@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(of = "id")
public class User {

public class User implements UserDetails {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private String id;
Expand All @@ -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;
}
}
58 changes: 58 additions & 0 deletions src/main/java/com/mandacarubroker/security/JwtAuthFilter.java
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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();
}
}
2 changes: 1 addition & 1 deletion src/main/java/com/mandacarubroker/service/AuthService.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public Optional<ResponseAuthUserDTO> 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);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}
5 changes: 5 additions & 0 deletions src/main/java/com/mandacarubroker/service/TokenService.java
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}

Expand Down
27 changes: 22 additions & 5 deletions src/main/java/com/mandacarubroker/service/UserService.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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();

Expand All @@ -26,8 +29,7 @@ private ResponseUserDTO userToResponseUserDTO(final User user) {
user.getFirstName(),
user.getLastName(),
user.getBirthDate(),
user.getBalance()
);
user.getBalance());
}

public List<ResponseUserDTO> getAllUsers() {
Expand All @@ -50,7 +52,7 @@ public Optional<ResponseUserDTO> 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;
}
Expand All @@ -66,7 +68,7 @@ public Optional<ResponseUserDTO> 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 -> {
Expand All @@ -85,6 +87,21 @@ public Optional<ResponseUserDTO> 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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
import java.util.Optional;

@SpringBootTest
@AutoConfigureMockMvc
@AutoConfigureMockMvc(addFilters = false)
class StockControllerIT {
@Autowired
private MockMvc mockMvc;
Expand Down
Loading
Loading