Skip to content

Commit 7002222

Browse files
committed
docs: SC(5-2)
1 parent 429b72f commit 7002222

20 files changed

+297
-0
lines changed

_posts/2025-10-01-SC(5-2).md

Lines changed: 297 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,297 @@
1+
---
2+
title: "[Security] Secure Coding(5-2) - OAuth2"
3+
4+
categories: [Security, Secure Coding]
5+
tags:
6+
- [Security, Cyberattacks, 보안, 시큐어 코딩, OAuth2, Access Token, Refres Token]
7+
toc: true
8+
toc_sticky: true
9+
10+
date: 2025-10-01
11+
last_modified_at: 2025-10-01
12+
---
13+
>🔒 시큐어 코딩 수업 정리
14+
15+
## OAuth2
16+
📚**<span style="color: #008000">OAuth2</span>**: `사용자 인증(Authentication)``권한 부여(Authorization)`**분리**하여, **제3자 애플리케이션이 사용자의 자원(Resource)에 안전하게 접근**할 수 있도록 지원하는 프로토콜
17+
* 복잡한 암호화나 서명 없이도 **HTTPS 연결**만으로 액세스 토큰을 주고받아 권한을 위임할 수 있다
18+
* 가볍고, 애플리케이션 환경에 구애받지 않는 유연성을 확보, 구현 난이도를 크게 낮춤
19+
20+
* 예: 어떤 쇼핑몰에서 ‘구글 계정으로 로그인’ 버튼을 눌렀을 때 쇼핑몰 사이트가 여러분의 구글 비밀번호를 알고 있지 않지만, 구글 인증 서버가 사용자를 대신해 발급해 준 액세스 토큰만 전달받아, 이메일 주소나 프로필 정보를 가져오는 흐름
21+
22+
### OAuth2 구성 요소
23+
24+
![alt text](OAuth2comp.png)
25+
26+
![alt text](OAuth2comp1.png)
27+
28+
#### Client Application (클라이언트)
29+
* **역할**: 사용자를 대신해 리소스에 접근하려는 앱/웹사이트
30+
* **식별정보**: `Client ID` (공개), `Client Secret` (비밀 키)
31+
32+
**주요 동작**:
33+
* 권한 서버에 인증 요청
34+
* Authorization Code 받음
35+
* Access Token 요청 및 수신
36+
* API 호출
37+
38+
#### Resource Owner (리소스 소유자)
39+
* **역할**: 실제 데이터의 주인 = 사용자(당신)
40+
41+
**주요 동작**:
42+
* 권한 서버의 로그인 창에서 인증
43+
* 클라이언트가 요청한 권한에 동의/거부
44+
45+
* **중요**: 비밀번호를 클라이언트에 주지 않고, 권한 서버에만 입력
46+
47+
#### Authorization Server (권한 서버)
48+
* **역할**: 사용자 인증 + 토큰 발급
49+
50+
**주요 동작**:
51+
* 사용자 로그인 확인
52+
* `Authorization Code` 발급 (임시 코드)
53+
* `Access Token` 발급 (짧은 유효기간, 실제 API 호출용)
54+
* `Refresh Token` 발급 (긴 유효기간, 토큰 갱신용)
55+
56+
* **검증**: Client ID/Secret 확인, Scope 검증
57+
58+
#### Resource Server (리소스 서버)
59+
* **역할**: 실제 데이터를 저장하고 API 제공
60+
61+
**주요 동작**:
62+
* Access Token이 유효한가?
63+
* Token의 scope에 이 API 접근 권한이 있는가?
64+
* Token이 만료되지 않았는가?
65+
66+
* **동작**: 검증 통과 시 → **데이터 반환**
67+
68+
---
69+
70+
### OAuth2 인증 흐름: Authorization Code Grant
71+
72+
![alt text](AuthorizationCodeGrant.png)
73+
74+
1. **사용자**: 애플리케이션에 서비스 접속 시도
75+
2. **애플리케이션**: 그 즉시 권한 서버의 인증 요청 페이지를 띄우도록 redirect
76+
3. **권한 서버**: redirect된 로그인 페이지에서 정보 입력 요청
77+
4. **사용자**: 로그인 정보 입력 및 권한 '허용'
78+
5. **권한 서버:** `Authirization code`, 즉 인가 코드를 발급하여 애플리케이션에 전달
79+
6. **애플리케이션:** 받은 인가 코드 기반으로 리소스 서버 액세스 토큰(`Access Token`) 요청을 권한 서버에 보낸다
80+
- 이때 Client Secret과 함께 인가 코드를 전송해서 자신을 인증
81+
7. **권한 서버:** 이를 검증하고 `Access Token`을 발급
82+
8. **애플리케이션:** 발급받은 `Access Token`으로 리소스 서버에 필요한 사용자 리소스 요청
83+
9. **리소스 서버:** 방금 발급 받은 `Access Token`이 맞는지 권한서버와 크로스 체크
84+
10. 유효하면 OK 응답 받음
85+
11. **리소스 서버**: 요청한 리소스 제공
86+
12. **애플리케이션:** 서비스 이용 화면 렌더링 후 사용자에게 서비스 제공
87+
88+
![alt text](AuthorizationCodeGrantseq.png)
89+
> 인증 흐름의 시퀀스 다이어그램
90+
91+
---
92+
93+
### OAuth2의 장단점
94+
**장점:**
95+
96+
1. **비밀번호 노출 위험 감소**
97+
* 사용자 비밀번호를 클라이언트에 전달 X(권한서버에서만 관리) → 보안성 향상
98+
2. **SSO (Single Sign-On) 지원**
99+
* 한 번의 로그인으로 여러 App에 접근 가능 → 편의성 향상
100+
3. **토큰 기반 인증**
101+
* Access Token, Refresh Token을 활용하여 세션 관리 및 권한 검증에 효과적
102+
* 유효기간을 짧게하여 토큰 탈취 피해 최소화
103+
4. **다양한 클라이언트 지원**
104+
* 여러 클라이언트에 유연하게 적용 가능, 확장성 향상
105+
5. **외부 인증 연동 용이**
106+
107+
### OAuth2의 단점
108+
**단점:**
109+
110+
1. **구현 복잡성**
111+
* 다양한 인증흐름과 토큰 관리가 복잡하여 잘못 구현하면 보안 취약점이 발생
112+
2. **클라이언트 비밀 관리 문제**
113+
3. **표준 해석 차이**
114+
* OAuth2는 표준 자체가 유연하게 되어있어서 연동 시스템 간 구현 차이 발생
115+
4. **추가 보안 고려 필요**
116+
* `PKCE`, 토큰 취소(`Token Revocation`), 타임스탬프/Nonce 등 추가 보안 대책이 없으면 리플레이 공격, 토큰 탈취 등의 위험 존재
117+
5. **서버 간 신뢰 관계 구성 필요**
118+
119+
* `PKCE`: 중간자 공격을 방어하는 방식 - 인가 코드 교환 보호
120+
* `Token Revocation`: 필요할 때 이전에 발급된 토큰 즉시 무효화 가능
121+
* `타임스탬프/Nonce`: 일회용 값 사용 - 과거 값 재사용 공격 무효화
122+
123+
### OAuth와 OAuth2 비교
124+
125+
![alt text](OAuthvsOAuth2.png)
126+
127+
---
128+
129+
## OAuth2 구현
130+
131+
#### 1. Google Cloud 가입
132+
https://cloud.google.com/apis?hl=ko
133+
134+
#### 2. 새 프로젝트 생성
135+
136+
![alt text](OAuth2create.png)
137+
138+
#### 3. 서비스 동의 및 시작
139+
140+
![alt text](OAuth2create1.png)
141+
142+
#### 4. 프로젝트 구성
143+
* 앱 정보 > 앱 이름: `OAuth2 Test`, 사용자 지원 이메일: 개인 메일 주소
144+
* 대상 > 외부 선택
145+
* 연락처 정보: 개인 메일 주소
146+
* '만들기' 버튼 선택
147+
148+
#### 5. 클라이언트 만들기
149+
150+
![alt text](OAuth2create2.png)
151+
152+
#### 6. 데이터 액세스 설정
153+
154+
![alt text](OAuth2create3.png)
155+
156+
#### 7. 대상 설정
157+
* 대상 > 테스트 사용자 > + `ADD USERS`에 본인 메일 주소 입력
158+
159+
#### 8. 설정 확인 및 테스트
160+
161+
![alt text](OAuth2create4.png)
162+
163+
* 아래 URL에 클라이언트 ID를 넣어 구글 로그인 창이 뜨는지 확인
164+
https://accounts.google.com/o/oauth2/auth?client_id=클라이언트ID&redirect_uri=http://localhost:8000/login/oauth2/code/google&response_type=code&scope=https://www.googleapis.com/auth/userinfo.email+https://www.googleapis.com/auth/userinfo.profile
165+
166+
#### 9. Authorization Code 발급
167+
* **Google OAuth API 등록** 필요
168+
* 사용자가 구글 로그인을 마치고 나면 `redirect_uri`에 Authorization Code를 응답해 줌
169+
170+
#### 10. LAB 환경에 소스 추가해서 OAuth2 연동 테스트
171+
172+
![alt text](OAuth2create5.png)
173+
> LAB 소스코드 application.properties 설정 추가
174+
175+
![alt text](OAuth2create6.png)
176+
177+
![alt text](OAuth2create7.png)
178+
179+
![alt text](OAuth2create8.png)
180+
181+
* 이제 LAB 코드를 실행시키고 앞서 시도해봤던 URL을 입력하여 구글 로그인 창이 나타나며, 계정을 선택하고 진행하면 STS 콘솔에 메시지가 제대로
182+
출력되면 성공
183+
184+
![alt text](OAuth2create9.png)
185+
186+
#### Access Token 발급
187+
188+
![alt text](OAuth2create10.png)
189+
> 기존 소셜로그인 메서드도 수정하고, getAccessToken 메서드도 추가
190+
191+
```java
192+
//Authorization Code를 사용하여 Access Token 요청
193+
//restTemplate.exchange()를 이용해 Google OAuth 서버와 통신
194+
195+
private String getAccessToken(String authorizationCode, String registrationId) {
196+
String clientId = env.getProperty("oauth2." + registrationId + ".client-id");
197+
String clientSecret = env.getProperty("oauth2." + registrationId + ".client-secret");
198+
String redirectUri = env.getProperty("oauth2." + registrationId + ".redirect-uri");
199+
200+
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
201+
params.add("code", authorizationCode);
202+
params.add("client_id", clientId);
203+
params.add("client_secret", clientSecret);
204+
params.add("redirect_uri", redirectUri);
205+
params.add("grant_type", "authorization_code");
206+
207+
HttpHeaders headers = new HttpHeaders();
208+
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
209+
210+
HttpEntity entity = new HttpEntity(params, headers);
211+
ResponseEntity<String> response = restTemplate.exchange(tokenUri, HttpMethod.POST, entity, String.class);
212+
213+
return objectMapper.readTree(response.getBody()).get("access_token").asText();
214+
}
215+
```
216+
217+
* Authorization code와 함께 HTTP 요청으로 보내서 액세스 토큰을 받아오는 것을 확인
218+
219+
#### Resource Server에서 유저정보 받기
220+
221+
![alt text](OAuth2create11.png)
222+
223+
```java
224+
//Access Token을 Authorization: Bearer 헤더에 포함하여 Google API 호출.
225+
//유저 ID, 이메일, 이름 등의 정보를 반환.
226+
227+
private JsonNode getUserResource(String accessToken, String registrationId) {
228+
String resourceUri = env.getProperty("oauth2." + registrationId + ".resource-uri");
229+
230+
HttpHeaders headers = new HttpHeaders();
231+
headers.set("Authorization", "Bearer " + accessToken);
232+
233+
HttpEntity entity = new HttpEntity(headers);
234+
return restTemplate.exchange(resourceUri, HttpMethod.GET, entity, JsonNode.class).getBody();
235+
}
236+
```
237+
238+
* 겟 유저 리소스라는 메서드를 추가
239+
* 메서드 코드를 보면, HTTP 헤더에 베어러라는 키워드를 넣고, 액세스 토큰을 넣음
240+
* 그 응답 값은 JSON 포맷으로 수신되므로, 아이디, 이메일, 닉네임 등을 적절히 추출하여 콘솔에 출력함
241+
242+
#### 유저 정보 토대로 계정 연동
243+
244+
![alt text](OAuth2create12.png)
245+
246+
```java
247+
// 컨트롤러
248+
MemberModel member = checkUserId(email);
249+
if (member == null) {
250+
throw new RuntimeException("User not found in local DB: " + email);
251+
}
252+
253+
// 서비스
254+
HttpSession session = request.getSession(true);
255+
session.setAttribute("userId", member.getUserId());
256+
session.setAttribute("userName", member.getUserName());
257+
response.setHeader("Set-Cookie", "JSESSIONID=" + session.getId() + "; Path=/; HttpOnly; Secure");
258+
```
259+
260+
#### 로그인 성공
261+
262+
![alt text](OAuth2create13.png)
263+
264+
---
265+
266+
## OAuth2 보안 위협 및 해결 방법
267+
268+
### OAuth2 구현 시 발생 가능한 취약점
269+
* `Redirect URI` 검증 미흡 - 중간에 공격자가 끼어듦
270+
* **State 파라미터** 미사용
271+
* State 파라미터: CSRF 방어에 사용하는 랜덤 토큰
272+
* `PKCE` 미적용
273+
* `PKCE(Proof Key for Code Exchange)`: 모바일 및 공용 클라이언트에서 Authorization 코드 탈취를 막기 위한 보안 기법
274+
275+
### OAuth2 주요 취약점 사례
276+
* **Authorization Code 탈취**
277+
* 중간자 공격(MITM)이나 오픈 리다이렉트 취약점을 이용해, 인증 코드가 탈취되어 부적절한 토큰 발급 위험
278+
* **Access Token 유출**
279+
* HTTPS 미적용, 불안전한 토큰 저장(예: 로컬 스토리지, 브라우저 캐시 등)으로 토큰 노출 가능
280+
* **토큰 재사용 및 리플레이 공격**
281+
* 만료되지 않은 토큰 또는 중복 요청 처리 미흡으로 인한 리플레이 공격, 부적절한 접근 권한 행사
282+
* **클라이언트 시크릿 관리 취약점**
283+
* 클라이언트 시크릿(Client Secret)이 공개 클라이언트에 노출될 경우, 악의적 사용자가 토큰 발급 시도 가능
284+
285+
---
286+
287+
### Access Token 유출 방지 전략
288+
* **안전한 전송 및 저장**
289+
* HTTPS/TLS 적용
290+
* 보안 쿠키 사용
291+
* **토큰 관리 정책 강화**
292+
* 짧은 유효기간
293+
* Refresh Token 도입
294+
* 토큰 취소 및 재발급
295+
* **애플리케이션 보안 강화**
296+
* XSS 방어: 입력값 검증, 인코딩 및 CSP(Content Security Policy) 적용
297+
* PKCE 사용: 공개 클라이언트의 경우, PKCE 도입으로 Authorization Code 탈취 방지
171 KB
Loading
231 KB
Loading
181 KB
Loading
56.4 KB
Loading
105 KB
Loading
134 KB
Loading
338 KB
Loading
262 KB
Loading
327 KB
Loading

0 commit comments

Comments
 (0)