기술 블로그 모음

국내 IT 기업들의 기술 블로그 글을 한 곳에서 모아보세요

전체 프론트엔드 백엔드 데브옵스 AI 아키텍처 DB 기타
AWS Amplify 호스팅 사이트 AWS WAF 방화벽 지원 시작
AWS KOREA
AWS Amplify 호스팅 사이트 AWS WAF 방화벽 지원 시작

오늘은 AWS WAF의 AWS Amplify Hosting 통합 정식 출시 소식을 알려드립니다. 웹 애플리케이션 소유자는 다양한 위협으로부터 애플리케이션을 보호하기 위해 끊임없이 노력 중입니다. 이전에는 Amplify Hosting으로 호스팅되는 애플리케이션에 강력한 보안 태세를 구현하려면 AWS WAF 보호가 지원되는 Amazon CloudFront...

구글처럼 복잡한 권한 쉽게 관리하기 feat. GraphQL
당근마켓
구글처럼 복잡한 권한 쉽게 관리하기 feat. GraphQL

안녕하세요. 당근 알림 경험팀에서 프론트엔드 엔지니어로 일하고 있는 딜런(Dylan.lee)이라고 해요.알림 경험팀은 당근 사용자들뿐만 아니라 당근 구성원들의 알림 경험(Notification Experiences)을 책임져요. 사용자가 그동안 받은 알림을 모아볼 수 있는 알림함부터 당근 구성원이 알림을 간편하게 발송할 수 있는 알림 센터까지, 알림과...

Shout-Out to Kotlin for Server-Side Content Creators!
JetBrain Korea
Shout-Out to Kotlin for Server-Side Content Creators!

If you’re using Kotlin for server-side development and sharing your experiences – whether through blog posts, videos, or sample projects – know that we see you, and we want to highlight your ...

2024년 PHP 현황
JetBrain Korea
2024년 PHP 현황

PHP는 여전히 웹 개발의 중추로, 전 세계 수백만 개 웹사이트의 기반입니다. 활기차고 헌신적인 PHP 커뮤니티는 PHP의 유연성과 사용 편의성에 큰 가치를 둡니다. 그런데 PHP 개발의 현황은 어떨까요? 에코시스템을 형성하는 더 심층적인 인사이트와 추세를 알아보기 위해 JetBrains의 사내 전문가인 PHP 개발자 애드버킷 Brent Roose에게...

Spring Transactional Rollback Deep Dive
하이퍼커넥트
Spring Transactional Rollback Deep Dive

안녕하세요. Azar API Dev Team의 Ledger입니다. 이번 글에서는 Spring Transactional 동작에서 Checked Exception과 Unchecked Exception의 롤백(rollback) 처리에 관한 내용을 다뤄보겠습니다. 여러 사례를 통해 예외 처리 코드를 작성해 보고, 자주 혼동되는 부분들을 정리해 보았습니다. 그...

당근페이 재무 결산 사례로 보는 백엔드와 데이터의 만남
당근마켓
당근페이 재무 결산 사례로 보는 백엔드와 데이터의 만남

안녕하세요. 저는 당근페이 머니서비스팀 백엔드 엔지니어 클로버(Clover)에요! 당근페이에서는 동네에서 쉽고 편하게 쓸 수 있는 금융 서비스를 만들고 있어요. 당근에서 일어나는 거래에는 당근머니가 사용되는데요. 머니서비스팀에서는 당근머니와 관련된 일들에 더해, 동네에서 쓰면 다양한 혜택이 적립되는 당근카드와 관련된 일들을 하고 있어요. 다시 말해 저희 팀은 지역에서 생기는 다양한 거래를 연결하는 게 목표예요!재무 결산의 중요성당근페이에서는 매월 셀 수 없이 많은 거래가 이루어지는데요. 매월 이 거래들을 바탕으로 당근페이 내에서 돈이 어떤 경우에 어디에서 어디로 흘러갔는지 재무 결산을 진행해요. 예를 들면 당근머니를 충전하면 사용자의 계좌에서 당근페이 모계좌로 돈이 이동하는데, 당근머니 충전액의 총합은 모계좌 입금액의 총합과 같아야 해요. 재무 결산을 했는데 단 1원이라도 맞지 않는다면 그 1원을 찾기 위해 모든 업무를 중단해야 해요. 그만큼 핀테크 회사인 당근페이에서는 재무 결산이 중요해요.당근페이 초기에는 거래량이나 거래 종류가 많지 않았기 때문에 데이터를 직접 SQL 쿼리로 뽑았었어요. 그러다가 당근페이가 커지면서…결산 어드민 도입 전 머니 결산 스레드위와 같이 매월 초에 열리는 머니 결산 스레드에서, 백엔드 엔지니어들이 태그되기 시작했어요. 머니서비스팀에 도메인 담당자가 많아질수록 매월 결산 슬랙 스레드에 태그되는 사람도 점점 많아졌죠. 결국 머니서비스팀 백엔드 개발자들은 매월 첫 주에는 정기적으로 코드가 아닌 SQL 쿼리를 치고 있는 상황이 일어났어요.이런 비효율을 어떻게 개선해 볼 수 있을까요? 모두가 SQL 쿼리를 더 정확하고 빠르게 작성하도록 SQLD 자격증 스터디를 열어볼 수도 있을 듯해요. 하지만 저희 팀에서는 이를 시스템적으로 개선해보고자 했어요.Spring Batch와 MySQL기반 결산가장 쉽게 떠오르는 방법은 스프링 배치를 이용하는 방법이에요. 대용량 데이터를 처리할 때 가장 흔하게 사용하는 기술이죠. 처음에는 저희 팀도 이 방법으로 결산을 진행했어요. 대략적인 구조는 다음과 같았어요.당근페이 머니서비스팀은 마이크로서비스로 컴포넌트들이 구성돼 있어서, 각자 다른 데이터베이스를 가지고 있어요. 여러 데이터들을 종합해서 진행하는 결산의 특성상, 배치는 여러 개의 데이터베이스를 바라보고 진행해요.이러한 결산은 크게 두 단계로 구성돼 있어요.원본 DB에서 성공 거래를 바탕으로 원장(raw data)을 구성해 결산 전용 DB에 밀어 넣기위에서 가져온 원장을 기반으로 집계해 일별 결산 리포트를 만들고 결산 전용 DB에 쌓기이런 단계별로 구성된 배치를 매일 00시에 Cron을 통해 트리거해요. 재무 담당자나 결산 이해관계자들은 이렇게 단계별, 일자별로 잘 집계된 데이터들을 스프링 기반 백엔드 및 결산 프론트 화면을 통해 언제든지 조회할 수 있어요. 여기까지는 괜찮은 것 같아요. 그러던 어느 날 문제를 마주했죠.구조의 한계배치 실패 알림 발생어느 날 갑자기 이런 배치 작업 실패 알림을 받게 됐어요. 보통 이렇게 결산 배치를 실패하는 이유는 크게 세 가지가 있어요.결산 코드 비즈니스 로직 상의 문제로 인한 실패원본 테이블 구조(혹은 필드 ENUM 타입) 변경으로 인한 실패인프라나 애플리케이션 자체(JVM, DB 커넥션 등)의 이슈로 인한 실패여기서 첫 번째와 두 번째 이슈가 가장 잦으면서도 중요한 이슈라 조금 더 구체적으로 예시를 들어볼게요. 예를 들어, 당근페이가 “당근머니 송금” 기능만을 제공하다가, “당근머니 결제”라는 기능을 추가적으로 오픈했다고 가정할게요. 이때 당근머니 DB에는 “송금”이라는 거래 타입뿐만 아니라 “결제”라는 거래 타입도 추가될 거예요. 게다가 기존의 “송금”은 단순히 사용자 간의 지갑 잔액이 변한다면, “결제”는 지갑 잔액 변동과 함께 외부로의 정산이 발생해요. 이 경우 테이블 구조뿐만 아니라 결산에 대한 비즈니스 로직도 변경될 수 있어요.물론 당근머니만을 대상으로 결산한다면 이는 큰 문제는 아닐 거예요. 당근머니와 관련된 기능이 추가될 때마다 결산에도 추가적으로 대응하면 되니까요. 하지만 당근페이에서의 결산은 당근머니, 은행 거래, 카드 거래 등 다양한 서비스를 기반으로 진행하게 돼요. 그리고 각 서비스들은 결산에서 긴밀하게 얽혀 있죠. 결국 결산은 언제든 깨질 수 있는, 테이블 끝에 걸친 유리잔이 됐어요. 각 서비스들에 새로운 기능이 생길수록 작은 변화가 결산에 큰 영향을 미치게 되고, 결국 결산 오류가 더 자주 발생하게 된 거예요.이외에도 다른 서비스 컴포넌트에서 기능이 추가될 때 대응이 복잡하다는 문제가 있어요. 결산이 하는 일은 거래들을 일자별로 모아서 더하고 빼는 것뿐이지만, 새 기능이 도입될 때마다 결산에 추가적으로 코틀린 ENUM 타입을 추가하고 코드를 작성해야 하는 건 굉장히 비효율적이죠. 모든 팀원들이 결산의 내부 동작 구조를 이해하고 있는 건 아니니까요.위의 문제를 해결할 수 있는 방법은 없을까요? 조금 더 엄밀히 말하면 테이블 변경이나 비즈니스 로직 변경에 좀 더 유연하게 대응하려면 어떻게 해야 할까요?Airflow와 dbt를 활용한 데이터 기반 결산Airflow와 dbt는 백엔드 개발자에게는 조금은 생소한 개념일 수도 있는데요. 우선 Airflow는 초기에 Airbnb에서 개발한 작업(워크플로)을 코드를 통해 작성하고, 스케쥴링하고, 모니터링할 수 있는 플랫폼이에요.dbt는 원시 데이터(raw data)를 분석 가능한 형태의 데이터로 변환하고, 데이터 중심의 결정을 내리도록 돕는 프레임워크예요. dbt는 Airflow 위에서 실행되면서, 미리 작성해 둔 SQL 쿼리를 바탕으로 데이터를 가공해요. 쿼리 결과에 맞는 형태로 테이블이나 뷰를 만들어주는 툴이라고 볼 수 있어요.기존 구조였던 Spring Batch와 MySQL 기반 결산과 대조되는 개념들로 이해하면 더 쉽게 이해할 수 있어요. 기존의 원장 데이터와 집계된 데이터를 저장하던 MySQL 데이터베이스는 데이터 웨어하우스가 되었고, Argo Workflow가 해주던 작업 스케쥴링은 이제 Airflow가 담당해요. 데이터 변환과 집계 로직을 담고 있던 Spring Batch는 dbt가 대신하게 됐고요!가장 큰 변화는 각 서비스 컴포넌트의 DB 구조를 이해하고 직접 집계해야 했던 기존 결산 구조에서 벗어났어요. dbt를 통해 각 서비스가 오너십을 가지고 집계 쿼리를 작성하고, 결산 서비스는 잘 집계된 데이터를 분류에 맞게 시각화하는 기능을 제공하게 됐어요. 이제 더 이상 각 컴포넌트의 기능 추가로 인해 결산 집계가 실패하는 일은 없어졌어요.큰 그림 말고, 이제 조금 더 깊게 살펴볼까요?dbt 스키마 구조위에서 기존 MySQL 기반 결산을 소개하면서, 결산은 크게 두 단계로 나뉜다고 설명드렸어요. 먼저 거래 원장을 구성하고, 원장을 바탕으로 결산 집계 작업을 진행하죠. 실제 재무 결산에서는 집계된 데이터를 보고 진행하지만, 혹여 결산 과정에서 틀어지거나 올바르지 않은 데이터가 있을 때를 교차검증하기 위해 원장 테이블이 존재해요.예를 들어, “프로모션 지급” 거래의 원장은 다음과 같은 쿼리를 통해 성공한 거래 내역을 추출해요.-- promotion_ledger.sql (프로모션 지급 거래 원장)SELECT 거래ID, 거래타입, 거래금액, 생성일시FROM 거래내역WHERE 거래타입 = '프로모션' AND 거래상태 = '성공'# dbt YAMLmodels: - name: promotion_ledger description: 프로모션 지급 거래 원장 columns: - name: 거래금액 description: 거래에서 발생한 금액 (실제 지급된 금액) data_tests: - not_null - dbt_utils.expression_is_true: expression: "> 0"이러한 형태로 추출된 dbt 스키마는 거래타입별로 존재해요. 모든 거래 타입의 거래 원장을 하나의 스키마로 관리하지 않는 이유는 실제 재무실 니즈에 조금 더 유연하게 대응하기 위함인데요. 예를 들면 같은 “프로모션” 거래타입 일지라도, 당근페이 내에서 진행한 프로모션이 있을 수도 있고 다른 서비스에서 진행한 프로모션이 있을 수 있어요. 원장을 거래타입별로 구분하면서 이러한 구체적인 거래 분기에 조금 더 유연하게 대응할 수 있게 되었어요.data_tests: - not_null - dbt_utils.expression_is_true: expression: "> 0"그리고 이 표현처럼 특정 필드의 값을 테스트할 수 있게 됐어요. 위 경우에서는 거래 금액이 null이 아니고 0보다 커야 함을 테스트하는 표현이에요. 데이터 변환과 함께 원장 데이터의 무결성도 함께 검증할 수 있게 된 셈이에요.이렇게 수집된 원장을 기반으로 다음과 같이 일별 집계를 진행해요.-- daily_report.sql (일별 결산 집계)WITH -- 프로모션 지급daily_promotion AS ( SELECT DATE(생성일시) AS 대상날짜, 거래타입, SUM(거래금액) AS 총거래금액 FROM {{ ref('promotion_ledger') }} GROUP BY 1, 2),-- 머니송금daily_transfer AS ( SELECT DATE(생성일시) AS 대상날짜, 거래타입, SUM(거래금액) AS 총거래금액 FROM {{ ref('transfer_ledger') }} GROUP BY 1, 2)...SELECT * FROM daily_promotionUNION ALLSELECT * FROM daily_transfer...# DBT YAMLmodels: - name: daily_report description: 일별 결산 집계 columns: - name: 총거래금액 description: 일별 거래에서 발생한 총 금액 data_tests: - not_null - dbt_utils.expression_is_true: expression: "> 0"앞서 수집했던 원장 스키마를 바탕으로 일별 거래타입별 거래액을 집계해요. 집계 결과도 마찬가지로 상황에 맞게 테스트를 진행해요.결국 이렇게 정의된 SQL과 dbt YAML들을 바탕으로 Airflow는 dbt Task를 실행시켜 주고, 이 Task는 결국 필요한 데이터를 데이터 웨어하우스에 밀어 넣어줘요. 그렇다면 이 데이터는 어떻게 기존 결산 어드민처럼 재무팀분들에게 보여줄 수 있을까요? 재무팀분들께 DW 접근 권한을 드려야 할까요?Spring Boot에 데이터 웨어하우스 연결하기현재 당근페이에서 사용하고 있는 데이터 웨어하우스는 Redshift인데요. 놀랍게도 Redshift(정확히는 AWS)에서 JDBC 드라이버를 제공하고 있어요. JDBC 드라이버가 존재한다는 것은 Spring Data JPA를 그대로 활용할 수 있다는 것을 의미해요. 결국 실제 백엔드에 새로운 쿼리 전용 라이브러리를 도입할 필요 없이, 기존 기술 스택 그대로 새로운 Airflow와 dbt 기반 결산 데이터를 조회할 수 있게 됐어요.우선 다음과 같이 Gradle에 RedShift JDBC 드라이버를 추가하고,dependencies { runtimeOnly("org.hibernate.orm:hibernate-community-dialects:6.4.4.Final") runtimeOnly("com.amazon.redshift:redshift-jdbc42:2.1.0.30")}다음과 같이 driver-class-name 과 database-platform 값, 그리고 RedShift 접근 정보를 채워주면 돼요.spring: jpa: driver-class-name: com.amazon.redshift.jdbc.Driver database-platform: org.hibernate.community.dialect.PostgreSQLLegacyDialect datasource: url: jdbc:redshift://{DB_HOSTNAME}:{DB_PORT}/{DB_DATABASE} username: {DB_USERNAME} password: {DB_PASSWORD}기존에 Spring Boot 2.x 에서는 Hibernate에 내장된 Dialect인 org.hibernate.dialect.PostgreSQL9Dialect를 사용할 수 있었지만, Spring Boot 3.x에서 해당 클래스가 사라지면서 org.hibernate.community.dialect.PostgreSQLLegacyDialect 로 지정해야 해요.그 외에는 기존 Spring Boot JPA와 똑같아요. 다음과 같이 Entity를 정의하고 쓰면 돼요. (대신 애플리케이션에서 업데이트하는 것을 방지하기 위해 @Immutable 어노테이션을 추가했어요.)@IdClass(DailyReportEntity.DailyReportEntityKey::class)@Immutable@Entity@Table(name = "daily_report")abstract class DailyReportEntity( @Id @Column(name = "대상날짜", nullable = false) val targetDate: LocalDate = LocalDate.now(), @Id @Column(name = "거래타입", nullable = false) val transactionType: String = "", @Column(name = "총거래금액", nullable = false) val totalAmount: Long = 0L,) { data class DailyReportEntityKey( val targetDate: LocalDate = LocalDate.now(), val transactionType: String = "", ) : Serializable}그 외에 다른 로직들은 기존 Spring JPA 사용법과 전부 동일해요. 다시 말해 과거에 Spring Batch와 MySQL기반으로 결산 데이터를 모으고 쌓는 부분만 Airflow로 이전하고, 그 외의 결산 결과를 확인하던 대시보드와 조회 로직은 그대로 사용할 수 있었어요. 덤으로 기존 MySQL로 되어있던 결산 데이터베이스는 필요 없어졌고요!최종 결산 아키텍처 구조는 다음과 같아요.(데이터팀분들이 구축해 주신 부분) 매 시간마다 운영(Reader 인스턴스) MySQL 데이터베이스에서 Spark를 통해 데이터 웨어하우스로 테이블이 싱크돼요.매일 Airflow는 dbt를 통해 결산에 필요한 원장과 일별 집계 데이터를 테이블로 가공해요.재무 담당자분들은 결산 어드민 프론트 화면을 통해서 데이터 웨어하우스에 적재된 결산 데이터를 볼 수 있어요.이루어낸 성과우선 기존 Spring Batch와 MySQL 기반 결산의 한계였던 구조적 복잡성을 해결했어요. 덕분에 머니서비스팀 내의 백엔드 개발자분들이 각자가 개발한 기능에 대한 결산 대응을 SQL 쿼리 수정만으로 쉽게 해결할 수 있게 됐죠.이는 단순히 작업자의 접근성을 끌어올려줬을 뿐만 아니라, 실제 결산 시 기능 추가 과정에서 필요한 작업들을 효과적으로 줄이기까지 했어요. 이제 더 이상 애플리케이션에서 각 타입을 알고 계산 로직을 추가해야 하는 게 아니라 SQL 쿼리에서만 대응하면 되니까요![Before]Spring Batch + MySQL 아키텍쳐에서 결산 로직 변경시 사례[After]Airflow + dbt 아키텍쳐에서 결산 로직 변경시 사례확실히 기존에 코틀린 코드로 존재하던 결산을 SQL 쿼리로 변경하니 수정 사항을 변경하는 게 간단해지기도 하고 가시성도 높아졌죠?마치며현대 사회에서 데이터의 가치는 회사의 중요한 자산과 경쟁력이 되고 있어요. 특히 당근페이와 같은 핀테크 기업에서 이러한 데이터의 정합성과 투명성은 사용자들의 신뢰를 구축하기 위한 핵심 요소예요. 위 사례에서 살펴본 데이터 기반 결산 아키텍처로의 변화는 단순한 결산 과정 비효율 개선을 넘어서, 장기적으로 당근페이 내 구성원들이 데이터 기반의 의사결정과 소통을 할 수 있도록 도와요. 결국 이를 통해 사용자에게 더 나은 서비스를 제공하는 데 기여할 수 있을 거예요.따라서 당근페이에서는 데이터 중심적 사고를 백엔드 엔지니어에게도 중요한 역량으로 인식해요. 당근페이의 백엔드 엔지니어들은 단순히 코드를 작성하는 것을 넘어, 데이터의 정합성과 무결성을 보장하기 위해 위의 Airflow 같은 다양한 데이터 중심의 아키텍처를 탐구하죠. 또한 데이터 중심적 사고방식을 확산하려고 노력하고 있기도 해요. 조직 전체가 데이터를 적극적으로 이해하고 활용하려는 문화가 정착되면서, 모두가 데이터에 기반해 더 나은 의사결정을 할 수 있도록 변화하고 있어요.지역의 다양한 거래를 연결한다는 팀의 비전을 위해 비효율적인 부분을 개선하고 데이터 관점에서도 끝없이 고민하는 머니서비스팀! 저희 팀이나 오늘의 이야기에 대해 궁금하신 게 있다면 clover@daangnpay.com 으로 연락 주세요! 사소한 궁금증도 괜찮아요 ✌️또 현재 머니서비스팀에선 기술의 장벽 없이 비효율적인 부분을 개선하고 다양한 문제를 해결해 나갈 백엔드 엔지니어도 채용하고 있으니 많은 관심 부탁드려요!https://about.daangn.com/jobs/4511184003/참조[1] https://airflow.apache.org/[2] https://www.getdbt.com/product/what-is-dbt당근페이 재무 결산 사례로 보는 백엔드와 데이터의 만남 was originally published in 당근 테크 블로그 on Medium, where people are continuing the conversation by highlighting and responding to this story.

Spring Boot MongoDB 트랜잭션 도입 실전 가이드
올리브영
Spring Boot MongoDB 트랜잭션 도입 실전 가이드

👉 들어가기에 앞서 안녕하세요! 올리브영에서 상품 도메인을 책임지고 있는 윤긱스입니다. 혹시 지금.. 이런 고민을 하고 계신가요? 🙋 : MongoDB…

Spring Boot 버전업 중 알게된 Java 버전별 캡슐화 정책 강화
마켓컬리
Spring Boot 버전업 중 알게된 Java 버전별 캡슐화 정책 강화

자바 모듈 시스템의 변화로 인한 직렬화 문제를 분석하면서 알게된 내용을 공유합니다.

커스텀 어노테이션과 리플렉션으로 구현한 Spring Boot 웜업 로직 최적화
올리브영
커스텀 어노테이션과 리플렉션으로 구현한 Spring Boot 웜업 로직 최적화

DX본부 홈쇼핑검색추천파트 추천서비스개발 담당 경력사원 채용(~10/27)
GS리테일
DX본부 홈쇼핑검색추천파트 추천서비스개발 담당 경력사원 채용(~10/27)

기기와 앱의 무결성 보장부터 서비스 요청 보호까지: LINE의 기기 증명 서비스 - 2편
라인
기기와 앱의 무결성 보장부터 서비스 요청 보호까지: LINE의 기기 증명 서비스 - 2편

들어가며 안녕하세요. Auth & Account Dev 팀의 김은찬, 김종민, 권기범, 정의엽, 허원영입니다. Auth & Account Dev 팀은 LY Corporation 그...

애셋팀 레거시 개선 (2) 쏘카존 관리 시스템 - 차량재배치 리팩터링
쏘카
애셋팀 레거시 개선 (2) 쏘카존 관리 시스템 - 차량재배치 리팩터링

목차 소개 개선 목적 차량재배치 설명 카프카 스프링으로 변경된 아키텍처 및 코드 설명 4.1. 기존 코드 설명 4.2. 책임분리 테스트코드 작성 리팩터링과 전략패턴 마무리 1. 소개 안녕하세요. 쏘카 서비스 엔지니어링본부 애셋(Asset)팀 백엔드 개발자 원스톤입니다. 저는 쏘카 존과 차량 도메인을 개발하고 있습니다. 지속 성장하는 소프트웨어를 만들기...

Headless CMS를 위한 변경 데이터 캡쳐(CDC) 기술 설계하기
라인
Headless CMS를 위한 변경 데이터 캡쳐(CDC) 기술 설계하기

들어가며 안녕하세요. UIT 팀 윤혜린입니다. LY Corporation Group에서는 헤드리스 CMS인 LandPress Content를 사내 임직원 대상으로 운영하고 있습니다...

Spring Security 의 인증 알아보기
네이버 페이
Spring Security 의 인증 알아보기

안녕하세요. 네이버페이 회원&인증BE 의 최용화입니다.Spring Security는 강력한 보안 프레임워크로서, 애플리케이션의 인증과 인가 과정을 효율적으로 관리합니다. 저희 팀에서는 다음과 같은 기능을 구현하는 데에 Spring Security 를 사용하고 있습니다.인증 여부 확인 (인증이 안된 사용자일 경우 로그인 및 가입 유도 / 인증이 완료된 사용자일 경우 적절한 권한 부여), 권한을 이용한 접근 제어(권한이 없는 사용자일 경우 권한 획득을 위한 절차 수행), 보안 공격으로부터 보호(CSRF 공격 방지), PC / MOBILE 최적화 페이지 제공(사용자가 접근한 환경을 파악하여 최적화된 페이지 제공)이 글에서는 Spring Security의 인증(Authentication) 과정 전반을 살펴보고, 각 단계의 역할과 작동 방식을 자세히 알아보겠습니다.이 글은 Spring Security 6.3.0 공식문서를 기반으로 작성되었습니다.Spring Security 의 Filter 기반 동작 방식 이해Spring Security의 인증 수행을 이해하려면 Spring Security 의 구조에 대한 이해가 선행되어야 합니다. Spring Security는 Servlet Filter 기반으로 동작합니다. 여기서, 중요한 개념인 FilterChainProxy, SecurityFilterChain, 보안 필터(Security Filter)에 대해 알아봅니다.FilterChainProxy의 개념FilterChainProxy는 Spring Security에서 제공하는 특수한 Filter로 SecurityFilterChain을 사용하여 다양한 보안 필터가 동작하게 합니다.사실, Servlet Container의 라이프사이클과 Spring의 ApplicationContext 사이를 연결하는 DelegatingFilterProxy 라는 필터 구현체도 중요한 상위 개념이나 이 글에서는 설명을 생략합니다. 자세한 설명은 이 페이지를 참고하세요.SecurityFilterChain의 개념SecurityFilterChain은 Spring Security에서 보안 필터(Security Filter)의 체인을 정의하는 데 사용됩니다. 요청이 애플리케이션의 Servlet에 도달하기 전에 다양한 보안 검사를 수행하는 필터들이 있으며, 이를 보안 필터라고 부릅니다. SecurityFilterChain은 각 보안 필터가 순차적으로 실행되도록 하여 애플리케이션의 보안 설정을 체계적으로 관리할 수 있게 합니다.보안 필터(Security Filter)의 주요 기능SecurityFilterChain 에 선언된 다양한 보안 필터를 통해 아래의 기능을 수행하게 됩니다.인증(Authentication): 사용자의 신원을 확인합니다. 예를 들어, 사용자가 로그인 폼을 제출하면, 이를 처리하는 필터가 실행됩니다.인가(Authorization): 사용자가 요청한 리소스에 접근할 권한이 있는지 확인합니다.각종 보안 공격으로부터 보호(Protection Against Exploits): CSRF 공격, Session Fixation 공격, sniffing 공격, Clickjacking 등의 보안 공격으로부터 보호합니다.세션 관리: 사용자의 세션을 생성, 관리, 종료하는 과정입니다.기타 기능: HTTP 응답 헤더를 설정하여 보안을 강화하는 기능, Remember Me 기능 등을 지원합니다.보안 필터(Security Filter) 소개여러 개의 보안 필터가 있지만, 이 글에서 자주 보게 될 몇 가지 보안 필터만 가볍게 소개 드리려고 합니다. 나열된 순서대로 실행됩니다.UsernamePasswordAuthenticationFilter: 폼 기반 로그인 처리를 수행합니다.DefaultLoginPageGeneratingFilter: 기본 로그인 페이지를 생성합니다.ExceptionTranslationFilter: ExceptionTranslationFilter 의 다음 Filter 에서 발생한 Exception을 처리하고 이에 대한 적절한 응답을 반환합니다.AuthorizationFilter: 사용자가 요청한 리소스에 대해 접근 권한이 있는지 확인합니다. 권한이 없는 경우 접근을 거부하고, 적절한 에러 페이지를 반환하거나 예외를 발생시킵니다.Spring Security의 Form 기반 인증인증(Authentication)은 특정 리소스에 액세스하려는 주체(Principal)의 신원을 확인하는 과정입니다. Spring Security는 다양한 인증 방법을 지원하며, 이 글에서는 주로 폼 기반 인증(Form-Based Authentication)을 예로 들어 설명하겠습니다.인증 관련 주요 용어Spring Security 인증과 관련하여 자주 사용되는 용어에 대해 설명합니다.SecurityContextHolder: Spring Security가 인증된 사용자의 정보를 저장하는 곳입니다.SecurityContext: SecurityContextHolder에서 가져오며 현재 인증된 사용자의 인증정보(Authentication)를 포함합니다.Authentication: 사용자가 입력한 자격 증명(Pricipal과 Credentials)을 AuthenticationManager에 전달하는 용도로 사용되거나 SecurityContext에서 현재 사용자를 나타내는 용도로 사용되는 객체입니다.GrantedAuthority(Authorities): 인증된 사용자에게 부여된 권한을 나타내며, 역할(role)이나 범위(scope) 등을 포함합니다.AuthenticationManager: Spring Security의 필터가 인증을 수행하는 방법을 정의한 API(인터페이스)입니다.ProviderManager: AuthenticationManager 의 구현체입니다.AuthenticationProvider: ProviderManager가 여러 종류의 인증(Basic 인증, Form 인증 등)을 지원 및 수행하기 위해 사용하는 인터페이스입니다. 하나의 ProviderManager에 여러 개의 AuthenticationProvider를 등록하여 사용할 수 있습니다. 가장 흔히 사용되는 구현체는 DaoAuthenticationProvider입니다.Form 기반 인증 수행 과정 — UsernamePasswordAuthenticationFilter아래는 UsernamePasswordAuthenticationFilter 에서 수행하는 인증 과정에 대한 도식입니다.Form 기반 인증 요청에서 username과 password를 추출하여 UsernamePasswordAuthenticationToke 객체를 ProviderManager에 전달합니다. (여기서, UsernamePasswordAuthenticationToken은 위에서 설명한 Authentication 인터페이스의 구현체이고, ProviderManager는 위에서 설명한 AuthenticationManager 인터페이스의 구현체입니다.)ProviderManager는 DaoAuthenticationProvider 를 이용하여 인증을 수행합니다. (여기서, DaoAuthenticationProvider 는 위에서 설명한 AuthenticationProvider 인터페이스의 구현체입니다.)DaoAuthenticationProvider는 UserDetailsService를 이용해 전달받은 username과 일치하는 UserDetails(저장된 사용자 정보)를 조회합니다.DaoAuthenticationProvider는 PasswordEncoder를 이용해 전달받은 password와 3번 과정에서 조회한 UserDetails의 비밀번호가 일치하는지 검증합니다.4번 과정에서 비밀번호 검증까지 성공하면 사용자 인증은 성공한 것입니다. 이 때, 인증이 완료된 UsernamePasswordAuthenticationToken 을 반환하게 되며 이 구현체의 principal 값은 UserDetailsService에서 조회해온 UserDetails로 설정됩니다.최종적으로, 반환된 UsernamePasswordAuthenticationToken은 SecurityContextHolder에 설정됩니다.Form 기반 인증 수행 예시 — 예제 코드인증 수행 과정을 Spring Security 예제 코드와 TRACE 로깅을 통해 확인해봅니다.먼저, Gradle 의존성부터 설정합니다.dependencies { implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'org.springframework.boot:spring-boot-starter-web'}리소스를 관리하는 ResourceController.java 입니다.@Controllerpublic class ResourceController { @GetMapping("/private") @ResponseBody public String loginSuccess() { return "Private Resource"; }}우리가 보호하려는 리소스에 대한 핸들러를 간단하게 명시하였습니다.다음으로 Spring Security 에서 제공하는 Form 기반 인증을 구성한 SecurityConfig.java 입니다.@EnableWebSecurity@Configurationpublic class SecurityConfig { @Bean public UserDetailsService userDetailsService() { InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager(); UserDetails user = User.withDefaultPasswordEncoder() .username("user") .password("12345") .authorities("READ") .build(); inMemoryUserDetailsManager.createUser(user); return inMemoryUserDetailsManager; } @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.authorizeHttpRequests(authorize -> { authorize .requestMatchers("/private").hasAuthority("READ") .anyRequest().authenticated(); }); http.formLogin(Customizer.withDefaults()); return http.build(); }}이 구성에서 설정한 내용은 아래와 같습니다.사용자 정보 저장을 위해 메모리 저장소를 이용하며 사용자 정보를 추가해두었습니다. 사용자의 정보는 다음과 같습니다.username: userpassword: 12345권한: READ2. 보호할 리소스인 http://localhost:8080/private 리소스에 접근하기 위해서는 인증된 사용자여야 하며, 인증된 사용자가 가지는 권한 중 READ 권한이 있어야 함을 명시하였습니다.3. Spring Security 에서 기본적으로 제공하는 Form 기반 인증을 수행합니다.다음으로 Spring Security 에서 제공하는 로깅 기능을 명시한 application.properties 파일입니다.logging.level.org.springframework.security=TRACESecurity Filter가 수행되는 순서와 사용자 요청이 어떻게 처리되는지 로그를 통해 확인할 수 있습니다.Form 기반 인증 수행 예시 — 인증되지 않은 사용자가 리소스를 요청할 때흐름도인증되지 않은 사용자가 http://localhost:8080/private 리소스에 접근을 시도합니다.AuthorizationFilter에서 AccessDeniedException을 발생시켜 인증되지 않은 요청이 거부되었음을 알립니다.ExceptionTranslationFilter는 AuthorizationFilter 에서 발생한 AccessDeniedException 에 대한 처리로 아래의 과정을 수행합니다.SecurityContextHolder 에 저장된 Authentication 데이터가 지워집니다.추후에 인증 과정이 성공할 때, 현재 실패한 http://localhost:8080/private 요청을 바로 수행할 수 있도록 현재의 요청 정보가 담긴 HttpServletRequest 객체를 RequestCache에 저장해둡니다.AuthenticationEntryPoint에 구현된 인증되지 않은 사용자에게 자격 증명을 요청하는 기능을 수행합니다. 여기서 구현된 AuthenticationEntryPoint 객체는 LoginUrlAuthenticationEntryPoint 이므로 로그인 페이지(기본 설정 값은 http://localhost:8080/login)로 redirect 하는 작업을 수행하게 됩니다.4. 사용자의 브라우저는 redirect된 로그인 페이지 (기본 설정 값은 http://localhost:8080/login) 를 요청하게 됩니다.5. LoginController 에서 로그인 페이지(login.html)를 렌더링하여 응답합니다.로그 확인위 로그는 흐름도 상 1 ~ 2번까지의 과정에 대한 로그입니다. 인증되지 않은 사용자가 http://localhost:8080/private 요청을 호출할 때 발생하는 로그입니다. 로그를 통해 아래와 같은 사실을 알 수 있습니다.FilterChainProxy를 통해 15개의 보안 필터(Security Filter)가 수행됩니다.8번째 보안 필터로 UsernamePasswordAuthenticationFilter가 수행되지만, 로그인 요청(POST /login)이 아니기 때문에 실질적인 인증 과정은 수행되지 않습니다. Spring Security 내부 구현은 아래와 같습니다.UsernamePasswordAuthenticationFilter — 로그인 요청(POST /login) 이 아닐 경우, 인증 로직을 수행하지 않고 다음 보안 필터 호출UsernamePasswordAuthenticationFilter — 로그인 요청(POST /login) 인지 확인15번째 보안 필터로 AuthorizationFilter가 수행되지만, /private 리소스에 접근하려는 사용자가 인증되지 않은 사용자이기 때문에 AccessDeniedException 을 던집니다.14번째 보안 필터인 ExceptionTranslationFilter 는 15번째 보안 필터인 AuthorizationFilter에서 던지는 AcessDeniedException 을 잡아 이전 절에서 설명한 예외 처리 로직을 수행합니다. Spring Security 내부 구현은 아래와 같습니다.ExceptionTranslationFilter — 다음 보안 필터 수행 중 예외 발생 시, handleSpringSecurityException 메소드 호출handleSpringSecurityException 메소드 — 발생한 예외가 AccessDeniedException 일 경우, handleAceessDeniedException 메소드 호출handleAceessDeniedException 메소드 — 인증 정보가 AnonymousAuthentication이므로 sendStartAuthentication 메소드 호출SecurityContext 제거 / 기존 요청 저장 / 로그인 진입점으로 이동위 로그는 흐름도 상 3 ~ 5번까지의 과정에 대한 로그입니다.로그를 통해 아래와 같은 사실을 알 수 있습니다.14번째 보안 필터인 ExceptionTranslationFilter 에 의해 아래 작업이 수행됩니다.현재의 요청 정보가 담긴 HttpServletRequest 객체를 HttpSessionRequestCache에 저장해둡니다.AuthenticationEntryPoint 에 의해 로그인 페이지(http://localhost:8080/login)로 redirect 하게 됩니다.사용자가 인증을 위해 로그인 페이지(http://localhost:8080/login)를 요청하면, 9번째 보안 필터인 DefaultLoginPageGeneratingFilter 에 의해 기본 설정된 로그인 페이지를 응답합니다. Spring Security 내부 구현은 아래와 같습니다.DefaultLoginPageGeneratingFilter — 기본 로그인 페이지 생성 및 응답Form 기반 인증 수행 예시 — 인증 과정에서 실패할 때흐름도사용자가 username과 password를 제출하면 UsernamePasswordAuthenticationFilter는 HttpServletRequest 객체에서 username과 password를 추출하여 Authentication의 구현체인 UsernamePasswordAuthenticationToken을 생성합니다.다음으로 UsernamePasswordAuthenticationToken이 인증을 위해 AuthenticationManager 인스턴스로 전달됩니다.인증이 실패하면 아래 과정을 수행합니다.SecurityContextHolder 에 저장된 Authentication 데이터가 지워집니다.RememberMeServices.loginFail() 메소드가 호출됩니다. RememberMeService 를 설정하지 않은 경우, 어떤 작업도 수행되지 않습니다. 이 예제에서는 RememberMeService 기능을 별도로 설정하지 않았기 때문에 어떤 작업도 수행되지 않습니다.AuthenticationFailureHandler 에 구현된 onAuthenticationFailure() 메소드를 수행합니다. 기본적으로 설정되어 있는 AuthenticationFailureHandler의 구현체는 SimpleUrlAuthenticationFailureHandler 입니다. 인증이 실패하면 /login?error URL로 redirect합니다. 로그인 페이지에서는 error 파라미터의 값을 사용하여 인증 실패 메시지를 사용자에게 표시할 수 있습니다.로그 확인위 로그는 흐름도 상 1 ~ 2번까지의 과정에 대한 로그입니다.인증되지 않은 사용자가 POST http://localhost:8080/login 요청을 잘못된 인증 정보와 함께 전송 시, 발생하는 로그입니다. 로그를 통해 아래와 같은 사실을 알 수 있습니다.8번째 보안 필터로 UsernamePasswordAuthenticationFilter가 수행되고 ProviderManager 와 DaoAuthenticationProvider 가 순차적으로 수행되며 인증이 수행됩니다. Spring Security 내부 구현은 아래와 같습니다.UsernamePasswordAuthenticationFilter — AuthenticationManager 의 authenticate 메소드 호출AuthenticationManager 의 authenticate 메소드 — AuthenticationProvider의 authenticate 메소드 호출AuthenticationProvider의 authenticate 메소드 — 제출된 username과 일치하는 사용자 정보가 없어 BadCredentialException 메소드 호출위 로그는 흐름도 상 3번 과정에 대한 로그입니다. 로그를 통해 아래와 같은 사실을 알 수 있습니다.인증 실패 시, 아래 작업을 수행합니다.SecurityContextHolder 에 저장된 Authentication 데이터가 지워집니다.SimpleUrlAuthenticationFailureHandler 에 구현된 onAuthenticationFailure() 메소드를 수행합니다. /login?error URL로 redirect합니다. Spring Security 내부 구현은 아래와 같습니다.AuthenticationFailureHandler 의 onAuthenticationFailure 메소드 — 인증 실패 시, 로그인 페이지로 redirectForm 기반 인증 수행 예시 — 인증 과정에서 성공할 때흐름도사용자가 username과 password를 제출하면 UsernamePasswordAuthenticationFilter는 HttpServletRequest 객체에서 username과 password를 추출하여 Authentication의 구현체인 UsernamePasswordAuthenticationToken을 생성합니다.다음으로 UsernamePasswordAuthenticationToken이 인증을 위해 AuthenticationManager 인스턴스로 전달됩니다.인증이 성공하면 아래 과정을 수행합니다.새로운 로그인이 발생한 것을 SessionAuthenticationStrategy에 통지합니다.SessionAuthenticationStrategy는 새로운 로그인이 발생할 때, 세션 관련 작업을 수행하는 전략입니다. 이전 세션을 무효화하거나, 동시 로그인 방지 정책을 적용할 수 있습니다.SecurityContextHolder를 새롭게 인증이 완료된 Authentication 데이터로 설정합니다.RememberMeServices.loginSuccess() 메소드가 호출됩니다. RememberMeService 를 설정하지 않은 경우, 어떤 작업도 수행되지 않습니다. 이 예제에서는 RememberMeService 기능을 별도로 설정하지 않았기 때문에 어떤 작업도 수행되지 않습니다.ApplicationEventPublisher가 InteractiveAuthenticationSuccessEvent를 발행합니다.AuthenticationSuccessHandler의 onAuthenticationSuccess() 메소드가 호출됩니다. 기본적으로 설정되어 있는 AuthenticationSuccessHandler 의 구현체는 SavedRequestAwareAuthenticationSuccessHandler 입니다. 로그인 페이지로 redirect되기 전, ExceptionTranslationFilter에서 RequestCache에 저장해둔 원래의 요청을 꺼내와 해당 요청으로 redirect 합니다.로그 확인위 로그는 흐름도 상 1 ~ 3번 과정에 대한 로그입니다.인증되지 않은 사용자가 POST http://localhost:8080/login 요청을 정상적인 인증 정보와 함께 전송 시, 발생하는 로그입니다. 로그를 통해 아래와 같은 사실을 알 수 있습니다.8번째 보안 필터로 UsernamePasswordAuthenticationFilter가 수행되고 ProviderManager 와 DaoAuthenticationProvider 가 순차적으로 수행되며 정상적으로 인증이 수행됩니다.인증 성공 시, 아래 작업을 수행합니다.CompositeSessionAuthenticationStrategy 에서 2가지 세션 관련 작업을 수행합니다.ChangeSessionIdAuthenticationStrategy 를 이용하여 Session Fixation 공격을 방지하기 위한 세션 ID 변경 작업을 수행합니다.CsrfAuthenticationStrategy 를 이용하여 세션에 연결된 CSRF 토큰을 교체합니다.SecurityContextHolder를 새롭게 인증이 완료된 Authentication 데이터로 설정합니다.SavedRequestAwareAuthenticationSuccessHandler 에 구현된 onAuthenticationSuccess() 메소드를 수행합니다. 인증 이전에 요청했던 리소스인 http://localhost:8080/private?continue URL로 redirect 를 수행합니다. Spring Security 내부 구현은 아래와 같습니다.기존 요청을 RequestCache에서 꺼내와서 해당 요청을 다시 수행하도록 redirect위 로그는 인증이 완료된 사용자가 GET http://localhost:8080/private로 redirect 될 때, 발생하는 로그입니다. 로그를 통해 아래와 같은 사실을 알 수 있습니다.15번째로 수행되는 보안 필터인 AuthorizationFilter 에서 사용자의 권한을 확인하는 과정을 수행합니다. 사용자의 권한이 모두 확인되면 FilterChainProxy는 모든 보안 필터가 수행되었으므로 보안적인 절차를 수행되었다는 로그를 출력합니다. 최종적으로 인증을 수행하기 전, 처음 요청했던 리소스에 접근할 수 있게 됩니다.글을 마치며Spring Security의 인증 과정은 체계적이고 확장 가능하게 설계되어 있습니다. 각 구성 요소는 명확한 책임을 가지고 있으며 이를 통해 다양한 인증 요구 사항을 유연하게 처리할 수 있습니다. 이번에는 기본적인 폼 기반 인증 과정을 설명했지만, Spring Security는 OAuth2, JWT, LDAP 등 다양한 인증 방식을 지원하므로 애플리케이션의 보안 요구 사항에 맞춰 적절한 인증 방식을 선택할 수도 있습니다.이 글이 Spring Security의 전반적인 인증 과정과 인증에 필요한 구성 요소에 대한 이해에 도움이 되기를 바랍니다.참고자료https://docs.spring.io/spring-security/reference/servlet/architecture.htmlhttps://docs.spring.io/spring-security/reference/servlet/authentication/architecture.htmlhttps://docs.spring.io/spring-security/reference/servlet/authentication/passwords/form.htmlhttps://docs.spring.io/spring-security/reference/servlet/authentication/passwords/dao-authentication-provider.htmlhttps://docs.spring.io/spring-security/reference/servlet/authentication/session-management.htmlSpring Security 의 인증 알아보기 was originally published in NAVER Pay Dev Blog on Medium, where people are continuing the conversation by highlighting and responding to this story.

워드프레스 백업과 복원은 웹 파일과 DB를 한 쌍으로 맺어야 한다
넷마블
워드프레스 백업과 복원은 웹 파일과 DB를 한 쌍으로 맺어야 한다

1년에 4번 정도 마주치는 워드프레스 업데이트 테스트 상황에 조금 더 편하게 대응하고 있습니다. 다만, 편하게 만나는 환경에 직면한 작업자는 저를 포함해 극소수밖에 되지 않아, 단순히 시간 효용을 계산하자면 큰 이득을 얻진 못했다고도 볼 수도 있습니다. 그래도 업데이트 테스트를... The post 워드프레스 백업과 복원은 웹 파일과 DB를 한 쌍으로...

확장할 수 있는 데이터 추출 서비스 구축 경험 공유
올리브영
확장할 수 있는 데이터 추출 서비스 구축 경험 공유

사람인 백엔드 개발자에게 취업이란? (w/ 제로베이스)
사람인
사람인 백엔드 개발자에게 취업이란? (w/ 제로베이스)

안녕하세요. IT연구소 서비스개발팀 빌링파트 오명학입니다. 우연한 계기로 제로베이스라는 코딩 부트 캠프에서 취준생들을 위한 질문과 관련하여 인터뷰 제의가 들어와 진행하게 되었습니다. 대단한 내용은 없다 보니, 많이 부끄럽긴 하지만 개발자가 되기 위해 준비중인 취준생 분들께서 보신다면 좋을것같아 인터뷰 전문을 사람인 블로그를 통해 공유해보고자 합니다. ...

Web을 위한 gRPC Stub과 Runtime 생성하기 - Feat. Buf & kubernetes
뱅크샐러드
Web을 위한 gRPC Stub과 Runtime 생성하기 - Feat. Buf & kubernetes

안녕하세요, 뱅크샐러드 웹 프론트엔드 챕터의 민찬기입니다. gRPC…

올리브영 온라인몰의 전시, 그리고 백엔드 여정
올리브영
올리브영 온라인몰의 전시, 그리고 백엔드 여정

안녕하세요. 올리브영 스토어전시 스쿼드에서 백엔드 개발을 하고 있는 복스뮤직 입니다. 올리브영 전시에도 여러 스쿼드가 존재하지만, 스토어전시는 주로 메인, 홈, 오특 등 GNB(Global Navigation Bar…

신규 재고 시스템 구축을 위한 개발 여정
올리브영
신규 재고 시스템 구축을 위한 개발 여정

BULK 처리 Write에 집중해서 개선해보기
마켓컬리
BULK 처리 Write에 집중해서 개선해보기

애플리케이션, DB 모두 행복한 BULK 처리

쿠폰 발급 RabbitMQ도입기
올리브영
쿠폰 발급 RabbitMQ도입기

안녕하세요. 쿠폰 스쿼드 포덕입니다. 지난 시간엔 어푸님께서 Sync 쿠폰 발급에서 Async로 전환하며 Redis Worker에 대해 포스팅 해주셨습니다. 이번 시간엔 지난 Redis Worker에서 Rabbit MQ…

Redis Pub/Sub을 활용한 쿠폰 발급 비동기 처리
올리브영
Redis Pub/Sub을 활용한 쿠폰 발급 비동기 처리

안녕하세요.️ 쿠폰스쿼드 백엔드 개발자 어푸입니다! 쿠폰 스쿼드에서는 올리브영을 방문하시는 많은 고객님이 안정적으로 쿠폰 서비스를 받으실 수 있도록 개선 작업을 진행하고 있습니다. 지난번 레이 님이 올리브영 쿠폰 발급 개선 이야기를 통해 Redis…

제목은 Spring Session 도입기로 하겠습니다. 근데 이제 Redis를 곁들인
줌 인터넷
제목은 Spring Session 도입기로 하겠습니다. 근데 이제 Redis를 곁들인

안녕하세요. 줌인터넷 핀테크개발팀 김민수입니다. 사내에서는 문제를 개선하고 지속 가능한 서비스를 만들기 위한 많은 노력을 하고 있습니다. 회원 서비스의 세션 저장소를 교체하면서 Spring Session을 도입한 경험을 공유합니다. 이런 분들이 읽으면 더 도움이 됩니다. 분산 환경에서 세션 동기화 문제를 해결하고자 하는 분 Spring Session을...

[여기보기] 적에게 내 WAS의 디렉터리와 파일을 알리지 말라, WAS 디렉터리 인덱싱 및 상위 디렉터리 접근 제한
넷마블
[여기보기] 적에게 내 WAS의 디렉터리와 파일을 알리지 말라, WAS 디렉터리 인덱싱 및 상위 디렉터리 접근 제한

공개 배포해야 하는 파일이 많은 서비스가 아니라면 WAS의 디렉터리 구조는 노출하지 않아야 합니다. 이번에는 WAS에서 디렉터리 인덱싱 취약점을 노출하지 않는 방법과 노출해야 하더라도 상위 디렉터리에 이동 제한을 설정해 접근할 수 없게 만드는 방법을 살펴보겠습니다. The post [여기보기] 적에게 내 WAS의 디렉터리와 파일을 알리지 말라, WAS...

풀필먼트 입고 서비스팀에서 분산락을 사용하는 방법 - Spring Redisson
마켓컬리
풀필먼트 입고 서비스팀에서 분산락을 사용하는 방법 - Spring Redisson

어노테이션 기반으로 분산락을 사용하는 방법에 대해 소개합니다.

[장애회고] ORM(JPA) 사용 시 예상치 못한 쿼리로 인한 HikariCP 이슈
사람인
[장애회고] ORM(JPA) 사용 시 예상치 못한 쿼리로 인한 HikariCP 이슈

안녕하세요. IT연구소 서비스개발팀 빌링파트 최재우입니다. 2022년 12월 7일 사람인의 결제 및 상품을 담당하는 Order API 서버의 CPU, Memory과부하가 지속되는 이슈가 발생했었습니다. 해당 이슈를 통해 발생한 에러들과 원인이 무엇인지 분석한 것에 대해 공유해 드리고자 합니다. 원인이 무엇이었나 저희 Order API는 Spring B...

무형상품 서비스에 캐시 적용하기
올리브영
무형상품 서비스에 캐시 적용하기

📖 Table of Contents 포스팅의 목차입니다. 캐시는 무엇인가요? LOCAL 캐시와 GLOBAL 캐시 ElastiCache 무엇인가요?? 캐시 적용하기! 안녕하세요. 올디브에서 Back-End…

Spring Cloud Stream 재시도 구현하기
사람인
Spring Cloud Stream 재시도 구현하기

안녕하세요. 사람인 HR IT 연구소 IT 전략팀 이창섭입니다. 저희 팀에서는 사람인 내부의 DB Trigger를 제거하고 이를 비동기 분산처리로 변환하는 작업을 진행했습니다. 그 중 Kafka를 통하여 메세지 처리 과정 중에 일부 문제가 있었던 부분을 Spring Cloud Stream을 이용하여 재시도 로직을 구현했습니다. 그 기술적인 구현 과정을...

Scala for Game Server Development
데브시스터즈
Scala for Game Server Development

Why Scala is a great match for developing game servers.