Я пытаюсь реализовать токен обновления в своем проекте Spring Boot/Reactjs. Я храню токен обновления в HttpOnly в своем бэкэнде, и при тестировании с помощью Postman все работает нормально. Но при тестировании в браузере файл cookie токена обновления не может быть найден на серверной стороне и, следовательно, в соответствии с моими конфигурациями выдает ошибку 403. Любая помощь приветствуется. Ниже приведен мой код.
AuthController:
@RestController
@RequestMapping("${api.prefix}/auth")
public class AuthController {
private final AuthenticationManager authenticationManager;
private final JwtUtils jwtUtils;
private final CookieUtils cookieUtils;
private final ShopUserDetailsService userDetailsService;
@Value("${auth.token.refreshExpirationInMils}")
private Long refreshExpirationTime;
@PostMapping("/login")
public ResponseEntity authenticateUser(@RequestBody LoginRequest loginRequest, HttpServletResponse response) {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(loginRequest.getEmail(), loginRequest.getPassword()));
String accessToken = jwtUtils.generateAccessTokenForUser(authentication);
String refreshToken = jwtUtils.generateRefreshToken(loginRequest.getEmail());
cookieUtils.addRefreshTokenCookie(response, refreshToken, refreshExpirationTime);
Map tokens = new HashMap();
tokens.put("accessToken", accessToken);
return ResponseEntity.ok(tokens);
}
@PostMapping("/refresh-token")
public ResponseEntity refreshAccessToken(HttpServletRequest request) {
cookieUtils.logCookies(request);
String refreshToken = cookieUtils.getRefreshTokenFromCookies(request);
System.out.println("Refresh token found: " + (refreshToken != null ? "yes": "no"));
if (refreshToken != null) {
boolean isValid = jwtUtils.validateToken(refreshToken);
if (isValid) {
String usernameFromRefreshToken = jwtUtils.getUsernameFromToken(refreshToken);
try {
UserDetails userDetails = userDetailsService.loadUserByUsername(usernameFromRefreshToken);
String newAccessToken = jwtUtils.generateAccessTokenForUser(new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()));
System.out.println("New access token generated: " + (newAccessToken != null ? "yes": "no"));
if (newAccessToken != null) {
Map tokens = new HashMap();
tokens.put("accessToken", newAccessToken);
return ResponseEntity.ok(tokens);
} else {
return ResponseEntity.status(500).body("Failed to generate new access token");
}
} catch (Exception e) {
return ResponseEntity.status(500).body("An error occurred while refreshing the token: " + e.getMessage());
}
}
}
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("Invalid or expired refresh token");
}
}
Here is the CookieUtils class:
@Component
public class CookieUtils {
@Value("${app.domain}")
private String domain;
@Value("${app.useSecureCookie}")
private boolean useSecureCookie;
public void addRefreshTokenCookie(HttpServletResponse response, String refreshToken, long maxAge) {
if (response == null) {
throw new IllegalArgumentException("HttpServletResponse cannot be null");
}
Cookie refreshTokenCookie = new Cookie("refreshToken", refreshToken);
refreshTokenCookie.setHttpOnly(true);
refreshTokenCookie.setPath("/");
refreshTokenCookie.setMaxAge((int) (maxAge / 1000));
refreshTokenCookie.setSecure(useSecureCookie);
String sameSite = useSecureCookie ? "None": "Lax";
// Log cookie details
System.out.println("Setting cookie: refreshToken, Value: " + refreshToken +
", Domain: " + domain +
", Path: " + refreshTokenCookie.getPath() +
", Max-Age: " + refreshTokenCookie.getMaxAge() +
", Secure: " + useSecureCookie +
", SameSite: " + sameSite);
setResponseHeader(response, refreshTokenCookie, sameSite);
}
private void setResponseHeader(HttpServletResponse response, Cookie refreshTokenCookie, String sameSite) {
StringBuilder cookieHeader = new StringBuilder();
cookieHeader.append(refreshTokenCookie.getName()).append("=")
.append(refreshTokenCookie.getValue())
.append("; HttpOnly; Path=").append(refreshTokenCookie.getPath())
.append("; Max-Age=").append(refreshTokenCookie.getMaxAge())
.append(useSecureCookie ? "; Secure" : "")
.append("; SameSite=").append(sameSite);
if (domain != null && !domain.isEmpty()) {
cookieHeader.append("; Domain=").append(domain);
}
// Use setHeader to avoid multiple Set-Cookie headers
response.setHeader("Set-Cookie", cookieHeader.toString());
}
public String getRefreshTokenFromCookies(HttpServletRequest request) {
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
System.out.println("Names of the cookie found: " +cookie.getName());
if ("refreshToken".equals(cookie.getName())) {
return cookie.getValue();
}
}
}
return null;
}
public void logCookies(HttpServletRequest request) {
Cookie[] cookies = request.getCookies();
System.out.println("Cookies: " + (cookies != null ? Arrays.toString(cookies) : "null"));
if (cookies != null) {
for (Cookie cookie : cookies) {
System.out.println("Cookie name: " + cookie.getName() + ", value: " + cookie.getValue());
}
}
}
}
Here is my CORS configuration:
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(@NonNull CorsRegistry registry) {
registry.and mapping("/**") // Apply to all endpoints
.allowedOrigins("http://localhost:5173")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*") // Allow all headers
.allowCredentials(true); // Allow credentials
}
};
}
Here is my axios interceptor:
const BASE_URL = "http://localhost:8182/api/v1";
export const api = axios.create({
baseURL: BASE_URL,
withCredentials: true,
});
const axiosPrivate = axios.create({
baseURL: BASE_URL,
withCredentials: true, // Important for cookies
headers: {
"Content-Type": "application/json",
},
});
const refreshToken = async () => {
console.log("The Refresh token function called : ");
try {
const response = await api.post("/auth/refresh-token");
return response.data.accessToken;
} catch (error) {
console.error("Error refreshing token:", error);
return Promise.reject(error);
}
};
axiosPrivate.interceptors.request.use(
(config) => {
const accessToken = localStorage.getItem("authToken");
console.log("The Access token: " , accessToken);
if (accessToken) {
config.headers.Authorization = `Bearer ${accessToken}`;
}
return config;
},
(error) => {
return Promise.reject(error);
}
);
axiosPrivate.interceptors.response.use(
(response) => { return response; },
async (error) => {
const originalRequest = error.config;
if (error.response.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
try {
const newAccessToken = await refreshToken();
localStorage.setItem("authToken", newAccessToken);
originalRequest.headers.Authorization = `Bearer ${newAccessToken}`;
return axiosPrivate(originalRequest);
} catch (refreshError) {
return Promise.reject(refreshError);
}
}
return Promise.reject(error);
}
);
export { axiosPrivate };
Подробнее здесь: https://stackoverflow.com/questions/790 ... pplication