카테고리 없음

2024 제 11회 소프트웨어(SW) 개발보안 경진대회 후기

kchabin 2024. 10. 1. 03:09

결과적으로 우수상을 수상했다!


작년엔 하는지 모르고 놓쳤는데, 올해 학교 동기들과 함께 P1B2라는 팀을 이뤄서 참가하게 되었다.

사실 대회가 끝난지 한 달이 넘었지만 그동안 엄마 병원 모시고 다니느라 미처 정리하지 못했던 짧은 개발 과정을 남겨보려고 한다.

기획

주제는 '안전한 디지털 세상' 이었다. 꽤 포괄적이라 일단 코드에 보안 요소를 잘 넣은 서비스를 만들면 어떨까 생각하게 되었고, 평소에 친구와 식단 공유할 수 있는 서비스를 만들면 어떨까 생각해두고 있던 게 있었어서 아이디어 초안을 제시하고 필요한 요소들을 팀원들과 함께 얘기한 다음 기획 파트를 맡은 친구가 구체화시켜 주었다.

피그마 프로젝트를 만들고 팀원들과 서비스에 들어가야 할 기능들을 직접 그려보며 기획했다.

기술 스택

Backend : Django
Frontend : Flutter
UXUI : Figma
DB : MySQL

Java, Python 둘 중 하나만 선택해서 개발 언어로 사용할 수 있는 규칙이 있었다.
Java spring 위주로 공부하던 사람인데, 동기들은 보안을 위주로 공부해서 그런지 Python 경험이 더 많았기에 Django 프레임워크를 사용해서 개발하게 되었다.
프론트엔드는 html, js 등을 사용하는 것보다 일단 백엔드 코드와 보안 코드를 잘 구현하는데 집중하는 게 더 좋을 것 같았다. 백엔드는 좀 해봤지만 프론트는 거의 경험이 전무했기 때문에... 작년에 flutter로 화면구현했던 걸 팀원들에게 설명하고 프론트엔드 구현에 사용하기 좋다는 걸 어필했다.
웹서비스를 생각하긴 했지만 UXUI는 모바일에 가까웠기 때문에 더욱 flutter가 적합할 것 같았다.

DB는 해커톤 형태의 대회인만큼 실제로 운영할 수 있을 정도의 코드는 작성하기 어려울 것 같아(그리고 맞았다) 로컬에서 MySQL을 사용했다.
openlab kotlin springboot 스터디하면서 docker-compose.yaml 파일을 작성해서 docker에 mysql 컨테이너를 띄우는 방법을 배워서 이번에도 연동하는 방법을 찾아서 했다.

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'fithubdb',#db명
        'USER': 'root', #db user 이름
        'PASSWORD': '', #db password
        'HOST': 'localhost', #나중에 aws로 연결
        'PORT': '3306', #mysql 포트번호
    }
}
SECRET_KEY = '시크릿키'

최근에 졸업프로젝트 하는데 포트 겹쳐서 꺼버렸다.

JWT 인증 구현, 커뮤니티 기능 구현

서비스의 주요 기능은 크게 3가지로 나뉜다. 자잘한 세부 기능에 신경 쓰기보다 일단 회원가입/로그인이 가능하게 하고, 캘린더에 내가 먹은 음식 칼로리 계산, 운동 등을 기록할 수 있게 하고 사용자들의 커뮤니티를 구현하자는 목표를 세우고 개발했다.

  • user 회원가입/로그인 기능
  • 내가 먹은 오늘의 음식과 운동 등을 공유하는 calander 기능
  • 요리 레시피, 전문가의 건강 꿀팁 및 조언 등을 볼 수 있는 community 기능

난 원래 community 담당이었지만 user 기능과 꽤 밀접하게 연결되어있다보니 user 파트를 맡은 친구가 구현해둔 코드를 가져와서 JWT 토큰 방식을 추가 구현했다.

https://ksw4060.tistory.com/137 <- 이 분의 블로그를 참고해서 개발했는데, 이 분이 아니었다면 api 테스트는 꿈도 꾸지 못했을 것

JWT 인증 방식

HTTP가 무상태성(stateless) 프로토콜로 누가 요청을 했는지, 인증된 클라이언트인지 확인할 수 없다.

세션 기반 인증은 서버 측에 클라이언트의 접속 상태를 저장하는 방식이다.

토큰 기반 인증은 서버-클라이언트 구조에서 서버가 클라이언트의 상태를 가지고 있지 않아 서버의 수평적 확장에 이점이 있다. 여러 대의 서버가 요청을 처리할 때, 세션 기반 인증 방식은 세션 불일치 문제를 겪을 수 있기 때문이다. 이를 해결하기 위해 Stickt session, Session Clustering, 세션 스토리지 외부 분리 등이 필요하다고 한다.

1. 무상태성 활용

2. 높은 확장성

출처 : https://www.akamai.com/ko/blog/security-research/owasp-authentication-threats-for-json-web-token

  • 헤더, 페이로드, 시그니처 3개의 파트로 구성되어있다.
  • 시그니처에는 헤더와 페이로드를 base64로 인코딩한 값과 256비트 길이의 시크릿키가 있다. 시그니처로 토큰의 위변조 여부를 확인하는데 사용된다.
    • 헤더와 페이로드는 복호화 및 조작이 가능하나 Signature는 secret_key가 유출되지 않는 이상 복호화할 수 없다.
  • 페이로드에는 클라이언트와 서버가 사용할 데이터를 저장하는데, 암호화되지 않고 base64로 인코딩한 값이라 누구나 디코딩해 데이터를 확인할 수 있다 따라서 페이로드에는 중요한 정보를 포함해선 안된다.
  • 토큰 자체의 데이터 길이가 길어, 인증 요청이 많아질 수록 네트워크 부하가 심해질 수 있다.
    • 인증 요청에 대한 분산을 어떻게 시켜야 할지 더 공부해야겠다는 생각이 들었다.

JWT에도 보안 취약점이 있어 안전한 사용이 필요한데, 우린 많이 사용되는 방식인 Access Token, Refresh Token을 발급하여 토큰의 만료 기간을 정해서 토큰이 탈취되더라도 악의적으로 사용되는 것을 막도록 했다.

 'ACCESS_TOKEN_LIFETIME': timedelta(minutes=60),
 'REFRESH_TOKEN_LIFETIME': timedelta(days=1),

Access token은 짧게, Refresh token은 길게 써야 토큰 탈취의 위험을 줄일 수 있다 정도로만 생각했는데, 오늘 후기 정리하면서 다시 공부해보니까 보안 상으로 좀 더 신경쓰지 못하고 개발했다는 생각이 들었다.

Postman API 테스트 : 게시물 수정

게시물 수정 시 권한이 있어야 수정할 수 있기 때문에 토큰 발급 api 구현 성공 후 이에 대한 테스트도 진행했다.

Bearer 토큰 형태로 Authorization 헤더에 담아 전송

커뮤니티 API

https://github.com/momentofluv/P1B2_FitHUB/blob/kchabin/community/views.py

[P1B2_FitHUB/community/views.py at kchabin · momentofluv/P1B2_FitHUB

Contribute to momentofluv/P1B2_FitHUB development by creating an account on GitHub.

github.com](https://github.com/momentofluv/P1B2_FitHUB/blob/kchabin/community/views.py)

views.py에 API 관련 메서드를 작성했다.

urlpatterns = [
    path('', views.ArticleView.as_view(), name='article_list'),
    path('<int:article_id>/', views.ArticleDetailView.as_view(), name='article_detail'),

    path('<int:article_id>/comment/', views.CommentView.as_view(), name='comment_view'),
    path('<int:article_id>/comment/<int:comment_id>/', views.CommentDetailView.as_view(), name='comment_detail_view'),
    path('<int:article_id>/like/', views.LikeView.as_view(), name='like_view'),
]

Article과 관련된 여러 엔드포인트를 정의하는 코드

커뮤니티 클래스

https://github.com/momentofluv/P1B2_FitHUB/blob/kchabin/community/models.py

[P1B2_FitHUB/community/models.py at kchabin · momentofluv/P1B2_FitHUB

Contribute to momentofluv/P1B2_FitHUB development by creating an account on GitHub.

github.com](https://github.com/momentofluv/P1B2_FitHUB/blob/kchabin/community/models.py)

models.py에 community article(게시글), comment(댓글) 클래스를 만들어서 DB 스키마를 정의했다.

class Article(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE, default=4)

    article_id = models.AutoField(primary_key=True, unique=True)
    content = models.TextField(null=True, blank=True)
    image = models.ImageField(blank=True, null=True, upload_to='')
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    likes = models.ManyToManyField(User, related_name='like_articles')

user : User 클래스의 외래키로, 사용자가 마이페이지에서 내가 작성한 게시글을 확인할 수 있도록 하기 위해 다대일 연관관계를 설정했다.

article_id : 게시글 id

content : 게시글의 본문

image : 사용자가 업로드한 image, blank=True로 설정해서 이미지 없이도 글을 게시할 수 있게 했다.

created_at : 게시글 생성 일시

updated_at : 게시글 수정 일시

likes : 게시글 좋아요 수. User와 다대다 연관관계를 가진다.

좋아요 수를 구현할 때 다대다 연관관계를 django에서는 꽤 간단하게 구현한다는 점이 신기했다. JPA 연관관계 공부할 때는 객체로는 다대다 관계를 표현할 수 있지만, RDB는 정규화된 2개의 테이블로 다대다 관계를 표현할 수 없어 중간 테이블이 필요하다고 배웠는데, django에서는 ManyToManyField를 지원한다.

개발자가 중간 테이블을 명시적으로 생성하지 않아도 아래처럼 자동으로 중간 테이블이 생성된다.

  • 첫 번째 행: user_id가 2인 사용자가 article_id가 6인 글을 좋아함.
  • 두 번째 행: user_id가 4인 사용자가 article_id가 6인 글을 좋아함.
  • 세 번째 행: user_id가 6인 사용자가 article_id가 6인 글을 좋아함.

진짜 후기

사실 지금까지 개발 대회를 나간 경험이 대부분 디자인, 화면 개발 위주로 진행해본 경험들 뿐이었다. 백엔드로 제대로 진로를 정하고 나서 프로젝트 경험이 너무 부족한 것 같아 Python으로 개발하게 되더라도 백엔드 개발 경험을 갖고 싶다는 생각에 간절한 마음으로 참가하게 된 대회였다.
계속 에러의 연속이었고, django를 단시간에 어느정도 이해하고 개발하는 것도 어려웠고, 플러터와 연동하는 과정도 답답하긴 했지만 DB에 데이터가 쌓이는 걸 보고 Postman에서 api 테스트가 성공하고 하는 과정에서 환희와 말로 설명하기 어려운 쾌감이 있었다. 내가 이래서 개발을 못 끊지.. 라는 생각..

대회 막바지에 어렵게 연결한 플러터에서 비록 로컬 환경이라도 화면에 데이터가 뜨는 걸 보는 순간 너무 감격스러웠다. 문자 인코딩이 이상하게 나오더라도 내가 개발한 대로 화면에 누르면 데이터가 뜨는 게 너무 신기하고 즐겁게 느껴졌다.
백엔드는 눈에 안 보인다고 재미없다고 한 사람 누구냐. 플러터와 안드로이드로 화면 개발을 먼저 접했던 사람으로서(개인의견입니다) 화면 개발은 내가 디자인한대로 동작하는 걸 보는게 뿌듯하고 기분 좋았지만 진짜 데이터는 아니라는 생각에 뭔가 좀 부족한 기분을 많이 느꼈었다. 어찌보면 그런 감정을 느꼈을 때부터 백엔드 공부하는 건 정해진 운명이었을수도..

사실 결과만 놓고보면 많이 부족했기에 수상은 꿈도 꾸지 못하고 있었는데 우수상으로 우리 팀 이름이 불리는 순간 정말 귀를 의심했다. 화면 개발도 정말 기본적으로만 해서 피그마로 기획한 디자인의 반의 반도 하지 못했고 댓글 기능이나 한글 깨짐 등 문제가 좀 많았는데 수상한 걸 보면 다른 팀원들이 최선을 다해줬기 때문인 것 같다.
보안 파트 담당한 팀원이 로그인 패스워드 암호화 코드 작성해주고, 캘린더 담당한 팀원이 최대한 기획 디자인에 맞게 화면을 잘 구현해주고 까다로울 수 있는 캘린더 기능의 백엔드 로직을 잘 짜준 덕분이 아닐까 생각한다.

결과는 좋았지만 개선할 점이 여전히 있다. 졸업 프로젝트 하면서 시간이 좀 되면 스프링부트로 리팩토링 해보려고 하고있다. kotlin으로 할지 java로 할지 아직 못 정했는데 lombok이랑 jpa 써서 좀 대중적인 자바 스프링 개발을 해볼지 아니면 졸업프로젝트 개발처럼 open api 문서 작성하고 code gen해서 kotlin으로 jpa 사용할지 고민중이다. kotlin이 jpa 쓸 때 좀 불편하긴 한데 그래도 요즘 좋아하는 언어이기도 해서 좀 더 kotlin으로 많이 써보고 싶긴하다. 아니면 python -> java -> kotlin 순으로 프로젝트를 진행해봐도 좋을 것 같다.

 

 

제출했던 시연영상