Merge pull request #38 from lumijiez/security_branch

Security branch
This commit was merged in pull request #38.
This commit is contained in:
Dima
2023-11-27 08:30:52 +02:00
committed by GitHub
8 changed files with 154 additions and 12 deletions

View File

@@ -1,10 +1,12 @@
package com.faf223.expensetrackerfaf.config;
import com.faf223.expensetrackerfaf.repository.CredentialRepository;
import com.faf223.expensetrackerfaf.repository.UserRepository;
import com.faf223.expensetrackerfaf.security.PersonDetails;
import com.faf223.expensetrackerfaf.service.CredentialService;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
@@ -18,11 +20,11 @@ import org.springframework.security.crypto.password.PasswordEncoder;
@RequiredArgsConstructor
public class ApplicationConfig {
private final CredentialService credentialService;
private final CredentialRepository credentialRepository;
@Bean
public UserDetailsService userDetailsService() {
return username -> new PersonDetails(credentialService.findByEmail(username).orElseThrow((() -> new UsernameNotFoundException("User not found"))));
return username -> new PersonDetails(credentialRepository.findByEmail(username).orElseThrow(() -> new UsernameNotFoundException("User not found")));
}
@Bean
@@ -42,4 +44,10 @@ public class ApplicationConfig {
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
@Primary
public JwtAuthenticationFilter customJwtAuthenticationFilter(JwtService jwtService, UserDetailsService userDetailsService) {
return new JwtAuthenticationFilter(jwtService, userDetailsService);
}
}

View File

@@ -62,7 +62,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
response.setContentType("application/json");
ErrorResponse errorResponse = new ErrorResponse("Your session has expired. Refresh your token.");
ObjectMapper objectMapper = new ObjectMapper(); // You may need to import ObjectMapper
ObjectMapper objectMapper = new ObjectMapper();
response.getWriter().write(objectMapper.writeValueAsString(errorResponse));

View File

@@ -5,6 +5,7 @@ import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;
@@ -16,6 +17,7 @@ import java.util.Map;
import java.util.function.Function;
@Service
@RequiredArgsConstructor
public class JwtService {
@Value("${application.security.jwt.secret-key}")
@@ -25,6 +27,7 @@ public class JwtService {
@Value("${application.security.jwt.refresh-token.expiration}")
private long refreshExpiration;
public String extractUsername(String token) {
return extractClaim(token, Claims::getSubject);
}

View File

@@ -1,31 +1,43 @@
package com.faf223.expensetrackerfaf.config;
import lombok.RequiredArgsConstructor;
import com.faf223.expensetrackerfaf.controller.auth.JwtAuthenticationSuccessHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.HttpStatusEntryPoint;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import java.util.Arrays;
import static org.springframework.security.config.Customizer.withDefaults;
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
@EnableMethodSecurity
public class SecurityConfiguration {
private final JwtAuthenticationFilter jwtAuthFilter;
private final AuthenticationProvider authenticationProvider;
private final ClientRegistrationRepository clientRegistrationRepository;
public SecurityConfiguration(JwtAuthenticationFilter jwtAuthFilter,
AuthenticationProvider authenticationProvider,
ClientRegistrationRepository clientRegistrationRepository) {
this.jwtAuthFilter = jwtAuthFilter;
this.authenticationProvider = authenticationProvider;
this.clientRegistrationRepository = clientRegistrationRepository;
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
@@ -33,15 +45,17 @@ public class SecurityConfiguration {
.cors(Customizer.withDefaults())
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/v1/auth/**").permitAll()
.anyRequest().authenticated()
)
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authenticationProvider(authenticationProvider)
.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class); // will be executed before UsernamePasswordAuthenticationFilter
.oauth2Login(withDefaults());
return http.build();
}
@Bean
public JwtAuthenticationSuccessHandler jwtAuthenticationSuccessHandler() {
return new JwtAuthenticationSuccessHandler();
}
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
@@ -54,4 +68,8 @@ public class SecurityConfiguration {
return source;
}
@Bean
public AuthenticationEntryPoint authenticationEntryPoint() {
return new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED);
}
}

View File

@@ -0,0 +1,19 @@
package com.faf223.expensetrackerfaf.controller.auth;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import java.io.IOException;
public class JwtAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
super.onAuthenticationSuccess(request, response, authentication);
}
}

View File

@@ -0,0 +1,24 @@
package com.faf223.expensetrackerfaf.controller.auth;
import com.faf223.expensetrackerfaf.service.AuthenticationService;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequiredArgsConstructor
public class OAuth2SuccessController {
private final AuthenticationService jwtService;
@GetMapping()
public AuthenticationResponse getUser(@AuthenticationPrincipal OAuth2User oAuth2User) {
AuthenticationResponse response = jwtService.register(oAuth2User);
System.out.println("Response: " + response);
return response;
}
}

View File

@@ -17,6 +17,7 @@ import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Service;
import java.util.Optional;
@@ -30,6 +31,7 @@ public class AuthenticationService {
private final PasswordEncoder passwordEncoder;
private final JwtService jwtService;
private final AuthenticationManager authenticationManager;
private final PasswordGenerator passwordGenerator;
public AuthenticationResponse register(RegisterRequest request) {
@@ -53,6 +55,56 @@ public class AuthenticationService {
.build();
}
public AuthenticationResponse register(OAuth2User oAuth2User) {
String userEmail = oAuth2User.getAttribute("email");
// Check if the user is already registered
Optional<Credential> existingCredential = credentialRepository.findByEmail(userEmail);
if (existingCredential.isPresent()) {
UserDetails userDetails = new PersonDetails(existingCredential.get());
String jwtToken = jwtService.generateToken(userDetails);
String refreshToken = jwtService.generateRefreshToken(userDetails);
return AuthenticationResponse.builder()
.accessToken(jwtToken)
.refreshToken(refreshToken)
.build();
}
String givenName = oAuth2User.getAttribute("given_name");
String familyName = oAuth2User.getAttribute("family_name");
String email = oAuth2User.getAttribute("email");
User user = User.builder()
.firstName(givenName)
.lastName(familyName)
.username(email)
.build();
String randomPassword = passwordGenerator.generateRandomPassword(8);
user.setPassword(passwordEncoder.encode(randomPassword));
userRepository.save(user);
Credential credential = new Credential(user, email, passwordEncoder.encode(randomPassword));
credentialRepository.save(credential);
UserDetails userDetails = new PersonDetails(credential);
String jwtToken = jwtService.generateToken(userDetails);
String refreshToken = jwtService.generateRefreshToken(userDetails);
System.out.println("New user: " + user);
System.out.println("New credentials: " + credential);
return AuthenticationResponse.builder()
.accessToken(jwtToken)
.refreshToken(refreshToken)
.build();
}
public AuthenticationResponse authenticate(AuthenticationRequest request) {
authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(request.getEmail(), request.getPassword()));

View File

@@ -0,0 +1,18 @@
package com.faf223.expensetrackerfaf.service;
import org.springframework.stereotype.Service;
import java.security.SecureRandom;
import java.util.Base64;
@Service
public class PasswordGenerator {
private final SecureRandom secureRandom = new SecureRandom();
public String generateRandomPassword(int length) {
byte[] randomBytes = new byte[length];
secureRandom.nextBytes(randomBytes);
return Base64.getUrlEncoder().withoutPadding().encodeToString(randomBytes);
}
}