TIL-230814(항해99 실전 프로젝트-행동대장(10))
📝오늘 공부한 것
- 실전 프로젝트 - '행동대장' CORS 에러 해결
⛔문제점
[에러메시지]
ERROR 63970 --- [nio-8080-exec-2] 로그인 및 JWT 생성 : No content to map due to end-of-input at [Source: (org.apache.catalina.connector.CoyoteInputStream); line: 1, column: 0] 2023-08-14T02:29:53.286Z ERROR 63970 --- [nio-8080-exec-2] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception java.lang.RuntimeException: No content to map due to end-of-input at [Source: (org.apache.catalina.connector.CoyoteInputStream); line: 1, column: 0]
-> 요청의 내용이 없는 상태에서 매핑을 시도하여 발생한 문제라고 한다. 이는 클라이언트에서 서버로 보내는 요청이 비어있거나 유효한 JSON 형식이 아닌 경우에 발생할 수 있다고 한다.
시도해 본 것들💦
✔ WebMvcConfig
@Configuration
@RequiredArgsConstructor
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowCredentials(true)
.allowedHeaders("*")
.exposedHeaders("*")
.allowedOrigins("http://localhost:3000")
.allowedMethods("OPTIONS", "GET", "POST", "PUT", "DELETE")
.maxAge(3000);
}
}
.allowedMethods("OPTIONS", "GET", "POST", "PUT", "DELETE") 에서
.allowedMethods("*") 로 바꿔보았고,
✔ WebSecurityConfig
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// CSRF 설정
http.csrf((csrf) -> csrf.disable());
http.sessionManagement((sessionManagement) ->
sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
);
http.authorizeHttpRequests((authorizeHttpRequests) ->
authorizeHttpRequests
.requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()
.requestMatchers("/").permitAll()
.requestMatchers("/api/signup/**").permitAll()
.requestMatchers("/api/login/**").permitAll()
.requestMatchers(HttpMethod.GET, "/api/**").permitAll()
.anyRequest().authenticated()
);
// 필터 관리
http.addFilterBefore(jwtAuthorizationFilter(), JwtAuthenticationFilter.class);
http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
.requestMatchers("/api/login/**").permitAll() 에서
.requestMatchers("/api/login).permitAll()로 바꿔보았다.
이미 설정처음부터 요청에 대한 것들을 거의 다 허용해놔서 더 이상 뭘 열어야하는지 몰랐다. 그래서 혹시나 하는 마음에 이것저것 바꿔보았다. 그런데도 프론트에서 계속 같은 CORS에러가 난다고 해서 아예 filter나 config 코드를 갈아엎어야 하나 생각하며 몇시간동안 구글링을 하고 있었다.
그런데 다행히도 프론트쪽에서 설정을 하지 않아서 에러가 났었다고 한다.....
다시 이것저것 수정했던 코드들을 롤백시켰다....!
💯해결
✔ WebMvcConfig
package com.sparta.actionboss.global.config;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
@RequiredArgsConstructor
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowCredentials(true)
.allowedHeaders("*")
.exposedHeaders("*")
.allowedOrigins("http://localhost:3000")
.allowedMethods("OPTIONS", "GET", "POST", "PUT", "DELETE")
.maxAge(3000);
}
}
✔ WebSecurityConfig
package com.sparta.actionboss.global.config;
import com.sparta.actionboss.global.filter.JwtAuthenticationFilter;
import com.sparta.actionboss.global.filter.JwtAuthorizationFilter;
import com.sparta.actionboss.global.security.JwtUtil;
import com.sparta.actionboss.global.security.UserDetailsServiceImpl;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.autoconfigure.security.servlet.PathRequest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
@EnableGlobalMethodSecurity(securedEnabled = true)
public class WebSecurityConfig {
private final JwtUtil jwtUtil;
private final UserDetailsServiceImpl userDetailsService;
private final AuthenticationConfiguration authenticationConfiguration;
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@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.sessionManagement((sessionManagement) ->
sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
);
http.authorizeHttpRequests((authorizeHttpRequests) ->
authorizeHttpRequests
.requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()
.requestMatchers("/").permitAll()
.requestMatchers("/api/auth/**").permitAll()
.requestMatchers(HttpMethod.GET, "/api/**").permitAll()
.anyRequest().authenticated()
);
// 필터 관리
http.addFilterBefore(jwtAuthorizationFilter(), JwtAuthenticationFilter.class);
http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
😥어려웠던 것들
CORS 에러가 나면 항상 프론트코드는 완벽해!내가 뭘 허용해주지 않아서 에러가 나오는 걸꺼야!하는 생각을 하고 코드를 수정한다. 그런데 첫 프로젝트를 진행하면서 지금까지 생겼던 CORS 에러들의 대부분은 프론트쪽에서 뭘 설정하지 않아서 났었던 에러들이었다. 이럴때마다 나는 이 코드들에서 더 이상 뭘 추가해야할지도 모르겠고, 그렇다고 프론트분들에게 코드를 잘 작성한게 맞을까요..?하고 물어볼 수도 없다ㅠㅠ
나는 이제 갓코딩을 시작한 단계라 항상 내 코드들에 의심이 많다. 그래서 에러가 나면 내가 잘못했겠지 하고 생각을 하는데 그래서 CORS에러가 났을때가 가장 해결하기 어려운 것 같다. 특히 이러한 에러들은 fostman으로 내가 직접 확인할 수 있는 것들이 아니라서 수정하고, 배포하고, 프론트쪽에 요청하고, 로그확인하고 하는 단계들을 거쳐야해서 더 복잡한 것 같다.
느낀 점🤔
국비지원으로 프론트엔드수업과 유튜브로 생활코딩의 react 강의를 들었었다. 그래서 CORS 에러가 날때마다 답답한 마음에 '차라리 리액트공부를 해볼까....?'하고 생각하지만, 역시 스프링하나도 잘 못하면서! 스프링 마스터하고 다른 거 시작하자!!는 생각에 바로 포기했다ㅎㅎㅎSpring을 잘하는 그날까지...아자아자 화이팅...!