-
Notifications
You must be signed in to change notification settings - Fork 0
[Suhhee] week9 미션 #108
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Suhhee] week9 미션 #108
Changes from all commits
3615243
98bf469
97ddf4a
3a2c484
d6221dc
ec75c49
7cff920
c535635
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,77 @@ | ||
| ## 1. 세션(Session)과 토큰(Token)의 차이 | ||
|
|
||
| ### 📊 비교 요약 | ||
| | 구분 | 세션 (Session) | 토큰 (Token) | | ||
| | :--- | :--- | :--- | | ||
| | **저장 위치** | 서버 (Server) 메모리나 DB | 클라이언트 (Client) 브라우저 등 | | ||
| | **상태 관리** | Stateful (서버가 로그인 상태 기억함) | Stateless (서버는 상태 기억 안 함) | | ||
| | **서버 부하** | 높음 (사용자 많아질수록 서버 터질 위험) | 낮음 (서버는 티켓 유효성만 검사) | | ||
| | **확장성** | 낮음 (서버 여러 대면 세션 공유 설정 복잡함) | 높음 (서버 개수 늘려도 문제없음) | | ||
| | **보안 통제** | 서버가 쥐고 있음 (해킹 의심 시 강제 로그아웃 가능) | 클라이언트가 쥐고 있음 (탈취당하면 만료 전까지 막기 힘듦) | | ||
| | **통신 크기** | 작음 (짧은 세션 ID 문자열만 주고받음) | 큼 (사용자 정보가 통째로 들어있어 무거움) | | ||
|
|
||
| --- | ||
|
|
||
| > 💡 **세션 : 서버 기반 인증 (Stateful)** | ||
| > | ||
| > 서버가 사용자의 로그인 상태를 메모리나 데이터베이스에 직접 저장하고 관리하는 구조입니다. | ||
| > | ||
| > * **작동 원리:** | ||
| > 1. 클라이언트가 로그인에 성공하면 서버는 서버 측 저장소(Session Store)에 해당 사용자의 데이터를 생성하고 고유한 `Session ID`를 발급함 | ||
| > 2. 서버는 이 `Session ID`를 HTTP 응답 헤더를 통해 클라이언트에게 전달 | ||
| > 3. 클라이언트는 이후 요청마다 쿠키에 `Session ID`를 담아 서버로 전송 | ||
| > 4. 서버는 전달받은 `Session ID`를 자신의 저장소에서 조회하여 유효성 검증 + 사용자 정보 가져옴 | ||
| > * **특징:** | ||
| > * **Stateful (상태 유지):** 서버가 클라이언트의 상태를 보관하고 있어야 됨 | ||
| > * **보안성:** `Session ID` 자체에는 정보가 없고 실제 정보는 서버에만 있어 안전하다. 탈취 시 서버에서 해당 세션을 강제 삭제하면 즉시 접근을 차단할 수 있음 (블랙리스트 관리) | ||
| > * **확장성(Scalability) 문제:** 사용자가 늘어나면 서버 메모리 부하가 커지고 서버를 여러 대로 확장할 경우 세션 정보를 공유하기 위해 별도의 세션 스토리지가 필요하거나 Sticky Session 설정이 필요함 | ||
|
|
||
| > 🪙 **토큰 : 클라이언트 기반 인증 (Stateless)** | ||
| > | ||
| > 인증 정보를 암호화된 문자열인 토큰 형태로 만들어 클라이언트가 저장하고 관리하는 구조입니다. | ||
| > | ||
| > * **작동 원리:** | ||
| > 1. 클라이언트가 로그인에 성공하면 서버는 사용자 식별 정보와 권한 등을 포함한 데이터(Payload)에 디지털 서명을 하여 토큰을 생성함 | ||
| > 2. 서버는 이 토큰을 클라이언트에게 반환하고 서버는 이 토큰을 저장하지 않음 | ||
| > 3. 클라이언트는 토큰을 로컬 스토리지나 쿠키에 저장하고 이후 요청마다 HTTP 헤더(`Authorization`)에 토큰을 담아 전송한다 | ||
| > 4. 서버는 전달받은 토큰의 서명을 비밀키로 복호화하여 위변조 여부를 검증한 뒤 서명이 유효하면 토큰 내부의 데이터를 신뢰하고 요청을 처리해준다 | ||
| > * **특징:** | ||
| > * **Stateless:** 서버는 클라이언트의 상태를 저장하지 않고 토큰 자체에 인증 정보를 포함한다 | ||
| > * **확장성 우수:** 서버가 상태를 저장하지 않으므로 서버를 무한정 늘려도 인증 처리에 문제가 없다는 이점이 있다 | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 토큰 방식이 서버 확장에 유리한 점은 맞지만, ‘무한정 늘려도 문제가 없음’은 운영 조건을 지나치게 단순화한 표현입니다. 서명 키 관리, 토큰 만료/재발급 정책, 블랙리스트 저장소, Refresh Token 저장 전략이 함께 맞아야 확장성이 안정적으로 확보됩니다. stateless 인증의 장점과 이를 보완하기 위해 필요한 상태 저장 요소를 구분해 정리하는 것을 권장합니다. |
||
| > * **보안 및 제어의 어려움:** 토큰이 탈취되면 유효기간이 만료될 때까지 서버에서 강제로 차단하기 어렵다는 문제가 생긴다 | ||
|
|
||
| --- | ||
|
|
||
| ## 2. 액세스 토큰(Access Token)과 리프레시 토큰(Refresh Token) | ||
|
|
||
| ### 🤝 공통점 | ||
| * **형식:** 주로 JWT 표준을 따르고 Header, Payload, Signature의 3부분으로 구성됨 | ||
| * **발급 주체:** 인증 서버가 사용자의 자격 증명을 확인한 후 발급함 | ||
| * **검증 방식:** 서버가 가진 비밀키 또는 공개키를 통해 서명의 무결성을 검증 | ||
|
|
||
| ### 🔍 차이점 | ||
| | 비교 항목 | 액세스 토큰 (Access Token) | 리프레시 토큰 (Refresh Token) | | ||
| | :--- | :--- | :--- | | ||
| | **핵심 목적** | 실질적인 리소스 접근(인가)<br>API 요청 시 인증 수단으로 사용 | 토큰 재발급 (인증 유지)<br>액세스 토큰이 만료되었을 때 새로운 토큰을 받기 위해 사용 | | ||
| | **유효 기간 (수명)** | **짧다** : 탈취되었을 때의 피해를 최소화시키기 위함 | **길다** : 사용자가 자주 로그인하지 않도록 편의성을 제공 | | ||
| | **전송 빈도** | **높음** : 서버로 보내는 모든 API 요청마다 헤더에 포함되어 전송 | **낮음** : 액세스 토큰이 만료되었을 때만 인증 서버로 전송 | | ||
| | **노출 위험도** | **높음** : 네트워크 통신이 잦으므로 탈취될 가능성이 상대적으로 높음 | **낮음** : 전송 횟수가 적고 보통 더 엄격한 보안 저장소에 저장됨 | | ||
| | **정보 포함량** | **많음** : 사용자 ID, 권한, 만료 시간 등 비즈니스 로직 처리에 필요한 정보를 포함함 | **적음** : 보통 사용자 식별자와 유효성 검증을 위한 최소한의 데이터만 포함하거나 단순 랜덤 문자열인 경우도 있다 | | ||
| | **DB 저장 여부** | **저장하지 않음 (Stateless)** : 서버는 서명만 검증 | **저장함 (선택적)** : 서버 DB에 저장하여 탈취 감지 시 관리자가 강제로 만료시킬 수 있도록 유연한 관리 | | ||
|
|
||
| > 🔒 **로그아웃 및 블랙리스트 관리** | ||
| > * 로그아웃 시 액세스 토큰을 블랙리스트로 올려서 재로그인 시 블랙리스트 조회 필터에서 조회해서 거를 수 있어야 됨 | ||
| > * 레디스나 캐시에 블랙리스트라는 목록을 추가해서 만료되지 않은 토큰을 올려둠 | ||
|
|
||
| --- | ||
|
|
||
| ## 3. OAuth 1.0과 OAuth 2.0의 차이 | ||
|
|
||
| ### 📊 비교 요약 | ||
| | 구분 | OAuth 1.0 | OAuth 2.0 | | ||
| | :--- | :--- | :--- | | ||
| | **보안 방식** | 매 요청마다 복잡한 암호화 서명 필요 | HTTPS (SSL/TLS) 암호화 통신에 의존 (단순함) | | ||
| | **토큰 형태** | 서명된 토큰 | Bearer 토큰 (복잡한 암호화 없이 그냥 문자열) | | ||
| | **개발 난이도** | 높음 (구현하다가 개발자들 많이 울었음) | 낮음 (현재 거의 모든 웹/앱의 표준) | | ||
| | **지원 환경** | 웹 서버 환경에 맞춰져 있음 | 모바일 앱, 데스크톱, 스마트 TV 등 다양하게 지원 | | ||
| | **인증 흐름** | 단일 흐름으로 통일 (유연성 부족) | 상황에 맞게 4가지 흐름(Grant Type)으로 나눠 제공 | | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| package com.study.UMC10.domain.auth.controller; | ||
|
|
||
| import org.springframework.stereotype.Controller; | ||
| import org.springframework.web.bind.annotation.GetMapping; | ||
| import org.springframework.web.bind.annotation.RequestMapping; | ||
|
|
||
| @Controller | ||
| @RequestMapping("/passkey") | ||
| public class TestController { | ||
|
|
||
| @GetMapping("/test") | ||
| public String test() { | ||
| return "/auth/index.html"; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,6 +11,9 @@ | |
| import io.swagger.v3.oas.annotations.Parameters; | ||
| import io.swagger.v3.oas.annotations.tags.Tag; | ||
| import lombok.RequiredArgsConstructor; | ||
| import com.study.UMC10.global.security.CustomUserDetails; | ||
| import org.springframework.security.core.annotation.AuthenticationPrincipal; | ||
|
|
||
| import org.springframework.web.bind.annotation.*; | ||
|
|
||
| @RestController | ||
|
|
@@ -21,7 +24,16 @@ public class UserController { | |
|
|
||
| private final UserService userService; | ||
|
|
||
| @Operation(summary = "마이페이지 API", description = "유저의 마이페이지 정보를 조회하는 API입니다.") | ||
| @Operation(summary = "로그인 API", description = "이메일과 비밀번호로 로그인하여 JWT 토큰을 발급받습니다.") | ||
| @PostMapping("/auth/login") | ||
| public ApiResponse<UserResponseDto.LoginResultDto> login( | ||
| @RequestBody UserRequestDto.LoginDto requestDto | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 로그인 요청 DTO를 그대로 받으면 이메일 형식, 비밀번호 공백 여부 같은 입력 계약이 컨트롤러 경계에서 검증되지 않습니다. |
||
| ) { | ||
| BaseSuccessCode code = UserSuccessCode.OK; | ||
| return ApiResponse.onSuccess(code, userService.login(requestDto)); | ||
| } | ||
|
|
||
| @Operation(summary = "마이페이지 API V1", description = "유저의 마이페이지 정보를 조회하는 API입니다.") | ||
| @PostMapping("/v1/users/me") | ||
| public ApiResponse<UserResponseDto.GetInfo> getInfo( | ||
| @RequestBody UserRequestDto.GetInfo dto | ||
|
|
@@ -30,6 +42,15 @@ public ApiResponse<UserResponseDto.GetInfo> getInfo( | |
| return ApiResponse.onSuccess(code, userService.getInfo(dto)); | ||
| } | ||
|
|
||
| @Operation(summary = "마이페이지 API V2", description = "JWT 토큰을 이용해 유저의 마이페이지 정보를 조회하는 API입니다.") | ||
| @GetMapping("/v2/users/me") | ||
| public ApiResponse<UserResponseDto.GetInfo> getInfoV2( | ||
| @AuthenticationPrincipal CustomUserDetails customUserDetails | ||
| ) { | ||
| BaseSuccessCode code = UserSuccessCode.OK; | ||
| return ApiResponse.onSuccess(code, userService.getMyInfoV2(customUserDetails.getUser())); | ||
| } | ||
|
|
||
| @Operation(summary = "회원가입 API", description = "새로운 유저를 등록하는 API입니다.") | ||
| @PostMapping("/auth/sign-up") | ||
| public ApiResponse<UserResponseDto.SignUpResultDto> signUp( | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,11 @@ | ||
| package com.study.UMC10.domain.user.repository; | ||
|
|
||
| public class UserLoginRepository { | ||
| } | ||
| import com.study.UMC10.domain.user.entity.UserLogin; | ||
| import com.study.UMC10.domain.user.enums.LoginType; | ||
| import org.springframework.data.jpa.repository.JpaRepository; | ||
|
|
||
| import java.util.Optional; | ||
|
|
||
| public interface UserLoginRepository extends JpaRepository<UserLogin, Long> { | ||
| Optional<UserLogin> findByLoginTypeAndSocialId(LoginType loginType, String socialId); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| package com.study.UMC10.global.config; | ||
|
|
||
| import org.springframework.context.annotation.Bean; | ||
| import org.springframework.context.annotation.Configuration; | ||
| import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; | ||
| import org.springframework.security.crypto.password.PasswordEncoder; | ||
|
|
||
| @Configuration | ||
| public class PasswordConfig { | ||
|
|
||
| @Bean | ||
| public PasswordEncoder passwordEncoder() { | ||
| return new BCryptPasswordEncoder(); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
JWT 서명 검증은 비밀키로 ‘복호화’하는 과정이라기보다, Header/Payload와 비밀키로 서명을 다시 계산해 Signature와 비교하는 과정으로 이해하는 것이 정확합니다. JWT 자체는 기본적으로 암호화가 아니라 Base64URL 인코딩과 서명으로 구성되므로, Payload에 민감 정보를 넣지 않는 이유까지 함께 정리하는 것을 권장합니다.