Skip to content

Commit ff5085e

Browse files
committed
Problem-Solving and Key Considerations 추가
1 parent 9045f45 commit ff5085e

File tree

1 file changed

+61
-7
lines changed

1 file changed

+61
-7
lines changed

README.md

Lines changed: 61 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,27 @@
11
# jwt-redis-ip-protection-backend
22

3-
> Spring Security + JWT + Redis에 IP 검증을 이용한 보안 필터 개발하기 <br>
4-
> JWT 토큰만으로 보안을 지킬 수 있는가 의문을 시작으로 고도화 해보는 프로젝트 <br>
3+
> > #### 배경
4+
> >
5+
> > 사내 CRM 프로젝트에서 기존 서버 사이드 JSP 프로젝트를 Cilent를 Vue로 전환하면서 Server가 stateless 형식을 가지고 의존성을 분리 시키는 작업이 필요했다.
6+
> > 일반적인 Spring Security, JWT, Redis 방식으로 보안성을 증가 시키고 분리시키는 기획을 가졌지만 항상 Token 탈취 우려가 있어 Client IP 까지 추가적으로 넣어 해당 컴퓨터로 로그인한 사용자만 우리 서버를 이용하는 방식으로 전략 짜보았다. 이를 전면 개발해보고 개선 사항을 기록한다.
7+
> > Spring Security + JWT + Redis에 IP 검증을 이용한 보안 필터 개발하기.
8+
> > JWT 토큰만으로 보안을 지킬 수 있는가 의문을 시작으로 고도화 작업.
59
>
610
> > #### 목표
711
> >
812
> > Client가 JWT 토큰을 발급 시 XSS로 인한 토큰 탈취를 방지하기 위한 Cookie HTTPS 설정 추가 ( 브라우저에서 토큰 접근 불가 )
913
> > Client가 JWT 토큰을 탈취 당할 시, 토큰에 저장된 IP와 Request IP를 검증을 통해 CSRF를 방지 고도화
1014
15+
## 목차
16+
17+
1. [분석](##-▶-분석)
18+
2. [설계](##-▶-설계)
19+
3. [구현](##-▶-구현)
20+
4. [테스트](##-▶-테스트)
21+
5. [Problem-Solving and Key Considerations](##-▶-Problem-Solving-and-Key-Considerations)
22+
23+
---
24+
1125
## ▶ 분석
1226

1327
<details><summary>Spring Security 프로세스 흐름도 ( 참고용 )</summary>
@@ -215,6 +229,7 @@ sequenceDiagram
215229
- 등록이 없다면 그 뒤에 AnonymousAuthenticationFilter, ExceptionTranslationFilter, AuthorizationFilter에서 요청이 거부되고 403 혹은 401 에러를 반환한다.
216230
- SecurityContextHolder은 Spring Security 전역 객체라 생각해야한다. 다음 필터들이 인증된 객체를 가지고 security 프로세스를 흘러가기에 요청 쓰레드 마다 인증이 되었다면 인증 객체를 꼭 등록이 필요하다
217231
- **Spring Security 공식 문서에도 나와 있지만 SecurityContextHolder는 기본적으로 스레드 로컬 변수를 사용하여 보안 컨텍스트를 관리하는데, SecurityContextHolder.getContext() 메서드를 사용하여 보안 컨텍스트를 설정할 때, 동일한 보안 컨텍스트를 여러 스레드에서 동시에 접근하고 수정할 가능성이 있기에 꼭 한 쓰레드 마다 별도에 SecurityContext 생성하고 등록 해주자**
232+
218233
```Java
219234
// 인증 객체 발급
220235
AuthenticationToken authenticationToken = jwtTokenProvider.getAuthenticationToken(accessToken);
@@ -225,8 +240,6 @@ context.setAuthentication(authenticationToken);
225240
// SecurityContextHolder 인증 객체 저장
226241
SecurityContextHolder.setContext(context);
227242
```
228-
229-
230243

231244
### LogOut 구현
232245

@@ -239,16 +252,57 @@ SecurityContextHolder.setContext(context);
239252
## ▶ 테스트
240253

241254
### Authentication 프로세스 통한 토큰 발급과 Redis에 저장
255+
242256
![security_test_1](https://github.com/silberbullet/jwt-redis-ip-protection-backend/assets/80805198/aeda1076-396c-4ecb-a160-2687c7215c1e)
243257

244258
#### Redis
259+
245260
![security_test_2](https://github.com/silberbullet/jwt-redis-ip-protection-backend/assets/80805198/7b44b57c-8e99-4577-b6b9-a50be425baf3)
246-
*****
261+
262+
---
247263

248264
### Jwt Verification프로세스 토큰으로 /ping API 접근
265+
249266
![security_test_3](https://github.com/silberbullet/jwt-redis-ip-protection-backend/assets/80805198/0ceaee9a-a69e-46fc-b1d1-f65b9cf17689)
250-
*****
251267

252-
### LogOut 프로세스 통한 토큰 초기화
268+
---
269+
270+
### LogOut 프로세스 통한 토큰 초기화
271+
253272
![security_test_4](https://github.com/silberbullet/jwt-redis-ip-protection-backend/assets/80805198/899e503a-c715-4712-b948-0db779039949)
254273

274+
## ▶ Problem-Solving and Key Considerations
275+
276+
1.**SecurityContextHolder 다루기**
277+
278+
- Spring Security 공식 문서에도 나와 있지만 SecurityContextHolder는 기본적으로 스레드 로컬 변수를 사용하여 보안 컨텍스트를 관리하는데, **SecurityContextHolder.getContext() 메서드를 사용하여 보안 컨텍스트를 설정할 때, 동일한 보안 컨텍스트를 여러 스레드에서 동시에 접근하고 수정할 가능성이 있기에 꼭 한 쓰레드 마다 별도에 SecurityContext 생성하고 등록 해주자**
279+
280+
```Java
281+
// 인증 객체 발급
282+
AuthenticationToken authenticationToken = jwtTokenProvider.getAuthenticationToken(accessToken);
283+
284+
SecurityContext context = SecurityContextHolder.createEmptyContext();
285+
context.setAuthentication(authenticationToken);
286+
287+
// SecurityContextHolder 인증 객체 저장
288+
SecurityContextHolder.setContext(context);
289+
```
290+
291+
2.**만료된 JWT 토큰에서 Client IP 가져오기**
292+
293+
- 기획 했던 것 중 제일 핵심은 AccessToken에서 Client IP와 Request객체 요청 IP를 비교하여 Servlet으로 넘기는 과정이다. 이에 JwtTokenFilter에서 JWT 토큰을 파싱 후 payload에서 로그인 한 Client IP를 가져오는 방식이 필연적이였다. **만약 만료가 된 JWT토큰을 파싱하게 된다면 ExpiredJwtException 예외 처리를 하게 된다** 이에 예외 처리에 따라 Handling을 하는게 정말 중요했다. 만료 된 토큰 일지라도 요청을 보낸 컴퓨터가 인증을 했던 사용자 인지 확인을 꼭 해야했다.
294+
295+
```Java
296+
private Claims getPayloadFromJwtToken(String token) {
297+
Claims claims = null;
298+
try {
299+
claims = Jwts.parser().verifyWith(key).build().parseSignedClaims(token).getPayload();
300+
} catch (ExpiredJwtException e) {
301+
log.debug("ExpiredJwtException Token");
302+
claims = e.getClaims();
303+
} catch (Exception e) {
304+
throw e;
305+
}
306+
return claims;
307+
}
308+
```

0 commit comments

Comments
 (0)