@WebMvcTest에서 Spring Security 적용, 403에러 -csrf

dalBeen·2023년 10월 31일
0

에러처리

목록 보기
1/1

스프링 시큐리티 적용한 Rest API Controller 테스트하다가 계속 403 에러가 나왔다. chatgpt에도 물어보고 에러를 검색해봐도 안나와서 스프링시큐리티 403에러 치니까 나오더라...

일단 나의 코드를 설명한다면
MemberController에서 로그인을 하면 토큰을 발행한다. 해당 토큰은 클라이언트 단에 보낸다.

이후 OwnerController에서 새로운 상점을 등록할때 로그인이 되어있으면서 권한이 "OWNER"인경우 상점 등록이 가능하다.

    @Test
    @WithMockUser(username = "test123",roles = {"OWNER"})
    void registerSuccessTest() throws Exception{
        //given
        StoreInput newStore=StoreInput.builder()
                .zipcode("121345")
                .street("남산동")
                .city("서울시")
                .ownerId("test123")
                .storeName("맛나 분식")
                .description("떡복이,김밥, 돈까스 전문점입니다.")
                .build();


        //then
        mockMvc.perform(post("/owner/newstore")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(newStore)))
                .andExpect(status().isOk())
                .andExpect(content().string("맛나 분식"));
    }

맨처음 실행했던 코드다. 이후 에러는

[Username=test123, Password=[PROTECTED], Enabled=true, AccountNonExpired=true, credentialsNonExpired=true, AccountNonLocked=true, Granted Authorities=[ROLE_OWNER]], Credentials=[PROTECTED], Authenticated=true, Details=null, Granted Authorities=[ROLE_OWNER]]]}

분명 권한들이 잘들어가있는데도 불구하고 403 에러가 나왔다.

그이유는 바로

csrf 때문이었다...


        mockMvc.perform(post("/owner/newstore")
                        .with(csrf())
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(newStore)))
                .andExpect(status().isOk())
                .andExpect(content().string("맛나 분식"));

.with(csrf())를 붙였더니 바로 테스트 통과...허무하다 이거때문에 2시간을 헤메었는데...

물론 아직 시큐리티를 정확하게 모르는 나의 잘못이지만...

어째든 그렇다면 csrf가 무엇이길래 안되었던건지 알아보자


csrf

사실 생각해보면 항상

http.csrf().disable()

이거를 항상 붙였던 기억이 있다. 근데 왜 붙였던 걸까??

csrf란

  • 공격자가 악의적인 코드를 심어높은 사이트를 만들어 놓고, 로그인된 사용자가 클릭하게 만들어 사용자 의지와 무관한 요청을 발생시키는 공격

과정

  1. 사용자는 보안이 취약한 서버에 로그인합니다
  2. 서버에 저장된 세션 정보를 사용할 수 있는 sessionId가 사용자의 브라우저 쿠키에 저장된다
  3. 공격자는 사용자가 악성 스크립트 페이지를 누르도록 유도
    • 게시판이 있는 웹사이트에 악성 스크립트를 게시글로 작성하여 사용자들이 게시글을 클릭하도록 유도
    • 메일등 으로 악성 스크립트를 직접전달하거나, 악성 스크립트가 적힌 페이지 링크를 전달
  4. 사용자가 악성스크립트가 작성된 페이지 접근시 웹브라우저에 의해 쿠키에 저장된 sessionId와 함께 서버로 요청
  5. 서버는 쿠키에 담긴 sessionId를 통해 해당 요청이 인증된 사용자로부터 온 것으로 판단하고 처리

이를 해결하기 위해 스프링 시큐리티에서 "csrf 토큰"을 이용해 토큰 값을 비교해서 일치하는 경우에만 메서드를 처리하도록 만든다(Sychronized Token Pattern)


Synchronizer Token Pattern

서버가 뷰를 만들어줄때 사용자별 랜덤값을 만들어 세션에 저장한 다음 이를 뷰페이지에 같이 담아 넘겨주게 된다
-> 클라이언트는 HTTP요청마다 숨겨진 csrf토큰을 같이 넘겨줘야하는 방식
-> 서버는 HTTP Request에 있는 csrf토큰값과 세션에 저장되어있는 토큰값을 비교해 일치하는 경우에만 처리를 진행
-> 위조된 사이트의 경우 csrf토큰 값이 일치하지 않기때문에 공격자가 악의적인 코드를 심어놔도 이를 실행하지 않음

다만 get요청에서는 csrf검증을 수행하지 않음


with(csrf()) 사용하지 않았을때

보면 Parameters에 아무것도 없고 Session Attrs의(세션에 저장된) CSRF_TOKEN과 매치되지 않아 요청을 수행하지 않고 403에러가 뜬다

그러나 with(csrf())을 사용하면 Parameters={csrf=[......]}

이 파라미터로 나타나면서 검증을 수행한다


그런데 왜 우리는 항상 csrf를 disable로 한걸까??

csrf토큰 방식을 보면 각 사용자에 대한 세션을 이용한 방식이다.
때문에 웹브라우저를 통한 접근을 하는 경우, 세션/쿠키를 사용해 상태를 유지하려는 경우 csrf를 사용하는 것이 안전하다

그러나 맨위의 내가 만든 코드는 Rest API로 대개는 무상태성을 유지하면 JWT토큰 방식으로 인증하게되면 요청이 세션에 의존하지 않게 된다

따라서 csrf를 disable하게 한것이다.


사실 지금 만난 에러는 스프링 시큐리티에 대해서 공부중이라 csrf를 잘 모르는 나의 잘못이다. 빠르고 정확하게 스프링 시큐리티 공부해야겠다!

profile
깊게 공부해보자

0개의 댓글