I am building an integrated environment, and both the front and back ends succeeded in generating tokens. However, I am experiencing 403 error and both the backend and frontend seem to be working fine. I am not sure where the error is occurring. It shows a fetch error, not a cors error, and when it is a 403 error, it says to redirect from the ApiService.js file in the front code to the login page, but it does not show a todo error on the todo page, the page you can enter after logging in.
package com.example.demo.config;
import com.example.demo.security.JwtAuthenticationFilter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.cors.CorsConfiguration;
@EnableWebSecurity
@Slf4j
@Configuration
public class WebSecurityConfig {
@Autowired
private JwtAuthenticationFilter jwtAuthenticationFilter;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.cors(cors -> cors.configurationSource(request -> {
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOriginPattern("http://localhost:3000"); // 변경된 부분
config.addAllowedHeader("*");
config.addAllowedMethod("*");
return config;
}))// CORS 설정
.csrf(csrf -> csrf.disable()) // CSRF 설정 비활성화
.httpBasic(httpBasic -> httpBasic.disable()) // HTTP Basic 인증 비활성화
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // 세션 관리 설정
.authorizeHttpRequests((authorizeRequests) ->
authorizeRequests
.requestMatchers("/", "/auth/**","/login").permitAll()
.anyRequest().authenticated()
)
.addFilterAfter(jwtAuthenticationFilter, CorsFilter.class);
return http.build();
}
}
This is WebSecurityConfig.java in Backend
package com.example.demo.security;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
@Slf4j
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Autowired
private TokenProvider tokenProvider;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
try {
// 요청에서 토큰 추출 및 로깅
String token = parseBearerToken(request);
log.info("Received token from request: {}", token);
if (token != null && !token.equalsIgnoreCase("null")) {
// 토큰 검증 및 로깅
String userId = tokenProvider.validateAndGetUserId(token);
log.info("Authenticated user ID: {}", userId);
// 인증 완료; SecurityContextHolder에 등록
AbstractAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
userId,
null,
AuthorityUtils.NO_AUTHORITIES
);
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContext securityContext = SecurityContextHolder.createEmptyContext();
securityContext.setAuthentication(authentication);
SecurityContextHolder.setContext(securityContext);
}
} catch (Exception ex) {
log.error("Could not set user authentication in security context", ex);
}
filterChain.doFilter(request, response);
}
private String parseBearerToken(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
}
}
This is JwtAuthenticationFilter.java in Backend Spring boot Eclipse
package com.example.demo.security;
import com.example.demo.model.UserEntity;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Date;
import java.security.Key;
@Slf4j
@Service
public class TokenProvider {
private static final Key key = Keys.secretKeyFor(SignatureAlgorithm.HS512);
public String create(UserEntity userEntity) {
// 기한을 현재부터 1일로 설정
Date expiryDate = Date.from(
Instant.now()
.plus(1, ChronoUnit.DAYS));
// JWT Token 생성
return Jwts.builder()
// header에 들어갈 내용 및 서명을 하기 위한 시크릿 키
.signWith(key)
// payload에 들어갈 내용
.setSubject(userEntity.getId()) // sub
.setIssuer("demo app") // iss
.setIssuedAt(new Date()) // iat
.setExpiration(expiryDate) // exp
.compact();
}
public String validateAndGetUserId(String token) {
// parseClaimsJws 메서드가 Base64로 디코딩 및 파싱.
// 즉, 헤더와 페이로드를 setSigningKey로 넘어온 시크릿을 이용해 서명 후, token의 서명과 비교.
// 위조되지 않았다면 페이로드(Claims) 리턴
// 그 중 우리는 userId가 필요하므로 getBody를 부른다.
Claims claims = Jwts.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(token)
.getBody();
return claims.getSubject();
}
}
This is TokenProvider.java in Backend
// ApiService.js
import { API_BASE_URL, ACCESS_TOKEN } from "../app-config";
export function call(api, method, request) {
let headers = new Headers({
"Content-Type": "application/json",
});
const accessToken = localStorage.getItem(ACCESS_TOKEN);
if (accessToken && accessToken !== null) {
headers.append("Authorization", "Bearer " + accessToken);
}
let options = {
headers: headers,
url: API_BASE_URL + api,
method: method,
};
if (request) {
options.body = JSON.stringify(request);
}
return fetch(options.url, options)
.then((response) => {
if (!response.ok) {
throw new Error(response.status);
}
return response.json();
})
.catch((error) => {
console.log(error);
if (error.message === "403") {
window.location.href = "/login";
}
return Promise.reject(error);
});
}
export function signin(userDTO) {
return call("/auth/signin", "POST", userDTO).then((response) => {
if (response.token) {
localStorage.setItem(ACCESS_TOKEN, response.token);
window.location.href = "/";
}
});
}
export function signout() {
localStorage.setItem(ACCESS_TOKEN, null);
window.location.href = "/login";
}
export function signup(userDTO) {
return call("/auth/signup", "POST", userDTO);
}
And this is ApiService.js in Frontend.
error message is 'Request URL: http://localhost:8080/todo Request Method: GET Status Code: 403 Forbidden : [::1]:8080 Policy: strict-origin-when-cross-origin Access-Control-Allow-Credentials: true Access-Control-Allow-Origin: http://localhost:3000 Cache-Control: no-cache, no-store, max-age=0, must-revalidate Connection: keep-alive Content-Length: 0 Date: Sun, 10 Mar 2024 13:19:33 GMT Expires: 0 Keep-Alive: timeout=60 Pragma: no-cache Vary: Origin Vary: Access-Control-Request-Method Vary: Access-Control-Request-Headers X-Content-Type-Options: nosniff X-Frame-Options: DENY X-Xss-Protection: 0 Accept: / Accept-Encoding: gzip, deflate, br, zstd Accept-Language: en-US,en;q=0.9,ko;q=0.8 Connection: keep-alive Content-Type: application/json Host: localhost:8080 Origin: http://localhost:3000 Referer: http://localhost:3000/ Sec-Ch-Ua: "Chromium";v="122", "Not(A:Brand";v="24", "Google Chrome";v="122" Sec-Ch-Ua-Mobile: ?0 Sec-Ch-Ua-Platform: "Windows" Sec-Fetch-Dest: empty Sec-Fetch-Mode: cors Sec-Fetch-Site: same-site User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36'
And Also preflight occurs well.
I checked all codes in backend, Frontend. I Checked Tokens. I can sign in with POST commands from the backend to postman on my own. It doesn't seem to be a CORS error. If I see authorizer token on the front end, it works fine. Tokens both generate normally, but they don't seem to be allowed on the backend.