-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
9 changed files
with
437 additions
and
197 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,172 @@ | ||
--- | ||
|
||
title: SHORTS - AOP를 활용하여 사용자 인증/인가 로직 간소화 | ||
date: 2023-01-01 | ||
categories: [SHORTS] | ||
tags: [SHORTS] | ||
layout: post | ||
toc: true | ||
math: true | ||
mermaid: true | ||
|
||
--- | ||
|
||
# AOP를 활용하여 사용자 인증/인가 로직 간소화 | ||
|
||
## 왜 이런 과정이 필요했는지? | ||
|
||
기존 코드에는 특정 API 컨트롤러마다 사용자 인증 정보를 가져오는 로직이 반복되고있었다. | ||
|
||
컨트롤러에서 이에 대한 관심사를 해결하는 것 보다는 이를 분리하는게 더 역할에 맞다고 생각해서 이를 분리하기로 했다. | ||
|
||
--- | ||
|
||
## 구체적으로 어떻게 구현한건지? | ||
|
||
HTTP Connection을 맺고 있는 Thread의 ThreadLocal에 서버에서 직접 발급하고 데이터베이스에서 관리하는 사용자의 UUID를 등록된 인증 정보를 보관하도록 하고 | ||
|
||
이 인증 정보를 사용할 수 있는 로직을 전역적으로 선언하여 Spring 내부의 계층에서 자유롭게 사용할 수 있는 로직을 작성했다. | ||
|
||
그리고 이 로직을 사용할 수 있는 대상을 애노테이션으로 지정할 수 있게 하여 반복되어 등장하는 사용자 인증 정보를 꺼내는 로직을 제거했다. | ||
|
||
### Auth.kt | ||
|
||
```kotlin | ||
@Target(AnnotationTarget.FUNCTION) | ||
@Retention(AnnotationRetention.RUNTIME) | ||
annotation class Auth | ||
``` | ||
|
||
### AuthAspect.kt | ||
|
||
```kotlin | ||
@Aspect | ||
@Component | ||
class AuthAspect( | ||
private val httpServletRequest: HttpServletRequest, | ||
private val memberRepository: MemberRepository | ||
) { | ||
|
||
@Around("@annotation($SHORTS_PACKAGE)") | ||
fun memberId(pjp: ProceedingJoinPoint): Any { | ||
val memberId = resolveToken(httpServletRequest) | ||
?: throw ShortsBaseException.from( | ||
shortsErrorCode = ShortsErrorCode.E401_UNAUTHORIZED, | ||
"Request Header에 memberId가 존재하지 않습니다." | ||
) | ||
|
||
val member = memberRepository.findByUniqueId(memberId) | ||
?: throw ShortsBaseException.from( | ||
shortsErrorCode = ShortsErrorCode.E404_NOT_FOUND, | ||
resultErrorMessage = "존재하지 않는 유저입니다. memberId : $memberId" | ||
) | ||
|
||
AuthContext.USER_CONTEXT.set(member) | ||
return pjp.proceed(pjp.args) | ||
} | ||
|
||
private fun resolveToken(request: HttpServletRequest): String? { | ||
val bearerToken = request.getHeader(AUTHORIZATION) | ||
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(PREFIX_BEARER)) { | ||
return bearerToken.substring(7) | ||
} | ||
|
||
return null | ||
} | ||
|
||
companion object { | ||
private const val AUTHORIZATION = "Authorization" | ||
private const val PREFIX_BEARER = "Bearer " | ||
private const val SHORTS_PACKAGE = "com.mashup.shorts.common.aop.Auth" | ||
} | ||
} | ||
``` | ||
|
||
### AuthContext.kt | ||
|
||
```kotlin | ||
object AuthContext { | ||
|
||
val USER_CONTEXT: ThreadLocal<Member> = ThreadLocal() | ||
|
||
fun getMember(): Member { | ||
USER_CONTEXT.get()?.let { | ||
return USER_CONTEXT.get() | ||
} ?: throw ShortsBaseException.from( | ||
shortsErrorCode = ShortsErrorCode.E401_UNAUTHORIZED, | ||
resultErrorMessage = "인증 체크 중에 ThreadLocal 값을 꺼내오는 중에 문제가 발생했습니다." | ||
) | ||
} | ||
} | ||
``` | ||
|
||
--- | ||
|
||
## 사용자는 그러면 어떻게 자신의 UUID를 가지고 있는가? | ||
|
||
클라이언트의 로컬 스토리지나 내부 DB에 저장하여 매 요청마다 인증 헤더에 실어 보내야한다. | ||
|
||
--- | ||
|
||
## 해당 UUID가 탈취되었을 때의 문제점과 해결방안은? | ||
|
||
DB에서 탈취된 것으로 판단된 UUID를 제거하여 피해를 막는 사후조치를 해야할 것 같다. | ||
|
||
--- | ||
|
||
## 이 과정 자체가 그러면 토큰 기반 방식의 인증 방법일까? 세션 기반 방식의 인증 방법일까? | ||
|
||
세션 기반 인증 방식으로 볼 수 있을 것 같다. | ||
|
||
--- | ||
|
||
### 위 질의 응답에 관한 근거 | ||
|
||
사용자의 UUID를 서버에서 직접 발급하고 서버 내부에서 관리하는 것이므로 세션 기반 인증 방식에 가깝다고 할 수 있을 것 같다. | ||
|
||
하지만 서버 내부의 메모리에서 해당 인증 정보를 관리하는 것이 아닌 DB에 저장되어있는 내용을 관리하는 것이기 때문에 완전한 세션방식이라고 하기엔 조금 어려울 수도 있을 것 같다. | ||
|
||
세션 기반 인증에서는 서버 측에서 세션을 관리하고, 클라이언트에게 세션 ID를 부여하여 이를 사용자 식별에 활용한다. | ||
|
||
--- | ||
|
||
## 공격자로부터 클라이언트의 인증 정보가 탈취되었음을 서버측에서는 어떻게 알 수 있을까? | ||
|
||
- 클라이언트의 UUID가 이전에 없던 위치에서 사용되었거나, 단기간 내에 많은 요청이 발생하는 경우 이상행동으로 간주하여 해당 세션을 무효화한다. | ||
|
||
- 클라이언트의 로그인 위치를 기록하고, 동일한 UUID가 다른 지역에서 사용되는 경우 해당 세션을 무효화한다. | ||
|
||
--- | ||
|
||
## 토큰 기반 인증 방식 장/단점 | ||
|
||
- 장점 1. 확장성과 분산화 | ||
- JWT는 토큰을 생성하고 검증하는 키를 기반으로 동작하며, 토큰에 필요한 정보를 담을 수 있어서 서버 간에 토큰을 공유하거나 전달할 수 있어 확장성이 뛰어나고 분산 환경에서 사용하기 용이하다. | ||
|
||
- 장점 2. 상태 없음(Stateless) | ||
- 서버 측에서 토큰을 검증하고 필요한 정보를 추출하므로, 서버는 클라이언트의 상태를 저장할 필요가 없어 리소스가 절약 될 수 있다. | ||
|
||
- 장점 3. 유연한 사용자 권한 관리 | ||
- 토큰 내에 사용자 권한과 관련된 정보를 포함하여 사용자 권한 관리가 용이하며, 토큰의 내용을 이용하여 권한 검사를 수행할 수 있다. | ||
|
||
- 단점 1. 토큰 크기와 보안 | ||
- JWT는 탈취될 가능성이 있다. 중요한 정보를 토큰에 포함시키면 보안 문제가 발생할 수 있다. | ||
|
||
- 단점 2. 토큰 유효성 검증의 어려움 | ||
- 토큰이 변조되지 않았는지 확인하기 위해 서명을 검증해야 하기 때문에 서명 검증 과정이 추가로 필요하며, 이에 따른 복잡성이 발생할 수 있다. | ||
|
||
--- | ||
|
||
## 서버 측 세션(Session) 기반 인증 방식 장/단점 | ||
|
||
- 장점 1. 보안성 | ||
- 세션은 서버에 저장되므로 클라이언트에 노출되지 않는다. 토큰 기반 인증에 비해 보안성이 높다. | ||
|
||
- 장점 2. 세션 탈취 시 대처가능 | ||
- 세션을 사용하면 만료 시간을 쉽게 조절하고 조절할 수 있으며, 만료 시간이 지나면 자동으로 세션을 무효화시킬 수 있다. | ||
|
||
- 단점 1. 상태 유지 | ||
- 세션은 서버 측에서 상태를 유지해야 하므로, 서버의 메모리를 사용하게 되어 클라이언트가 많을 때 성능 저하가 발생할 수 있다. | ||
|
||
- 단점 2. 확장성 | ||
- 분산 환경에서 각 서버마다 발급하는 세션을 관리하기 위해 세션 클러스터를 운영해야하는 복잡성이 증가한다. |
Oops, something went wrong.