TIL-230719(항해99 미니 프로젝트-ANABADA(3))
📝오늘 공부한 것
- 미니프로젝트 - ANABADA 사이트 만들기
(로그인 프론트와 연결 / CORS에러 / Same-Origin Policy)
⛔문제점
로그인 기능을 구현하고 fostman으로 잘 값이 들어가는 지 확인 후 main에 push하였다. 그런데 프론트와 연결하니 에러가 떴다.
package com.sparta.anabada.filter;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.sparta.blog2.dto.LoginRequestDto;
import com.sparta.blog2.entity.UserRoleEnum;
import com.sparta.blog2.jwt.JwtUtil;
import com.sparta.blog2.security.UserDetailsImpl;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import java.io.IOException;
@Slf4j(topic = "로그인 및 JWT 생성")
public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private final JwtUtil jwtUtil;
public JwtAuthenticationFilter(JwtUtil jwtUtil) {
this.jwtUtil = jwtUtil;
setFilterProcessesUrl("/api/user/login");
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
log.info("로그인 시도");
try {
LoginRequestDto requestDto = new ObjectMapper().readValue(request.getInputStream(), LoginRequestDto.class);
return getAuthenticationManager().authenticate(
new UsernamePasswordAuthenticationToken(
requestDto.getUsername(),
requestDto.getPassword(),
null
)
);
} catch (IOException e) {
log.error(e.getMessage());
throw new RuntimeException(e.getMessage());
}
}
@Override
public void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
log.info("로그인 성공 및 JWT 생성");
String username = ((UserDetailsImpl) authResult.getPrincipal()).getUsername();
UserRoleEnum role = ((UserDetailsImpl) authResult.getPrincipal()).getUser().getRole();
String token = jwtUtil.createToken(username, role);
jwtUtil.addJwtToCookie(token, response);
response.setContentType("text/plain;charset=UTF-8");
response.getWriter().write("로그인에 성공하였습니다.");
}
@Override
public void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
log.info("로그인 실패");
response.setStatus(400);
response.setContentType("text/plain;charset=UTF-8");
response.getWriter().write("회원을 찾을 수 없습니다.");
}
}
[벡엔드쪽 에러 메시지]
java.lang.RuntimeException: No content to map due to end-of-input
at [Source: (org.apache.catalina.connector.CoyoteInputStream); line: 1, column: 0]
at co m.anabada.anabada.security.filter.JwtAuthenticationFilter.attemptAuthentication(JwtAuthenticationFilter.java:46) ~[classes!/:0.0.1-SNAPSHOT]
at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:231) ~[spring-security-web-6.1.1.jar!/:6.1.1]
at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:221) ~[spring-security-web-6.1.1.jar!/:6.1.1]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) ~[spring-security-web-6.1.1.jar!/:6.1.1]
at co m.anabada.anabada.security.filter.JwtAuthorizationFilter.doFilterInternal(JwtAuthorizationFilter.java:60) ~[classes!/:0.0.1-SNAPSHOT]
java.lang.RuntimeException: No content to map due to end-of-input
[프론트쪽 에러 메시지]
Access to XMLHttpRequest at 'http://ec2-3-34-193-123.ap-northeast-2.compute.amazonaws.com:8080/api/login' from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: It does not have HTTP ok status.
시도해 본 것들💦
서버에서는 이런 메시지가 떴고 프론트쪽에서는 CORS에러가 떴다. 구글링 했을 때 어떤 에러인지 잘 파악하지 못해서 chatGPT한테 물어봤다.
java.lang.RuntimeException: No content to map due to end-of-input
이 에러는 요청의 본문이 비어있거나 유효한 JSON형식이 아니라는 것을 나타낸다고 한다.
해당 예외가 발생하는 원인은 요청 본문에 데이터가 없거나, 데이터가 전송되는 도중에 문제가 발생하여 비정상적으로 종료되었을 가능성이 있다고 한다.
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("https://fe-anabadastore.vercel.app")
.allowedMethods("*")
.allowedHeaders("*")
.allowCredentials(true).maxAge(3600);
}
}
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true)
public class WebSecurityConfig {
private final JwtUtil jwtUtil;
private final UserDetailsServiceImpl userDetailsService;
private final AuthenticationConfiguration authenticationConfiguration;
public WebSecurityConfig(JwtUtil jwtUtil, UserDetailsServiceImpl userDetailsService, AuthenticationConfiguration authenticationConfiguration) {
this.jwtUtil = jwtUtil;
this.userDetailsService = userDetailsService;
this.authenticationConfiguration = authenticationConfiguration;
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
return configuration.getAuthenticationManager();
}
@Bean
public JwtAuthenticationFilter jwtAuthenticationFilter() throws Exception {
JwtAuthenticationFilter filter = new JwtAuthenticationFilter(jwtUtil);
filter.setAuthenticationManager(authenticationManager(authenticationConfiguration));
return filter;
}
@Bean
public JwtAuthorizationFilter jwtAuthorizationFilter() {
return new JwtAuthorizationFilter(jwtUtil, userDetailsService);
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// CSRF 설정
http.csrf((csrf) -> csrf.disable());
http.cors((cors) -> cors.disable());
http.sessionManagement((sessionManagement) ->
sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
);
http.authorizeHttpRequests((authorizeHttpRequests) ->
authorizeHttpRequests
.requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()
// .requestMatchers("/api/register/**", "/api/login").permitAll()
.anyRequest().permitAll()
);
// 필터 관리
http.addFilterBefore(jwtAuthorizationFilter(), JwtAuthenticationFilter.class);
http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
서버 측에서 CORS 구성을 하라고 했는데 다 허용을 해놨었다.
허용할 수 있는 건 다 허용을 했다고 생각했고 뭐가 문젠지 찾지 못해서 다른 분에게 물어보았다.
아마 필터들간에 문제일 수도 있다고 하였다. 그래서 혹시 해결해보려고 이것저것 추가했던 것들이 문제가 되었던 것은 아닐까 해서 처음 구현했던 코드에서 요청만 모두 허용해주는 방식으로 코드를 수정해보았다.
🌐 악명 높은 CORS 개념 & 해결법 - 정리 끝판왕 👏
악명 높은 CORS 에러 메세지 웹 개발을 하다보면 반드시 마주치는 멍멍 같은 에러가 바로 CORS 이다. 웹 개발의 신입 신고식이라고 할 정도로, CORS는 누구나 한 번 정도는 겪게 된다고 해도 과언이
inpa.tistory.com
그런데!!!!더 이상 프론트에서 CORS에러는 나지 않는데 쿠키값이 전달되지 않는 것이었다!!!!
백엔드쪽에서는 "로그인 성공 및 JWT 생성"이라고 로그도 잘 찍히고, 프론트쪽에서는콘솔창에 "로그인에 성공하였습니다."라는 메시지는 뜨지만 검사 -> Application에 쿠키가 나오지 않는것이었다....!!
구글링을 해봤더니 Same-Origin Policy문제일수도 있다고 한다.
SOP(Same-origin policy) 란 무엇일까? | 개발자 이동욱
브라우저 보안 정책에 SOP(same-origin-policy) 있다는 것을 알게 되었고, 어떤건지 궁금해서 찾아보았다. 마침 MDN 문서에 잘 나와있어서 이를 참조할 수 있었다. Same-Origin-Policy(동일 출처 정책) SOP는 한
dongwooklee96.github.io
https://velog.io/@yejinh/CORS-4tk536f0db
Same-Origin Policy 동일 출처 정책과 CORS 에러
동일 출처 정책 Same-Origin Policy 동일 출처 정책(same-origin policy)은 어떤 출처에서 불러온 문서나 스크립트가 다른 출처에서 가져온 리소스와 상호작용하는 것을 제한하는 중요한 보안 방식입니다.
velog.io
CORS에러를 해결했더니,,,또 다른 문제가....ㅜㅜ
회원가입 로그인이 제일 어려운 것 같다ㅠㅠㅠㅠ뭐가 문제지 파악하기도 어려운 것 같다ㅠㅠ
개선할 점💪🏻
결국 끝까지 정확히 원인이 무엇인지 파악하지도 못하고 에러를 해결하지도 못했다. 내일은 프로젝트 마지막 날이다. 로그인 기능을 위해 토큰값을 헤더나 바디에 보내는 방법도 고민해봐야겠다.
느낀 점🤔
그 말로만 듣던 CORS에러를 겪게 되다니....
프론트분께서 CORS에러는 무조건 백엔드 잘못이라고 CORS에러가 뜨면 백엔드가 해결해줄때까지 가만히 기다리면 된다고 배웠다고 하셨다ㅠㅠ
기능 구현을 완성하고 Fostman으로 잘 실행되는지 확인도 했는데도 에러가 나서 당황했다. 백엔드쪽에서는 CORS에러라고도 뜨지 않아서 무슨 에러인지 파악하는 것부터 어려웠다. CORS에러에 대해서도 공부를 많이 해봐야겠다.