반응형

1. 오류 상황

 restdocs를 사용하여 JUnit 테스트 코드에서 REST API에 대한 명세 파일을 생성 도중 특정 케이스에서만 Form parameters with the following names were not documented: [_csrf] 에러가 발생하였다.

_csrf 에 대한 SnippetException

 


2. 오류내용

 _csrf 라는 이름을 가진 매개변수가 formParameters에 정의되지 않았고, 최종적으로 문서화 되지않았다는 오류였다. 즉, 요청 파라미터에는 _csrf 값이 있는데, formParameters에는 정의하지 않아 발생했다.

테스트의 HttpServletRequest 정보

 


3. 코드

@Test
@TestMemberAuth
@DisplayName("운동 상태정보 수정")
void updateExecStatusToW() throws Exception{
    mockMvc.perform(
            patch("/api/member/exec-status")
                    .header("Authorization", "Bearer JWT_ACCESS_TOKEN")
                    .param("status", ExecStatus.W.name())
                    .with(csrf()))
            .andExpect(status().isNoContent())
            .andDo(document(
                    "updateExecStatus"
                    , requestHeaders(
                            headerWithName("Authorization").description("JWT_ACCESS_TOKEN"))
                    , formParameters(
                            parameterWithName("status").description("헬스 상태 (W : 준비중, H : 헬스중, I : 부상으로 쉬는중"))
                    )
            );
}

 

* with(csrf()) 구문이 포함되어 있는 이유

 with(csrf()) 는 MockHttpServletRequest에 CSRF 토큰 값을 추가하는 메서드이다. SpringSecurity 옵션을 통해 csrf 토큰 사용을 disable 처리했는데, mockMvc를 사용할 경우 기본적으로 csrf 토큰이 사용되도록 동작하기 때문이다. 이를 해결하기 위해 해당 구문을 사용하고 있었다.


5. 원인 분석

5.1. [ formParameters() + _csrf ] 케이스 예외 발생

 예외가 발생하는 케이스는 Paramters에 _csrf 값이 들어가고, form 으로 전송되는 Parameters 에 대한 문서화 메서드인 formParameters() 를 사용하는 케이스였다. Paramters에 _csrf가 들어가고 있는데 formParameters에는 이에 대한 명세를 하지 않았기 때문에 발생했다.

 

 반대로 Json 형태의 요청 값을 문서화 시킬 경우 requestFields() 메서드를 사용하는데 이는 Parameters 가 아닌 Body에 있는 값을 기준으로 매핑하기 때문에 _csrf 관련 에러가 발생하지 않았던 것이다.

application/form 타입의 요청에 대한 MockHttpServletRequest 정보
application.json 타입의 요청에 대한 MockHttpServletRequest 정보

 


6. 해결

6.1. csrf 토큰을 헤더로!

 알려진 해결방안은 테스트 전용 SecurityConfig 설정 파일을 만들어서 csrf에 대해 disable 처리하고, MockMvc 테스트 마다 생성한 설정파일을 Configuration 파일로 로드하는 코드를 추가하면 된다고 하는데, _csrf 값을 요청 파라미터가아닌 헤더로 받으면 되지 않을까라는 생각이 들었다.

 

 클라이언트에서 CSRF 토큰 값을 서버로 전송하는 방법은 요청 헤더에 토큰 값을 넣거나 요청 파라미터에 토큰 값을 넣는 것이며, 요청 헤더의 경우 X-CSRF-TOKEN, 요청 파라미터의 경우 _csrf 라는 키 값에 넣으면 된다.

 

 with(csrf())를 사용할 경우 요청 파라미터에 csrf 토큰 값이 포함되어 들어가고 있는데, 이를 요청 헤더로 이동시키면 Paramters의 _csrf 값을 제거될 것이고, 에러도 해결될 것 같았다.

 

 곧장 csrf() 의 내부코드를 보니 아래와 같이 asHeader 값이 true일 경우 토큰 값을 헤더에, 그 외에는 Paramter에 설정하는 것을 확인할 수 있었다. 기본값은 false 였기에 Paramter로 토큰이 전송되고 있었다.

csrf()에 메서드에 대한 csrf 설정 부분

 

asHeader만 true로 설정해주는 메서드인 asHeader()도 곧바로 찾을 수 있었다. 바로 적용해보았다.

asHeader를 true로 설정하는 asHeader() 메서드

 

 

6.2. 적용 및 테스트

 기존 csrf() 메서드에 체인 메서드 형태로 asHeader() 메서드만 추가해줬다. 테스트 결과 예외는 해결되었으며, 요청 값을 확인해보면 csrf 토큰 값이 Headers로 요청되는 것을 확인할 수 있다.

@Test
@TestMemberAuth
@DisplayName("운동 상태정보 수정")
void updateExecStatusToW() throws Exception{
    mockMvc.perform(
            patch("/api/member/exec-status")
                    .header("Authorization", "Bearer JWT_ACCESS_TOKEN")
                    .param("status", ExecStatus.W.name())
                    .with(csrf().asHeader())) // asHeader() 추가
            .andExpect(status().isNoContent())
            .andDo(document(
                    "updateExecStatus"
                    , requestHeaders(
                            headerWithName("Authorization").description("JWT_ACCESS_TOKEN"))
                    , formParameters(
                            parameterWithName("status").description("헬스 상태 (W : 준비중, H : 헬스중, I : 부상으로 쉬는중"))
                    )
            );
}

 

Parameters 에서 Headers로 옮겨진 csrf 토큰

 

반응형
반응형

1. 상황

Controller 단위 테스트 도중 andExpect 메서드를 사용하여 검증하는 과정에서 예외가 발생하였다.

 

2. 발생 예외

com.fasterxml.jackson.databind.exc.InvalidDefinitionException Java 8 date/time type `java.time.LocalDateTime` not supported by default: add Module "com.fasterxml.jackson.datatype:jackson-datatype-jsr310" to enable handling ...

발생한 오류 첨부

 

3. 오류 내용 분석

 예외 메시지를 보니 DTO 객체를 직렬화하는 과정에서 필드에 존재하는 `java.time.LocalDateTime` 의 데이터 타입을 지원되지 않아 처리할 수 없다는 오류였다.

 

3.1. 갑자기 웬 직렬화?

 @RestController 또는 @ResponseBody 어노테이션을 사용할 경우 리턴 값이 Json 스트링 형식으로 변환되어 응답되는데, 이때 내부 MessageConverter에 정의된 ObjectMapper에 의해 데이터가 직렬화된다. (직렬화 : Object to Json)

 반대로 @RequestBody 어노테이션을 사용할 경우 요청 Json 데이터를 Object로 변환하는데, 이때는 역직렬화 기술이 사용된다.

 

3.2. 잘못잡은 포인트

 내부 MessageConverter에서 직렬화 도중 발생한 예외인줄 알았으나, 테스트 코드의 objectMapper를 사용하여 응답 값을 검증하는 부분에서 발생했던 것이었다. ^^;;

 

private final ObjectMapper objectMapper = new ObjectMapper();
...

@Test
@TestMemberAuth
@DisplayName("채팅방 ID에 대한 채팅내역 조회")
void getChatHistory() throws Exception {
    mockMvc.perform(
                    get("/api/chatting/history/{roomId}",CHATTING_ROOM_ID_FOR_LOGIN_MEMBER_AND_VALID_MEMBER))
            .andExpect(status().isOk())
            .andExpect(content().string(objectMapper.writeValueAsString(CHATTING_HISTORY_DTO))); // 예외발생 부분
}

 

4. 해결

 테스트 코드에서 objectMapper를 생성한 이후 아래 설정을 추가하였다.

objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);

 위 설정은 ObjectMapper에 JavaTimeMoule을 등록한 후, 날짜와 시간을 타임스탬프로 직렬화하지 않도록 한다.

 

 참고로 예외 메시지에서 jackson-datatype-jsr310 의존을 추가하라고 하는데, spring-boot-starter-web 에 의존하는 경우 종속관계인  jackson-datatype-jsr310 도 자동 의존하는 것을 확인할 수 있었다.

 

 

 

 

 

 

 

 

 

 

 

반응형
반응형

1. 개요

 - 회사 로컬 PC에 STS 최신버전 (4.12.1) 설치 후 Lombok 연동 시 에러 발생


2. 환경

 - STS 4.12.1

 - Lombok 1.18.20


3. 에러 상황

 - 개인 PC에 설치한 STS에서는 Lombok이 정상기동되었음.

 - git을 통해 clone하여 소스 로드함.


4. 조치사항

 - 구글링을 통해 다음과 같은 조치를 하였으나 안먹힘

 1) SpringToolSuite4.ini의 마지막줄에 -javaagent:lombok.jar 코드 추가 (x)

 2) SpringToolSuite4.ini의 마지막줄에 -vmargs -javaagent:lombok.jar 코드 추가 (x)

 3) SpringToolSuite4.ini의 -vmargs 구문의 제일 위에 -javaagent:lombok.jar 코드 추가 (x)

 4) SpringToolSuite4.ini의 마지막줄에 --illegal-access=warn --add-opens java.base/java.lang=ALL-UNNAMED 추가 (x)
 5) PC 재부팅, 프로젝트 clean (x)


5. JDK 16의 보안정책 강화

 - JDK 16부터 보안정책이 강화되어 최신의 STS에 Lombok 라이브러리 연동 시 에러가 발생한다고 함.

https://github.com/projectlombok/lombok/issues/2810

 

[BUG] Unhandled event loop exception in Eclipse · Issue #2810 · projectlombok/lombok

After updating Eclipse to use Java 16, building projects gives an error. Install Lombok 1.18.20 in Eclipse, either through the update site or the jar (I tried both). If you used the update site, yo...

github.com


6. 해결

 - 최신버전 멈춰! STS 4.9.0 버전으로 재설치

 - 스무스하게 동작. 역시 최신버전보단 검증된 버전으로..

 

 

반응형
반응형

1. 개요

 - Git에서 관리하던 Gradle 프로젝트를 Clone한 후 빌드 시 org.gradle.wrapper.GradleWrapperMain가 발생하였다.


2. 원인

 - org.gradle.wrapper.GradleWrapperMain 패키지 클래스는 gradle-wrapper.jar 파일 내의 패키지이다. 이 파일이 프로젝트 내에 없었고, 그로 인해 위 클래스를 찾지 못해 발생한 에러였다. 어디선가 누락이 된것이다. 

 - 기본적으로 gradle-wrapper.jar는 '프로젝트 루트 디렉토리/gradle/wrapper' 경로에 있는데, 에러가 발생했던 프로젝트에는 이 파일이 누락되어 있었다.

gradle-wrapper.jar


3. 누락은 어디서?

 - 필자의 경우 gitignore에 *.jar 가 있었고, 그로인해 누락되었다. gitignore의 default 코드에 작성되어있어 신경을 쓰지 못한 것이 화근이었다.

gitignore


4. 해결

 - gradle.wrapper.jar파일을 gradle/wrapper 경로에 넣어주면 된다. 필자의 경우 다른 프로젝트에 있는 jar파일을 가져왔지만, 아래 링크를 통해 다운받아도 무관하다.

https://mvnrepository.com/artifact/org.gradle/gradle-wrapper/5.2.1

 

 

 

반응형
반응형

1. 개요

 여름이 내 옆으로 바짝 다가온 그날. 때아닌 열대야가 찾아와 방안에 있는 내 몸을 달구고있었다. 얼른 에어컨을 켜 25도를 맞추고 시원한 마음으로 책상에 앉아 msi 노트북의 입을 열어 젖힌다.

 입 안에 반짝이는 이클립스가 보인다. 이녀석... 바로 이클립스를 클릭하여 개인 프로젝트를 재개한다.

 그런데...나는 아무것도 만진게 없는데. web.xml에서 빨간 X 표시가 뜨고있었다. 

에러


2. 해결방안

 간단했다. 구글링한 결과 아래 빨간색 java 스펠링을 대문자로 바꿔주면 된다.

에러

 

끄읕

반응형
반응형

1. 개요

 어느 날, 갑자기 SQLTimeoutException 에러가 특정 요청에서 간헐적으로 발생했다. 정확히는 월의 첫날 0시 00분부터 발생했다.

 

2. 오류 내용

### The error occurred while setting parametersSQLTimeoutException: ORA-01013: 사용자가 현재 작업의 취소를 요청했습니다.

 

3. 분석

 SQLTimeoutException 에러는 말 그대로 SQL 쿼리를 실행하기 위해 DB로 요청 후 wait Time을 초과하여 발생한 에러이다. 오라클 서버가 비지한 상태(ex - 배치)에서 DB 처리 요청이 들어간 것이다.

 하지만 위 시스템 내에 DB 요청 후 기다리는 wait Time이 따로 설정되어 있지 않았고, 요청을 보냈을 당시 DB에서 처리가 되지 않아 sqlTimeoutException을 떨군 것이었다.

 

mybatis의 설정파일에 waitTime의 기능을 하는 defaultStatementTimeout 설정을 추가해주었으며 코드는 다음과 같다.

1
2
3
    <settings>
        <setting name="defaultStatementTimeout" value="30"/> 
    </settings>
cs

 

4. 특이사항

 개발 서버에 위 현상이 발생하였다. 특정 쿼리에서만 발생하는 것 같아 mybatis 설정파일에서 defaultStatementTimeout을 세팅하지 않고 매퍼 파일 안에 다음과 같이 넣어주었다. 이렇게 하면 해당 쿼리에 대해서만 timeout 값이 적용된다. 방법은 아래와 같다.

1
<select id="id" parameterType="java.util.HashMap" resultType="test.dto" timeout = "60">
cs

하지만 작업을 진행하다보니 동일 문제가 발생하는 쿼리가 더 확인됐고, mybatis 설정파일에 defaultStatementTimeout을 설정하였다. 하지만 계속 sqlTimeoutException이 발생하였다.

db 관련 설정을 모두 확인해도 이상한점은 없었다.

이전처럼 매퍼 파일 안에 timeout을 설정했다.

된다!

 

이에 대한 원인 파악을 위해 구글링을 해 보았으나 찾지못했다.

혹시나해서 매퍼에 입력한 timeout값을 모두 지워보았다.

된다!!

 

결론.

timeout 값을 매퍼 파일 안에 각각 입력해줄경우 defaultStatementTimeout 값이 먹히지 않는다.

만약 defaultStatementTimeout 이 일하지 않을 경우 매퍼에 설정한 timeout값을 모두 지우자.

 

찾아보니 timeout값은 덮어씌워진다고한다. 그리고 간헐적으로 timeout값이 적용되는 것 같다.. 정확한 원인을 다시 찾아보도록 하겠다.

 

 >> 일반적인 mybatis 구조에서는 defaultStatementTimeout 이 적용되나, 필자가 맡은 프로젝트는 사내 플랫폼 기반 안에 mybatis가 접목된 구조라 해당 설정이 적용이 되지 않는다고 한다. 아마 플랫폼 코어 내에 특정 부분에서 mybatis에 대한 설정을 덮어씌우는 구간이 있는 것으로 추정된다.

 

결론

 모두 다 적용시키려면 defaultStatementTimeout를, 특정 쿼리 ID에만 적용시키려면 timeout을!

반응형
반응형

STS의 Spring Web 프로젝트를 생성했더니, tomcat 연동이 되지 않는 오류 발생.

 

에러 내용은

There are no resource that can be added or removed from the server.

 

두둥.

 

구글링 결과,

 

  1) 프로젝트 우클릭 > Properties > Project Facets > Dynamic Web Module, Java, Javascript를 체크

    2) Further configuration available ... 클릭!

 

    3) 아래와 같이 설정 및 web.xml 체크

 

하면 된다고 했으나, 그래도 되질 않아서 삽질에 삽질 중 해결.

 

   4) Cloud Foundry Standalone Application 체크 해제!

 

STS 버전에 따라 저게 체크된 채로 프로젝트가 생성되더군요..

 

해제하니까 add and Remove 성공...

 

즐코!

반응형

+ Recent posts