환경 세팅
- Spring Boot + Spring Security
- Html, CSS, JS
해결할 문제 🚩
- CORS (Cross Origin Resource Sharing) 문제
- 응답 헤더 (ex. Authorization 헤더) 내용이 누락되는 현상
CORS 문제에 대한 자세한 설명은 다음 글을 참고하길 바랍니다. CORS 문제 잘 정리 된 글!
문제를 인지했으면 해결책은 서버와 클라이언트가 CORS 에 대한 예외를 허용하도록 처리하면 됩니다!
몇 시간 동안 막혔던 부분은 CORS 문제는 해결했지만 응답 헤더에 Authorization 을 포함해서 전달해야하는데 클라이언트에 전달이 안 된다는 문제였습니다.
이는 cross-origin request 에게 보안상 response header 를 전부 노출시키진 않고 CORS-safelisted response headers 만 노출시키는 보안이 이유였습니다.
이를 해결하기 위해 Access-Control-Expose-Headers 를 추가 설정해줘야 했습니다! 여러분의 시간을 아껴드리기 위해 코드와 주석으로 설명을 대체하겠습니다 💕
@Configuration
public class CorsConfig {
// CORS (Cross Origin Resource Sharing) 은 같은 도메인 (스키마://호스트:포트) 가 같아야 한다는 SOP(Same Origin Policy) 에 의해 실행
// 다른 도메인에서도 허락하도록 아래 설정 추가
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOriginPattern("*"); // 허용할 URL
config.addAllowedHeader("*"); // 허용할 Header
config.addAllowedMethod("*"); // 허용할 Http Method
// ⭐CORS 는 해결했지만 프론트에 응답 헤더에 추가한 Authorization 이 전달되지 않는 문제 해결
config.setExposedHeaders(Arrays.asList("Authorization", "Authorization-refresh"));
source.registerCorsConfiguration("/**", config); // 모든 Url에 대해 설정한 CorsConfiguration 등록
return new CorsFilter(source);
}
}
@Slf4j
@RequiredArgsConstructor
@EnableWebSecurity
@Configuration
public class SecurityConfig {
private final CorsConfig corsConfig;
private final AuthenticationConfiguration authenticationConfiguration;
@Bean
public BCryptPasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationManager authenticationManager() throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception{
http.csrf().disable();
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) // disable the session
.and()
.formLogin().disable()
.httpBasic().disable()
.apply(new MyCustomDsl())
.and()
// ~ 권한 처리 ~
.authorizeHttpRequests()
.anyRequest().permitAll();
return http.build();
}
// 커스텀 필터 추가를 여기서 처리하기
public class MyCustomDsl extends AbstractHttpConfigurer<MyCustomDsl, HttpSecurity> {
@Override
public void configure(HttpSecurity http) throws Exception {
AuthenticationManager authenticationManager = http.getSharedObject(AuthenticationManager.class);
http
// cors 오류를 해결하기 위해 Controller 에 @CrossOrigin 을 붙여주는 방법도 있지만
// 이 방식은 필터 추가와 다르게 인증이 필요 없는 url 만 처리해줌
.addFilter(corsConfig.corsFilter()) // cors 에 대해 허락하는 필터
.addFilter(new JwtAuthorizationFilter(authenticationManager));
}
}
}
/* ajax 방식 */
$.ajax({
url: "요청 URL",
type: "요청 METHOD",
headers: { 'Authorization': accessToken },
xhrFields: { // CORS 문제 우회해서 헤더 넘겨주기
withCredentials: true
},
success: function (data, textStatus, request) {
},
error: function (jqXHR) {
}
})
/* axios 방식 */
axios.get("https://example.com/items", {
withCredentials: false, // default
})
/* fetch API 방식 */
fetch("https://example.com:1234/users", {
credentials: "include",
})
withCredentials: true
옵션은 서로다른 도메인 에 요청을 보낼 때 요청에 credential
정보를 담아서 보낼 지 결정합니다. 이를 true 로 지정해야 Front 의 Authorization 헤더가 정상적으로 Back에 전달이 되겠죠!?