Merge pull request #38 from lumijiez/security_branch
Security branch
This commit was merged in pull request #38.
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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()));
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user