From c45cd0549f117901b0bee72432cca9dd77b79934 Mon Sep 17 00:00:00 2001 From: Dmitrii Cravcenco Date: Tue, 21 Nov 2023 15:36:51 +0200 Subject: [PATCH] Create endpoint for google oauth, generate JWT token, save to DB by email --- .../config/ApplicationConfig.java | 10 +++- .../config/JwtAuthenticationFilter.java | 3 +- .../expensetrackerfaf/config/JwtService.java | 4 ++ .../config/SecurityConfiguration.java | 40 ++++++++++---- .../auth/JwtAuthenticationSuccessHandler.java | 19 +++++++ .../auth/OAuth2SuccessController.java | 24 +++++++++ .../service/AuthenticationService.java | 54 +++++++++++++++++++ .../service/PasswordGenerator.java | 18 +++++++ 8 files changed, 157 insertions(+), 15 deletions(-) create mode 100644 src/main/java/com/faf223/expensetrackerfaf/controller/auth/JwtAuthenticationSuccessHandler.java create mode 100644 src/main/java/com/faf223/expensetrackerfaf/controller/auth/OAuth2SuccessController.java create mode 100644 src/main/java/com/faf223/expensetrackerfaf/service/PasswordGenerator.java diff --git a/src/main/java/com/faf223/expensetrackerfaf/config/ApplicationConfig.java b/src/main/java/com/faf223/expensetrackerfaf/config/ApplicationConfig.java index 57d1823..e338a3d 100644 --- a/src/main/java/com/faf223/expensetrackerfaf/config/ApplicationConfig.java +++ b/src/main/java/com/faf223/expensetrackerfaf/config/ApplicationConfig.java @@ -6,6 +6,7 @@ import com.faf223.expensetrackerfaf.security.PersonDetails; 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; @@ -19,12 +20,11 @@ import org.springframework.security.crypto.password.PasswordEncoder; @RequiredArgsConstructor public class ApplicationConfig { - private final UserRepository userRepository; private final CredentialRepository credentialRepository; @Bean public UserDetailsService userDetailsService() { - return username -> new PersonDetails(credentialRepository.findByEmail(username).orElseThrow((() -> new UsernameNotFoundException("User not found")))); + return username -> new PersonDetails(credentialRepository.findByEmail(username).orElseThrow(() -> new UsernameNotFoundException("User not found"))); } @Bean @@ -44,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); + } + } diff --git a/src/main/java/com/faf223/expensetrackerfaf/config/JwtAuthenticationFilter.java b/src/main/java/com/faf223/expensetrackerfaf/config/JwtAuthenticationFilter.java index 4485ea6..9d710a2 100644 --- a/src/main/java/com/faf223/expensetrackerfaf/config/JwtAuthenticationFilter.java +++ b/src/main/java/com/faf223/expensetrackerfaf/config/JwtAuthenticationFilter.java @@ -31,8 +31,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { @NonNull HttpServletResponse response, @NonNull FilterChain filterChain ) throws ServletException, IOException { - if (request.getServletPath().contains("/api/v1/auth") || request.getServletPath().contains("/github")) { - System.out.println("hi"); + if (request.getServletPath().contains("/api/v1/auth")) { filterChain.doFilter(request, response); return; } diff --git a/src/main/java/com/faf223/expensetrackerfaf/config/JwtService.java b/src/main/java/com/faf223/expensetrackerfaf/config/JwtService.java index 9cf6066..07b8f2c 100644 --- a/src/main/java/com/faf223/expensetrackerfaf/config/JwtService.java +++ b/src/main/java/com/faf223/expensetrackerfaf/config/JwtService.java @@ -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}") @@ -24,6 +26,8 @@ public class JwtService { private long jwtExpiration; @Value("${application.security.jwt.refresh-token.expiration}") private long refreshExpiration; +// private final AuthenticationService authenticationService; + public String extractUsername(String token) { return extractClaim(token, Claims::getSubject); diff --git a/src/main/java/com/faf223/expensetrackerfaf/config/SecurityConfiguration.java b/src/main/java/com/faf223/expensetrackerfaf/config/SecurityConfiguration.java index 094284d..e5f71b8 100644 --- a/src/main/java/com/faf223/expensetrackerfaf/config/SecurityConfiguration.java +++ b/src/main/java/com/faf223/expensetrackerfaf/config/SecurityConfiguration.java @@ -1,23 +1,27 @@ package com.faf223.expensetrackerfaf.config; +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 @EnableMethodSecurity @@ -41,16 +45,30 @@ public class SecurityConfiguration { .cors(Customizer.withDefaults()) .csrf(AbstractHttpConfigurer::disable) .authorizeHttpRequests(auth -> auth - .requestMatchers("/api/v1/auth/**", "/github").permitAll() +// .requestMatchers("/api/v1/auth/**").permitAll() .anyRequest().authenticated() ) - .oauth2Login(Customizer.withDefaults()) - .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) - .authenticationProvider(authenticationProvider) - .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class); // will be executed before UsernamePasswordAuthenticationFilter + .oauth2Login(withDefaults()); +// .exceptionHandling(exceptionHandling -> +// exceptionHandling +// .authenticationEntryPoint(authenticationEntryPoint()) +// ) +// .oauth2Login(oauth2Login -> +// oauth2Login +// .loginPage("/login") +// .clientRegistrationRepository(clientRegistrationRepository) +// .userInfoEndpoint(userInfoEndpoint -> +// userInfoEndpoint.userService(oAuth2UserService()) +// ) +// .successHandler(jwtAuthenticationSuccessHandler())); + return http.build(); } + @Bean + public JwtAuthenticationSuccessHandler jwtAuthenticationSuccessHandler() { + return new JwtAuthenticationSuccessHandler(); + } @Bean public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration configuration = new CorsConfiguration(); @@ -63,8 +81,8 @@ public class SecurityConfiguration { return source; } -// @Bean -// public OAuth2UserService oAuth2UserService() { -// return new DefaultOAuth2UserService(); -// } + @Bean + public AuthenticationEntryPoint authenticationEntryPoint() { + return new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED); + } } diff --git a/src/main/java/com/faf223/expensetrackerfaf/controller/auth/JwtAuthenticationSuccessHandler.java b/src/main/java/com/faf223/expensetrackerfaf/controller/auth/JwtAuthenticationSuccessHandler.java new file mode 100644 index 0000000..9706cb8 --- /dev/null +++ b/src/main/java/com/faf223/expensetrackerfaf/controller/auth/JwtAuthenticationSuccessHandler.java @@ -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); + } +} + diff --git a/src/main/java/com/faf223/expensetrackerfaf/controller/auth/OAuth2SuccessController.java b/src/main/java/com/faf223/expensetrackerfaf/controller/auth/OAuth2SuccessController.java new file mode 100644 index 0000000..571207e --- /dev/null +++ b/src/main/java/com/faf223/expensetrackerfaf/controller/auth/OAuth2SuccessController.java @@ -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; + } +} \ No newline at end of file diff --git a/src/main/java/com/faf223/expensetrackerfaf/service/AuthenticationService.java b/src/main/java/com/faf223/expensetrackerfaf/service/AuthenticationService.java index 82924df..17534a1 100644 --- a/src/main/java/com/faf223/expensetrackerfaf/service/AuthenticationService.java +++ b/src/main/java/com/faf223/expensetrackerfaf/service/AuthenticationService.java @@ -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,58 @@ public class AuthenticationService { .build(); } + public AuthenticationResponse register(OAuth2User oAuth2User) { + String userEmail = oAuth2User.getAttribute("email"); + + // Check if the user is already registered + Optional 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(); + } + + // Extract user details from OAuth2User + String givenName = oAuth2User.getAttribute("given_name"); + String familyName = oAuth2User.getAttribute("family_name"); + String email = oAuth2User.getAttribute("email"); + + // Create a new User entity and save it to the database + User user = User.builder() + .firstName(givenName) + .lastName(familyName) + .username(email) // You can adjust the username as needed + .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 the registered user's authentication response + return AuthenticationResponse.builder() + .accessToken(jwtToken) + .refreshToken(refreshToken) + .build(); + } + + + public AuthenticationResponse authenticate(AuthenticationRequest request) { authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(request.getEmail(), request.getPassword())); diff --git a/src/main/java/com/faf223/expensetrackerfaf/service/PasswordGenerator.java b/src/main/java/com/faf223/expensetrackerfaf/service/PasswordGenerator.java new file mode 100644 index 0000000..98fc34c --- /dev/null +++ b/src/main/java/com/faf223/expensetrackerfaf/service/PasswordGenerator.java @@ -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); + } +}