IT

spring security 403 에러 해결!!!

호야투은하 2024. 6. 5. 14:16
반응형

현재 나는 게시판을 만들고 있다. 

로그인한 관리자는 글 작성, 조회, 수정, 삭제 모두 가능하게 만들고,

로그인하지 않은 익명의 일반 사용자는 글 작성과 조회만 가능하도록 하는 기능을 만들고 있다.

 

그렇게 이것저것 구현을 했는데, 어느순간부터 계속 403 FORBIDDEN 에러가 나는 것이 아닌가!!!

Spring Security 설정 부분에 나는 403 FORBIDDEN 에러를 특정 상황에 발생시키도록 설정해두었다.

아래와 같은 형식으로 말이다.

package com.hoya.mannaback.config;

import java.io.IOException;

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.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import com.hoya.mannaback.filter.JwtAuthenticationFilter;

import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {

        private final JwtAuthenticationFilter jwtAuthenticationFilter;

        @Bean
        public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {

                // csrf disable
                http
                                .csrf((auth) -> auth.disable());

                // http basic 인증 방식 disable
                http
                                .httpBasic((auth) -> auth.disable());

                // 경로별 인가 작업
                http
                                .authorizeHttpRequests(auth -> {
                                        try {
                                                auth.requestMatchers(HttpMethod.GET, "/", "/api/v1/board/list",
                                                                "/api/v1/board/detail/**", "/file/**",
                                                                "/api/v1/user/**")
                                                                .permitAll()
                                                                .requestMatchers(HttpMethod.POST, "/api/v1/board/post",
                                                                                "/api/v1/auth/sign-up",
                                                                                "/api/v1/auth/sign-in",

                                                                                "/file/upload")

                                                                .permitAll()
                                                                // admin 권한이 있는 사용자만 허용되는 요청
                                                                .requestMatchers(HttpMethod.PATCH,
                                                                                "/api/v1/board/update/**")
                                                                .hasRole("ADMIN")
                                                                .requestMatchers(HttpMethod.DELETE,
                                                                                "/api/v1/board/detail/**")
                                                                .hasRole("ADMIN")
                                                                // 나머지 요청은 인증을 필요로 함
                                                                .anyRequest().authenticated().and()
                                                                .exceptionHandling()
                                                                .authenticationEntryPoint(
                                                                                new FailedAuthenticationEntryPoint());

                                        } catch (Exception e) {
                                                e.printStackTrace();
                                        }
                                });

                // 세션 설정
                http
                                .sessionManagement((session) -> session
                                                .sessionCreationPolicy(SessionCreationPolicy.STATELESS));

                http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);

                return http.build();
        }
}

class FailedAuthenticationEntryPoint implements AuthenticationEntryPoint {

        @Override
        public void commence(HttpServletRequest request, HttpServletResponse response,
                        AuthenticationException authException) throws IOException, ServletException {

                response.setContentType("application/json");
                response.setStatus(HttpServletResponse.SC_FORBIDDEN);
                response.getWriter().write("{ \"code\": \"NP\", \"message\":\"Do not have a permission.\" }");

        }

}

 

즉, 계속해서 인증에 실패서  FailedAuthenticationEntryPoint() 함수를 실행시켜 에러를 띄운다는 것이다!

계속 정체기에 있다가..... chat gpt 와 씨름후에 이유를 알아냈다.

 

# 원인 : JWT 토큰이 요청의 'Authorization' 헤더에 제대로 포함되지 않았기 때문에!!!

그렇다... 이것이 문제였다.

 

postman 에서 Authorization 에 로그인 결과로 얻은 토큰을 넣어주고, Body에 요청문을 써서 api 를 요청하면, 분명히 제대로 동작했다.

 

하지만 프론트에서 돌리면 안된다는 건, 위 원인이 문제였다.

 

그렇게 아래와 같이 코드를 수정했다.

 

 

 

localStorage 에서 토큰을 꺼내온 후, 해당 토큰이 있는 경우에만 Authorization 헤더에 토큰을 포함시킨다. 

그리고 axios 요청시에 마지막에 config 을 넣어주면 403 에러 발생 없이 정상적으로 잘 동작한다!!!

 

오예에에에! 신난다. 며칠동안 정체기였는데 ( 사실 요근래 시간투자를 잘 못했다 ㅠㅠ ) 이제 다음 스텝으로 넘어가보자!


이 외에도 Spring Security 를 도입후 react 와 연동하는 것과 관련하여 내가 헤매었던 부분을 잠깐 정리를 하고 가자면..!

 

1. 토큰 만료 시간을 처음에 1시간으로 해놓았던걸 잊고 살았다가 그것 때문에 오류 발생 
=> 토큰을 갱신하는 방법도 있겠지만, 우선은 토큰 만료 시간을 3년으로 늘림.



2. 로그인 할 때에도 아래 코드와 같이 토큰을 따로 localStorage 에 저장해두어야 나중에 꺼내쓸 수 있다.

 

우선 생각나는건 여기까지! (사실 굉장히 많음..)

반응형