해당 이슈는 Postman 같은 똑똑한 클라이언트를 사용하지 않고 Windows or Mac 등 직접 파일 업로드 요청을 보낼 때 발생할 수 있는 문제다 내 경우엔 Windows 클라이언트가 C++로 짠 코드로 서버에 HTTP 요청을 보내 파일 업로드를 요청했다 파일 업로드 시 파일 이름이 한글인 경우 업로드된 파일 이름이 깨지는 상황이 발생했다 Windows 클라이언트 개발자에게 HTTP 요청을 전달받아 상황을 재현했다 업로드할 파일이 한글 파일명을 가지고 있었기에 아래와 같은 형태로 요청을 만들었다고 한다 ### POST http://localhost:8080/test/file-upload Accept: application/json Content-Type: multipart/form-data; bo..
어느 날 고객사에서 Apache Commons Fileupload 이슈가 인입되었다 apache commons file upload library 1.5 버전 미만에서 RequestParts 개수 제한을 걸어두지 않아 Dos 공격이 가능하다 Apache Tomcat: Important: Apache Tomcat denial of service (CVE-2023-24998) Rapid7's VulnDB is curated repository of vetted computer software exploits and exploitable vulnerabilities. www.rapid7.com 이후로 팀원분이 내부 동작을 파악하고 간단하게 리뷰를 해주셨는데 들을 때는 그렇구나 하고 수동적으로 들어서 그런지 남..
이전 글에서 @RequestBody 바인딩 처리를 알아보았고 이번에는 @ModelAttribute 처리에 대해 알아보자 스프링 버전에 따라 라인 수가 달라질 수 있으니 사진 설명에 클래스#메서드 형태로 남겨놨다 어떤 메서드를 호출하는지에 주의해 흐름을 따라가보자, 테스트할 객체와 컨트롤러는 아래 더보기를 참고하면 된다 더보기 @XmlAccessorType(XmlAccessType.PROPERTY) @XmlRootElement(name = "data") @AllArgsConstructor @NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter @ToString public class TestRequest { @XmlElement(name = "xml_nam..
레거시 시스템을 살펴보면 도메인에도 @Data, @Setter가 달려있는 모습을 볼 수 있다 불변의 중요성이 커지면서 & 무분별한 setter로 인해 변경점 파악이 어려워 @Setter는 안티 패턴과 같이 여겨지고 있다 @Setter와 같은 역할을 하더라도 더 의미 있는 changeXX() 등의 네이밍이 권장되고 있다 그렇다면 레거시 시스템에서는 @Setter를 왜 썼을까?, 개인적인 생각에 다음과 같은 이유지 않았을까 싶다 1. setter와 이름만 다르지 역할은 같은 수 많은 메서드를 만들기 귀찮음 2. @RequestBody, @ModelAttribute 바인딩을 가장 쉽게 할 수 있는 방법이므로 언젠가 코드리뷰에서 DTO에서 매핑이 안 되던 문제가 나왔던 적이 있다 입사한 지 얼마 안 된 신입 시..
이전 글에서 이어지는 내용으로 commitOrRollback의 세부 구현을 살펴보자 EventListener 구현체는 JpaEventListener를 구현하고 여기서는 공통 Transaction 작업만 처리해주면 된다 import java.util.function.Consumer; import org.hibernate.Session; import org.hibernate.Transaction; import org.hibernate.engine.spi.SharedSessionContractImplementor; public interface JpaEventListener { default void commitOrRollback( SharedSessionContractImplementor session, C..
도메인 주도 개발 혹은 MSA로 갈 때 연관 관계를 다루기 위해서는 객체 자체가 아닌 식별자를 저장하곤 한다 예시만 살짝 맛보기로 하기 위해 극단적으로 간단한 엔티티를 만들었다, Panda -> Bear에 대한 간접 참조를 갖는다 @Entity @NoArgsConstructor @Getter public class Panda { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String name; private Long bearId; @Builder public Panda(String name, Long bearId) { this.name = name; this.bearId = bearId; } } @Entity ..
REST API 형태로 프로그램을 작성 중이라면 문서화에 Restdocs 혹은 Swagger2를 이용할 수 있다 개인적인 생각으로는 Swagger-ui가 훨씬 더 사용하기 쉽고 UI, UX가 좋지만 코드에 침투적이라는 이유 하나만으로도 싫다 다만 Restdocs를 사용해 본 경험이 있어 진행 중인 프로젝트에 학습 목적으로 Swagger2를 도입했다 오늘 만난 에러는 Springboot 2.6.x 이상의 버전을 사용한다면 마주칠 수 있는 문제다 난 부트 2.6.3, swagger는 3.0.0을 사용 중이다 plugins { id 'org.springframework.boot' version '2.6.3' } dependencies { implementation 'io.springfox:springfox-b..
진행 중인 프로젝트에서 OAuth2 검증 필터로 OncePerRequestFilter를 상속 받고 빈으로 등록해 사용 중이다 OncePerRequestFilter가 무엇인지는 밸덩을 참고하자 일반 필터와 다르게 이름 그대로 단일 요청에 대해 필터를 단 한번만 타게끔 해주는 녀석이다 OAuth2 검증 필터 흐름은 아래와 같다 1. 사용자의 쿠키에서 ID 토큰을 꺼내와 JWT 파싱 후, issuer를 확인해 구글 토큰인지 확인한다 2-1. 구글 토큰이 아닐 시 필터 체인에 태워 넘긴다 2-2. 구글 토큰일 시 파싱한 정보를 바탕으로 authentication을 만들어 SecurityContextHolder에 넣어준다 3. 만약 토큰의 만료 시간이 지났다면 갱신 url로 forward를 이용해 넘긴다 - 이 ..
개인 프로젝트에서 템플릿 엔진으로 thymeleaf를 선택해 사용 중이다 spring-security와 엮어서 사용할 필요가 있었고 thymeleaf-extras-springsecurity 라이브러리를 추가하면 간단하게 사용 가능하다 다만 최신 릴리즈인 3.0.4.RELEASE는 2018년 10월에 나왔기에 나온지 좀 됐고 그 때문에 보안 취약점들이 몇개 걸린 상태다 2022년 1월에 따끈따끈한 보안 패치 버전이 나왔으나 정식 버전이 아닌 Milestone 버전이다 모험가 정신이 투철한 나는 참을 수 없이 M1 버전을 라이브러리에 넣고 돌렸더니 굉장히 장황한 NoSuchMethodError가 뜨는 것이 아닌가 내 눈을 믿을 수 없어 서버 재시동 후 몇번 더 시도했지만 같은 에러를 뿜었다 마음을 가다듬고 ..
객체지향, 자바 & 스프링을 올바르게 공부하고 있는 건가 싶은 의문이 든다면 아래 동영상을 한번 보자 얼마 전 팀장님과 잠깐 나눴던 대화 중 앉아만 있다고 공부하는 것이 아니라는 말씀을 해주셨다 난 2021.02 기준으로 개발 공부를 시작했고 그때부터 약간은 강박적으로 공부에 매달렸다 이전 회사에서도 그랬고 지금도 퇴근 후 적어도 4시간 이상은 하고 주말에 약속이 없다면 거진 10시간 정도를 하는데 그래서 난 공부를 오래 하고, 정말 열심히 한다는 생각을 했던 것 같다 팀장님의 말씀과 위 동영상을 보고나니 의식적인 연습이 부족했구나 하는 생각이 든다 그렇게 긴 시간을 공부하더라도 무의식적인 공부라면 코드 따라 치기 일 뿐이고 잘 봐줘도 API 사용법 학습일 뿐이다 위 동영상에서 말한 것과 팀장님께서 해주..
자바 툴로 인텔리제이를 사용한다면 개인 프로젝트라도 코드 스타일을 맞추는 걸 추천한다 국내에서는 보통 구글 혹은 네이버의 스타일을 따르는 것 같다 나는 구글 스타일을 선택했고 웬만하면 커스텀 옵션을 주지 않고 사용하고 있다 GitHub - google/styleguide: Style guides for Google-originated open-source projects Style guides for Google-originated open-source projects - GitHub - google/styleguide: Style guides for Google-originated open-source projects github.com 위 깃허브에 접속해 xml 파일을 긁어다 저장하면 된다 Copy ..
Spring + JPA 사용 시 JPA 구현체 중 하나인 Hibernate가 제공하는 꿀 기능이 있다 show_sql, format_sql 두 속성인데 sql을 정렬된 상태로 볼 수 있게 해 준다 여기에 logging 하위 속성이 제공하는 BasicBinder까지 사용하면 sql parameter에 무슨 값이 들어가는지 알 수 있다 단 이전 글에서도 언급했듯 기본 제공하는 것을 그대로 쓴다면 큰 단점이 있다 sql 쿼리가 점점 복잡해질수록 가독성이 좋지 못하고 조건이 늘어날수록 어떤 값이 어떤 변수에 붙는지 알기 힘들다 또한 ordinal로 보여주기 때문에 column=value 형식이 아닌 [0]=value 형식으로 보게 된다 [P6Spy] Log4j2, p6spy 적용해보기 진행하고 있는 프로젝트에서..
OAuth2 로그인을 하더라도 애플리케이션에서 회원 관리를 직접 하고 있다면 CustomOAuth2UserService를 만들어 우리의 DB에 OAuth2 로그인으로 받아온 회원 정보를 넣어줘야 한다 그렇다면 그 설정은 어찌하는가? 흔히 사용하는 SecurityConfig에서 http를 인자로 받는 메서드에서 설정을 잡아준다 수많은 튜토리얼들이 있으니 참고해보시고 최신 버전의 스프링부트 스타터들을 사용한다면 여기까지 설정했을 때 구글 로그인은 customOAuth2UserService를 타지 않는다 왜 내가 만든 커스텀 서비스를 안 타는지 이해 하려면 OAuth2, OIDC (open id connect) 두 방식의 차이를 이해해야 한다 OAuth2는 인가에 초점을 맞췄고 다양한 상황에 쓰일 수 있도록 ..
[AOP] 소요 시간 측정 AOP class level에 적용하기 진행 중인 프로젝트 메서드에 slow query 분석 또는 잘못된 비즈니스 로직을 해결하기 위해 시간 측정 AOP를 활용해봤다 우선 aop 관련 package를 따로 만들어주고 시간 측정할 메서드에 적용시킬 custom ryumodrn.tistory.com 이전 글에 해당하는 내용 지난 버전에서는 LogExecutionTime이라는 custom annotation을 만들고 이 녀석을 적용시킬 수 있는 범위를 클래스까지 추가하여 클래스에 해당 애노테이션을 붙여 시간 측정을 했다 실제 운영 시에는 이와 같이 선별적인 방식으로 시간 측정할 클래스에 애노테이션을 붙여 사용해야 할 것인데 애노테이션을 붙이지 않고 더 간편하게 선별적으로 적용시키는 ..
이 글은 OAuth2 구현을 소개하는 글은 아니다, SpringBoot OAuth2 구현은 이미 훌륭하게 작성된 예제 코드가 많다 구글에 검색했을 때 대부분의 OAuth2 구현은 callicoder.com에서 소개된 예제를 기반으로 만들어져 있다 나도 callicoder.com을 참고해 OAuth2를 구현하였고 코드를 자세하게 이해하기 위해, 또한 가독성 향상을 위해 여러 번의 리팩토링을 거쳐서 코드는 조금 다를지라도 흐름은 거의 비슷하다 아래 블로그는 한국어로 친절하게 설명되어 있어 구현하는데 어려움이 없을 것이다 https://www.callicoder.com/spring-boot-security-oauth2-social-login-part-2/ http://yoonbumtae.com/?p=3000 ..
[Redis] HATEOAS와 @Cacheable - 2 [Redis] HATEOAS와 @Cacheable 이전 Redis 삽질기에서 해결법은 찾지 못 했으나 타협점을 찾았기 때문에 올린다 내가 의도 했던 것은 RestController 응답을 HATEOAS에 맞춰 ResponseEntity 또는 ResponseEntity.. ryumodrn.tistory.com 위 글에서 이어지는 글이다 2편에서의 문제점은 GenericJackson2JsonRedisSerializer를 사용했을 때 발생하던 아래 에러다 Caused by: java.lang.ClassCastException: class java.util.LinkedHashMap cannot be cast to class org.springframewo..
[Redis] HATEOAS와 @Cacheable 이전 Redis 삽질기에서 해결법은 찾지 못 했으나 타협점을 찾았기 때문에 올린다 내가 의도 했던 것은 RestController 응답을 HATEOAS에 맞춰 ResponseEntity 또는 ResponseEntity 로 내려주려고 했다 또한 응답. ryumodrn.tistory.com 위 글에서 이어지는 글이다 많은 우여곡절 끝에 HATEOAS를 적용한 PagedModel 형태에 캐시 추상화 @Cacheable을 접목했다 정확하게 말해 Page를 캐시하고 Controller 단에서 Page 형태의 캐시 데이터를 PagedModel로 변환하여 반환한다 지난 글에서의 문제점은 _links 가 깨진다는 점이었다 사실 위 문제를 해결하고도 PagedModel에..
[Redis] @Cacheable 삽질기 조회 성능 향상을 위해 Spring Data Redis를 도입했고 간단해 보이는 @Cacheable, @CachePut 등을 사용하기로 결정했다 그런데 LocalDateTime을 변환할 때 살짝 문제가 있었다 가장 쉬운 해결 방법은 아래와 같 ryumodrn.tistory.com 위 글에서 이어지는 글이다 해결은 금방 했는데 이제야 올리게 됐다 Redis를 사용하면서 @Cacheable로 데이터를 캐싱할 때 발생했던 문제다 LocalDateTime을 JSON 형태로 직렬화 / 역직렬화할 때 LocalDateTime이 풀어 헤쳐지던 문제인데 해결은 간단했다 @JsonFormat(pattern=어쩌구, timezone=저쩌고)로도 풀 수 있지만 이 애노테이션을 모든 ..
멀티 모듈 프로젝트에서 의존성 관리는 어렵다 경험 삼아 & 재미로 멀티 모듈 프로젝트를 하고 있는데 의존성 관리를 아래와 같이 해왔다 module-api, module-service 가 module-core에 의존하고 있는 중이다 얼마전 비대해진 root build.gradle의 dependencies를 세분화하여 각각의 모듈 build.gradle로 옮기는 작업을 진행했다 깔끔하게 다이어트 시키고 모듈 별로 필요한 라이브러리에만 의존할 수 있게 만들어 성공적이었다 현재 root build.gradle에서는 위에 지정해준 project(':module') 라인이 빠진 상태고 모듈 별 build.gradle의 dependencies에서 implementation project(':module') 형태로 관..
토요일에 정말 오랜만에 친구들과 술 한잔 하고 있었는데 긴급 연락이 왔다 뉴스 링크가 걸려 있었는데 마인크래프트에서 시작한 java logging framework의 문제에 관한 기사였다 밖이라서 바로 대응은 못 했는데 logback 쓰고 있던 게 기억나서 더 찾아보았다 원격으로 코드를 밀어 넣을 수 있어 엄청난 문제긴 한데 특정 버전 (2.0-beta9 ~ 2.14.1)에만 해당하는 문제다 다행히 log4j2의 1.x 버전과 logback은 해당 사항 없어서 별 다른 대응 없이 잘 지나갈 수 있었다 원인으로는 log4j-core 모듈이라는데 대충 찾아본 바에 따르면 유연성과 편의 기능을 위해 작성된 코드로 인해 SQL injection 마냥 코드가 들어와서 실행되는 게 문제라고 한다 개인적으로 진행하는..