Merge branch 'security_branch'
This commit is contained in:
7
pom.xml
7
pom.xml
@@ -64,10 +64,9 @@
|
|||||||
<version>0.11.5</version>
|
<version>0.11.5</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.projectlombok</groupId>
|
<groupId>jakarta.validation</groupId>
|
||||||
<artifactId>lombok</artifactId>
|
<artifactId>jakarta.validation-api</artifactId>
|
||||||
<version>1.18.20</version>
|
<version>2.0.2</version>
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
<build>
|
<build>
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
package com.faf223.expensetrackerfaf.config;
|
package com.faf223.expensetrackerfaf.config;
|
||||||
|
|
||||||
|
import com.faf223.expensetrackerfaf.controller.auth.ErrorResponse;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import io.jsonwebtoken.ExpiredJwtException;
|
||||||
import jakarta.servlet.FilterChain;
|
import jakarta.servlet.FilterChain;
|
||||||
import jakarta.servlet.ServletException;
|
import jakarta.servlet.ServletException;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
@@ -15,7 +18,6 @@ import org.springframework.stereotype.Component;
|
|||||||
import org.springframework.web.filter.OncePerRequestFilter;
|
import org.springframework.web.filter.OncePerRequestFilter;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
||||||
@@ -35,22 +37,37 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
|||||||
}
|
}
|
||||||
final String authHeader = request.getHeader("Authorization");
|
final String authHeader = request.getHeader("Authorization");
|
||||||
final String jwt;
|
final String jwt;
|
||||||
final String userEmail;
|
String userEmail;
|
||||||
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
|
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
|
||||||
filterChain.doFilter(request, response);
|
filterChain.doFilter(request, response);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
jwt = authHeader.substring(7);
|
jwt = authHeader.substring(7);
|
||||||
userEmail = jwtService.extractUsername(jwt);
|
|
||||||
if (userEmail != null && SecurityContextHolder.getContext().getAuthentication() == null) {
|
try {
|
||||||
UserDetails userDetails = this.userDetailsService.loadUserByUsername(userEmail);
|
userEmail = jwtService.extractUsername(jwt);
|
||||||
if (jwtService.isTokenValid(jwt, userDetails)) {
|
if (userEmail != null && SecurityContextHolder.getContext().getAuthentication() == null) {
|
||||||
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
|
UserDetails userDetails = this.userDetailsService.loadUserByUsername(userEmail);
|
||||||
userDetails, null, userDetails.getAuthorities());
|
if (jwtService.isTokenValid(jwt, userDetails)) {
|
||||||
authToken.setDetails(new WebAuthenticationDetailsSource()
|
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
|
||||||
.buildDetails(request));
|
userDetails, null, userDetails.getAuthorities());
|
||||||
SecurityContextHolder.getContext().setAuthentication(authToken);
|
authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
|
||||||
|
SecurityContextHolder.getContext().setAuthentication(authToken);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} catch (ExpiredJwtException e) {
|
||||||
|
// Token is expired; return a custom response
|
||||||
|
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
|
||||||
|
response.setContentType("application/json");
|
||||||
|
|
||||||
|
ErrorResponse errorResponse = new ErrorResponse("TokenExpired", "Your session has expired. Please log in again.");
|
||||||
|
ObjectMapper objectMapper = new ObjectMapper(); // You may need to import ObjectMapper
|
||||||
|
response.getWriter().write(objectMapper.writeValueAsString(errorResponse));
|
||||||
|
|
||||||
|
|
||||||
|
response.getWriter().flush();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
filterChain.doFilter(request, response);
|
filterChain.doFilter(request, response);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,6 +38,10 @@ public class JwtService {
|
|||||||
return generateToken(new HashMap<>(), userDetails);
|
return generateToken(new HashMap<>(), userDetails);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String generateRefreshToken(UserDetails userDetails) {
|
||||||
|
return generateRefreshToken(new HashMap<>(), userDetails);
|
||||||
|
}
|
||||||
|
|
||||||
public String generateToken(
|
public String generateToken(
|
||||||
Map<String, Object> extraClaims,
|
Map<String, Object> extraClaims,
|
||||||
UserDetails userDetails
|
UserDetails userDetails
|
||||||
@@ -45,6 +49,13 @@ public class JwtService {
|
|||||||
return buildToken(extraClaims, userDetails, jwtExpiration);
|
return buildToken(extraClaims, userDetails, jwtExpiration);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String generateRefreshToken(
|
||||||
|
Map<String, Object> extraClaims,
|
||||||
|
UserDetails userDetails
|
||||||
|
) {
|
||||||
|
return buildToken(extraClaims, userDetails, refreshExpiration);
|
||||||
|
}
|
||||||
|
|
||||||
private String buildToken(Map<String, Object> extraClaims, UserDetails userDetails, long expiration) {
|
private String buildToken(Map<String, Object> extraClaims, UserDetails userDetails, long expiration) {
|
||||||
return Jwts
|
return Jwts
|
||||||
.builder()
|
.builder()
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ 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.security.authentication.AuthenticationProvider;
|
import org.springframework.security.authentication.AuthenticationProvider;
|
||||||
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
|
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.http.SessionCreationPolicy;
|
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||||
@@ -14,7 +14,7 @@ import org.springframework.security.web.authentication.UsernamePasswordAuthentic
|
|||||||
@Configuration
|
@Configuration
|
||||||
@EnableWebSecurity
|
@EnableWebSecurity
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@EnableGlobalMethodSecurity(prePostEnabled = true)
|
@EnableMethodSecurity
|
||||||
public class SecurityConfiguration {
|
public class SecurityConfiguration {
|
||||||
|
|
||||||
private final JwtAuthenticationFilter jwtAuthFilter;
|
private final JwtAuthenticationFilter jwtAuthFilter;
|
||||||
|
|||||||
@@ -1,19 +1,17 @@
|
|||||||
package com.faf223.expensetrackerfaf.controller.auth;
|
package com.faf223.expensetrackerfaf.controller.auth;
|
||||||
|
|
||||||
import com.faf223.expensetrackerfaf.service.AuthenticationService;
|
import com.faf223.expensetrackerfaf.service.AuthenticationService;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("api/v1/auth")
|
@RequestMapping("api/v1/auth")
|
||||||
|
@RequiredArgsConstructor
|
||||||
public class AuthenticationController {
|
public class AuthenticationController {
|
||||||
|
|
||||||
private final AuthenticationService service;
|
private final AuthenticationService service;
|
||||||
|
|
||||||
public AuthenticationController(AuthenticationService service) {
|
|
||||||
this.service = service;
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping("/register")
|
@PostMapping("/register")
|
||||||
public ResponseEntity<AuthenticationResponse> register(@RequestBody RegisterRequest request) {
|
public ResponseEntity<AuthenticationResponse> register(@RequestBody RegisterRequest request) {
|
||||||
return ResponseEntity.ok(service.register(request));
|
return ResponseEntity.ok(service.register(request));
|
||||||
@@ -23,4 +21,9 @@ public class AuthenticationController {
|
|||||||
public ResponseEntity<AuthenticationResponse> authenticate(@RequestBody AuthenticationRequest request) {
|
public ResponseEntity<AuthenticationResponse> authenticate(@RequestBody AuthenticationRequest request) {
|
||||||
return ResponseEntity.ok(service.authenticate(request));
|
return ResponseEntity.ok(service.authenticate(request));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping("/refreshtoken")
|
||||||
|
public ResponseEntity<AuthenticationResponse> refreshAccessToken(@RequestBody TokenRefreshRequest request) {
|
||||||
|
return ResponseEntity.ok(service.refreshAccessToken(request));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.faf223.expensetrackerfaf.controller.auth;
|
package com.faf223.expensetrackerfaf.controller.auth;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
@@ -11,5 +12,8 @@ import lombok.NoArgsConstructor;
|
|||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
public class AuthenticationResponse {
|
public class AuthenticationResponse {
|
||||||
|
|
||||||
private String token;
|
@JsonProperty("access_token")
|
||||||
|
private String accessToken;
|
||||||
|
@JsonProperty("refresh_token")
|
||||||
|
private String refreshToken;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.faf223.expensetrackerfaf.controller.auth;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public class ErrorResponse {
|
||||||
|
private String error;
|
||||||
|
private String message;
|
||||||
|
|
||||||
|
public ErrorResponse(String error, String message) {
|
||||||
|
this.error = error;
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package com.faf223.expensetrackerfaf.controller.auth;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class TokenRefreshRequest {
|
||||||
|
private String refreshToken;
|
||||||
|
}
|
||||||
@@ -7,4 +7,6 @@ import java.util.Optional;
|
|||||||
|
|
||||||
public interface UserRepository extends JpaRepository<User, String> {
|
public interface UserRepository extends JpaRepository<User, String> {
|
||||||
Optional<User> getUserByUserUuid(String userUuid);
|
Optional<User> getUserByUserUuid(String userUuid);
|
||||||
|
|
||||||
|
Optional<User> findByUsername(String username);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import com.faf223.expensetrackerfaf.config.JwtService;
|
|||||||
import com.faf223.expensetrackerfaf.controller.auth.AuthenticationRequest;
|
import com.faf223.expensetrackerfaf.controller.auth.AuthenticationRequest;
|
||||||
import com.faf223.expensetrackerfaf.controller.auth.AuthenticationResponse;
|
import com.faf223.expensetrackerfaf.controller.auth.AuthenticationResponse;
|
||||||
import com.faf223.expensetrackerfaf.controller.auth.RegisterRequest;
|
import com.faf223.expensetrackerfaf.controller.auth.RegisterRequest;
|
||||||
|
import com.faf223.expensetrackerfaf.controller.auth.TokenRefreshRequest;
|
||||||
import com.faf223.expensetrackerfaf.model.Credential;
|
import com.faf223.expensetrackerfaf.model.Credential;
|
||||||
import com.faf223.expensetrackerfaf.model.User;
|
import com.faf223.expensetrackerfaf.model.User;
|
||||||
import com.faf223.expensetrackerfaf.repository.CredentialRepository;
|
import com.faf223.expensetrackerfaf.repository.CredentialRepository;
|
||||||
@@ -12,10 +13,13 @@ import com.faf223.expensetrackerfaf.security.PersonDetails;
|
|||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.security.authentication.AuthenticationManager;
|
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.UsernameNotFoundException;
|
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class AuthenticationService {
|
public class AuthenticationService {
|
||||||
@@ -38,9 +42,13 @@ public class AuthenticationService {
|
|||||||
Credential credential = new Credential(user, request.getEmail(), passwordEncoder.encode(request.getPassword()));
|
Credential credential = new Credential(user, request.getEmail(), passwordEncoder.encode(request.getPassword()));
|
||||||
credentialRepository.save(credential);
|
credentialRepository.save(credential);
|
||||||
|
|
||||||
String jwtToken = jwtService.generateToken(new PersonDetails(credential));
|
UserDetails userDetails = new PersonDetails(credential);
|
||||||
|
String jwtToken = jwtService.generateToken(userDetails);
|
||||||
|
String refreshToken = jwtService.generateRefreshToken(userDetails);
|
||||||
|
|
||||||
return AuthenticationResponse.builder()
|
return AuthenticationResponse.builder()
|
||||||
.token(jwtToken)
|
.accessToken(jwtToken)
|
||||||
|
.refreshToken(refreshToken)
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,10 +57,30 @@ public class AuthenticationService {
|
|||||||
|
|
||||||
Credential credential = credentialRepository.findByEmail(request.getEmail()).orElseThrow((() -> new UsernameNotFoundException("User not found")));
|
Credential credential = credentialRepository.findByEmail(request.getEmail()).orElseThrow((() -> new UsernameNotFoundException("User not found")));
|
||||||
|
|
||||||
String jwtToken = jwtService.generateToken(new PersonDetails(credential));
|
UserDetails userDetails = new PersonDetails(credential);
|
||||||
|
String jwtToken = jwtService.generateToken(userDetails);
|
||||||
|
String refreshToken = jwtService.generateRefreshToken(userDetails);
|
||||||
return AuthenticationResponse.builder()
|
return AuthenticationResponse.builder()
|
||||||
.token(jwtToken)
|
.accessToken(jwtToken)
|
||||||
|
.refreshToken(refreshToken)
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public AuthenticationResponse refreshAccessToken(TokenRefreshRequest refreshRequest) {
|
||||||
|
String refreshToken = refreshRequest.getRefreshToken();
|
||||||
|
|
||||||
|
Optional<Credential> credential = credentialRepository.findByEmail(jwtService.extractUsername(refreshToken));
|
||||||
|
if (credential.isPresent()) {
|
||||||
|
UserDetails userDetails = new PersonDetails(credential.get());
|
||||||
|
|
||||||
|
String jwtToken = jwtService.generateToken(userDetails);
|
||||||
|
return AuthenticationResponse.builder()
|
||||||
|
.accessToken(jwtToken)
|
||||||
|
.refreshToken(refreshToken)
|
||||||
|
.build();
|
||||||
|
} else {
|
||||||
|
throw new RuntimeException("Invalid or expired refresh token");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user