Security branch #38

Merged
DmitriiKaban merged 6 commits from security_branch into master 2023-11-27 06:30:52 +00:00
8 changed files with 157 additions and 15 deletions
Showing only changes of commit c45cd0549f - Show all commits

View File

@@ -6,6 +6,7 @@ import com.faf223.expensetrackerfaf.security.PersonDetails;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider; import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
@@ -19,12 +20,11 @@ import org.springframework.security.crypto.password.PasswordEncoder;
@RequiredArgsConstructor @RequiredArgsConstructor
public class ApplicationConfig { public class ApplicationConfig {
private final UserRepository userRepository;
private final CredentialRepository credentialRepository; private final CredentialRepository credentialRepository;
@Bean @Bean
public UserDetailsService userDetailsService() { 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 @Bean
@@ -44,4 +44,10 @@ public class ApplicationConfig {
public PasswordEncoder passwordEncoder() { public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(); return new BCryptPasswordEncoder();
} }
@Bean
@Primary
public JwtAuthenticationFilter customJwtAuthenticationFilter(JwtService jwtService, UserDetailsService userDetailsService) {
return new JwtAuthenticationFilter(jwtService, userDetailsService);
}
} }

View File

@@ -31,8 +31,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
@NonNull HttpServletResponse response, @NonNull HttpServletResponse response,
@NonNull FilterChain filterChain @NonNull FilterChain filterChain
) throws ServletException, IOException { ) throws ServletException, IOException {
if (request.getServletPath().contains("/api/v1/auth") || request.getServletPath().contains("/github")) { if (request.getServletPath().contains("/api/v1/auth")) {
System.out.println("hi");
filterChain.doFilter(request, response); filterChain.doFilter(request, response);
return; return;
} }

View File

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

View File

@@ -1,23 +1,27 @@
package com.faf223.expensetrackerfaf.config; package com.faf223.expensetrackerfaf.config;
import com.faf223.expensetrackerfaf.controller.auth.JwtAuthenticationSuccessHandler;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.config.Customizer; import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; 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.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; 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.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.SecurityFilterChain; 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.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource; import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import java.util.Arrays; import java.util.Arrays;
import static org.springframework.security.config.Customizer.withDefaults;
@Configuration @Configuration
@EnableWebSecurity @EnableWebSecurity
@EnableMethodSecurity @EnableMethodSecurity
@@ -41,16 +45,30 @@ public class SecurityConfiguration {
.cors(Customizer.withDefaults()) .cors(Customizer.withDefaults())
.csrf(AbstractHttpConfigurer::disable) .csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(auth -> auth .authorizeHttpRequests(auth -> auth
.requestMatchers("/api/v1/auth/**", "/github").permitAll() // .requestMatchers("/api/v1/auth/**").permitAll()
.anyRequest().authenticated() .anyRequest().authenticated()
) )
.oauth2Login(Customizer.withDefaults()) .oauth2Login(withDefaults());
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // .exceptionHandling(exceptionHandling ->
.authenticationProvider(authenticationProvider) // exceptionHandling
.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class); // will be executed before UsernamePasswordAuthenticationFilter // .authenticationEntryPoint(authenticationEntryPoint())
// )
// .oauth2Login(oauth2Login ->
// oauth2Login
// .loginPage("/login")
// .clientRegistrationRepository(clientRegistrationRepository)
// .userInfoEndpoint(userInfoEndpoint ->
// userInfoEndpoint.userService(oAuth2UserService())
// )
// .successHandler(jwtAuthenticationSuccessHandler()));
return http.build(); return http.build();
} }
@Bean
public JwtAuthenticationSuccessHandler jwtAuthenticationSuccessHandler() {
return new JwtAuthenticationSuccessHandler();
}
@Bean @Bean
public CorsConfigurationSource corsConfigurationSource() { public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration(); CorsConfiguration configuration = new CorsConfiguration();
@@ -63,8 +81,8 @@ public class SecurityConfiguration {
return source; return source;
} }
// @Bean @Bean
// public OAuth2UserService oAuth2UserService() { public AuthenticationEntryPoint authenticationEntryPoint() {
// return new DefaultOAuth2UserService(); 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.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.util.Optional; import java.util.Optional;
@@ -30,6 +31,7 @@ public class AuthenticationService {
private final PasswordEncoder passwordEncoder; private final PasswordEncoder passwordEncoder;
private final JwtService jwtService; private final JwtService jwtService;
private final AuthenticationManager authenticationManager; private final AuthenticationManager authenticationManager;
private final PasswordGenerator passwordGenerator;
public AuthenticationResponse register(RegisterRequest request) { public AuthenticationResponse register(RegisterRequest request) {
@@ -53,6 +55,58 @@ public class AuthenticationService {
.build(); .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();
}
// 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) { public AuthenticationResponse authenticate(AuthenticationRequest request) {
authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(request.getEmail(), request.getPassword())); 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);
}
}