학습 요약
schedulev2
schedulev2 lv1~lv3
현재 진행사항
Schedulev2는 일정 생성까지, Writer는 회원가입, 모든 유저들 조회까지
클래스들도 많고 인터페이스도 많아서 여기에 코드를 다 올리기엔 지저분 할 것 같아서 깃허브 주소를 남기겠습니다. 원치 않게 lv1부터 lv3을 통틀어서 하고 있네요
깃허브 주소 -> https://github.com/byeongtaek12/ScheduleProject-Spring-JPA
Spring 숙련 - 2주차
Cookie 1강
Cookie
사용자의 웹 브라우저에 저장되는 정보로 사용자의 상태 혹은 세션을 유지하거나 사용자 경험을 개선하기 위해 사용
Cookie를 사용하는 이유
- HTTP는 Stateless, Connectionless 특성을 가지고 있음
- Client가 재요청시 Server는 이전 요청에 대한 정보를 기억하지 못함
- 로그인과 같이 상태를 유지해야 하는 경우가 발생
- Request에 사용자 정보를 포함하면 해결
- 로그인 후에는 사용자 정보와 관련된 값이 저장되어 있어야 함
- 브라우저를 완전히 종료한 뒤 다시 열어도 사용자 정보가 유지되어야 함
로그인 성공시 응답

로그인 이후 요청

- 로그인 이후에는 모든 요청마다 Request Header에 항상 Cookie 값을 담아서 요청
- 네트워크 트래픽이 추가적으로 발생
- 최소한의 정보만 사용해야 함
서버에서는 HTTP 응답 헤더에 Set-Cookie 속성을 사용해 생성하고 설정할 수 있음
Set-Cookie
- Server에서 Client로 Cookie 전달(Response Header)
Cookie
- Client가 Cookie를 저장하고 HTTP 요청시 Server로 전달(Request Header)
Response
java
set-cookie:
**sessionId**=abcd;
**expires**=Sat, 11-Dec-2024 00:00:00 GMT;
**path**=/;
**domain**= ~.kr;
**Secure**
-
Cookie의 생명주기
- 세션 Cookie
- 만료 날짜를 생략하면 브라우저 완전 종료시 까지만 유지(
Default)
expires, max-age 가 생략된 경우
- 브라우저를 완전 종료 후 다시 페이지를 방문했을 때 다시 로그인을 해야 함
- 영속 Cookie
- 만료 날짜를 입력하면 해당 날짜까지 유지한다.
expires=Sat, 11-Dec-2024 00:00:00 GMT;
max-age=3600 (second, 3600초는 한시간. 60 * 60)
-
Cookie의 도메인
- 쿠키가 아무 사이트에서나 생기고 동작하면 안 됨
domain= ~.kr
domain= ~.kr를 지정하여 쿠키를 저장
dev.~.kr와 같은 서브 도메인에서도 쿠키에 접근
- domain을 생략하면 현재 문서 기준 도메인만 적용
-
Cookie의 경로
- 1차적으로 도메인으로 필터링 후 Path가 적용
- 일반적으로
path=/ 루트(전체)로 지정
- 위 경로를 포함한 하위 경로 페이지만 쿠키에 접근
-
Cookie 보안
Secure
- 기본적으로 Cookie는 http, https 구분하지 않고 전송
- Secure를 적용하면 https인 경우에만 전송
HttpOnly
- XSS(Cross-site Scripting) 공격을 방지
- 자바스크립트에서 Cookie 접근을 못하도록 막음
- HTTP 요청시 사용
SameSite
- 비교적 최신 기능이라 브라우저 지원여부를 확인 해야 함
- CSRF(Cross-Site Request Forgery) 공격을 방지
- 요청 도메인과 쿠키에 설정된 도메인이 같은 경우만 쿠키 전송
Cookie 2강
Cookie로 로그인 상태 유지
Cookie는 주로 사용자 세션 관리(로그인, 장바구니, 접속시간)나 광고 트래킹(사용자 행동) 등의 목적으로 사용
Cookie 문제점
- 쿠키 값은 임의로 변경할 수 있음
- Client가 임의로 쿠키의 값을 변경하면 서버는 다른 유저로 인식
- Cookie에 저장된 Data는 탈취되기 쉬움
userId = 주민번호, userId = 인덱스 값
- 쿠키는 네트워크 전송 구간에서 탈취될 확률이 매우 높음
- HTTPS를 사용하는 이유 중 하나에 속함
- 민감한 정보를 저장X
- 한번 탈취된 정보는 변경이 없다면 반영구적으로 사용할 수 있음
- 보안 대처방법
- 쿠키에 중요한 값을 저장X
- 사용자 별로 일반 유저나 해커들이 알아보지 못하는 값을 노출
- 일반적으로 암호화된
Token을 쿠키에 저장한다.
- 서버에서 암호화된
Token과 사용자를 매핑해서 인식
- Token은 서버에서 관리
- 토큰은 해커가 임의의 값을 넣어도 동작하지 않도록 만들어야 함
- 해커가 토큰을 탈취해도 사용할 수 없도록 토큰 만료시간을 짧게 설정
- 탈취가 의심되는 경우 해당 토큰을 강제로 만료시키면 됨
Session 1강
보안 문제를 해결하려면 중요한 정보는 모두 서버에서 저장해야 하고 Client와 서버는 예측이 불가능한 임의의 값으로 연결해야 함
Session
서버에서 중요한 정보를 보관하며 로그인 연결을 유지하는 방법
Session 생성 순서

1. 로그인에 성공하면 Server에서 임의로 만든 Session ID를 생성
- Session ID는 예측이 불가능해야 함
- UUID와 같은 값을 활용
2. 생성된 Session ID와 조회한 User 인스턴스를 서버의 Session 저장소에 저장
- 서버에 유저와 관련된 중요한 정보를 저장
Session 동작 순서
- 로그인
- 상태유지를 위해 Cookie를 사용
- 서버는 클라이언트에
Set-Cookie: SessionId=임의생성값 을 전달
- 클라이언트는 Cookie 저장소에 전달받은
SessionId 값을 저장
- Sessions을 사용하면 유저와 관련된 정보는 클라이언트에 없음
- 로그인 이후 요청
- 클라이언트는 모든 요청에
Cookie 의 SessionId를 전달
- 서버에서는
Cookie를 통해 전달된 SessionId로 Session 저장소를 조회
- 로그인 시 저장하였던 Session 정보를 서버에서 사용
Session 특징
- Session을 사용하여 서버에서 민감한 정보들을 저장
- 예측이 불가능한 세션 ID를 사용하여 쿠키값을 변조해도 문제X
- 세션 ID에 중요한 정보는 들어있지 않음
- 시간이 지나면 세션이 만료되도록 설정
- 해킹이 의심되는 경우 해당 세션을 제거하면 됨
- Session은 특별한것이 아니라 단지 Cookie를 사용하여 클라이언트가 아닌
서버에서 데이터를 저장해두는 방법
- Servlet은 Session을 자체적으로 지원
Servlet의 HttpSession
- Servlet이 공식적으로 지원하는 Session인 HttpSession은 Session 구현에 필요한 다양한 기능들을 지원
- Servlet을 통해 HttpSession 을 생성하게되면 SessionId가 JSESSIONID로 생성되고 JSESSIONID의 Value는 예측 불가능한 랜덤값으로 생성
ex)
request.getSession(true);
- Default 설정
- Request 객체 내에 Session이 존재한다면 기존 Session을 반환
- Request 객체 내에 Session이 없으면 새로운 Session을 생성해서 반환
request.getSession(false);
- Request 객체 내에 Session이 존재한다면 기존 Session을 반환
- Request 객체 내에 Session이 없으면 null을 반환
session.setAttribute(Const.LOGIN_USER, responseDto);
- Session에 Data를 저장하는 방법으로
request.setAttribute(); 와 비슷
- 하나의 Session에 여러개의 데이터를 메모리에 저장할 수 있음
Session 2강
Spring의 Session
Spring에서는 Session을 쉽게 다루도록 @SessionAttribute라는 어노테이션이 제공
- @SessionAttribute
request.getSession(true); 와는 다르게 Session을 새로 생성하는 기능X
- 이미 로그인이 완료된 사용자를 찾는 경우 즉, Session이 있는 경우에 사용
Session 정보
HttpSession은 Session을 간편하게 사용할 수 있도록 다양한 기능을 지원
session.getId();
session.getMaxInactiveInterval();
- 세션의 유효시간
- second 단위 default는 30분(1800초)
session.getCreationTime();
session.getLastAccessedTime();
session.isNew();
Session 3강
Session TimeOut
Session은 logout 기능을 사용하여 session.invalidate(); 가 되어야 삭제되지만 대부분의 사용자들은 로그아웃을 굳이 하지않고, 브라우저를 종료
Session의 문제점
- HTTP는 Connectionless 특성을 가지고 있어서 서버가 브라우저 종료 여부 판별X
- 서버에서 Session을 언제 삭제해야 하는지 판단하기 힘듬
- JSESSIONID의 값을 탈취 당한 경우 해당 값으로 악의적인 요청을 할 수 있음
- 세션은 서버 메모리에 생성되고 자원은 한정적이기 때문에 꼭 필요한 경우만 생성해야 함
Session 생명주기
- 기본적으로 30분을 기준으로 세션을 삭제
- 실제 로그인 후 30분 이상의 시간동안 사용중인 사용자의 세션 또한 삭제
- 다시 로그인 해야하는 경우가 발생
HttpSession 사용
- 세션 생성시점 30분이 아닌 서버에 최근 Session을 요청한 시간을 기준으로 30분을 유지
HttpSession은 기본적으로 해당 방식으로 세션의 생명주기를 관리
- Session 정보에서
LastAccessedTime 을 기준으로 30분이 지나면 WAS가 내부적으로 세션을 삭제
Session의 한계
Session은 서버의 메모리를 사용하여 확장성이 제한
- 서버가 DB 혹은 메모리에 저장된 세션 정보를 매번 조회하여 오버헤드가 발생
- 서버가 상태를 유지해야 하므로 사용자 수가 많아질수록 부담이 커짐
- Cookie는 웹 브라우저에만 존재하여 모바일 앱 등의 다양한 클라이언트에서 인증을 처리할 수 없음
- Scale Out(수평적 확장)에서 서버간 세션 공유 어려움
✅오버헤드(Overhead)란?
어떤 처리를 하기 위해 들어가는 간접적인 처리 시간, 메모리 등을 의미
Token
- Web Application이나 API에서 인증과 인가 과정에서 사용
- 사용자 또는 시스템의 신원과 권한을 증명하고 요청의 유효성을 검증하는 데 사용되는 디지털 문자열
Token을 사용하는 이유
- Token은 서버가 아닌 클라이언트에 저장되어 서버의 부담을 덜 수 있음
- Cookie는 웹 브라우저에만 존재하여 모바일 앱 등의 다양한 클라이언트에서 인증을 처리할 수 없음
- Token 방식은 Stateless를 기반으로 하여 확장성이 높음
- 인증된 사용자임을 확인하기 위한 고유한 서명을 포함하여 위조된 요청인지 확인할 수 있음
Token 동작순서

Token의 단점
- Cookie/Session 방식보다 Token 자체의 데이터 용량이 많음
- Payload(전송되는 데이터)는 암호화되지 않아서 중요한 데이터를 담을 수 없음
- Token을 탈취당하면 대처하기 어려워 만료 시간(30분)을 설정
JWT(JSON Web Token)
- 인증에 필요한 정보들을 암호화시킨 JSON 형태의 Token을 의미
- JSON 데이터 포맷을 사용하여 정보를 효율적으로 저장하고 암호화로 서버의 보안성을 높임
JWT 구조
XXXXXX.YYYYYY.ZZZZZZ
(Header).(Payload).(Signature)
- Header
- 토큰의 타입과 해싱 알고리즘을 정의
ex)json
{
"alg": "HS256",
"typ": "JWT"
}
- Payload
- 실제로 인증과 관련된 데이터(
Claims)를 담고 있음
- Claims의 종류
- Registered Claims : 미리 정의된 Claims
- iss(issuer) : 발행자
- exp(expiration time) : 만료시간
- sub(subject) : 제목
- iat(issued At) : 발행 시간
- jti(JWT ID) : 토큰의 고유 식별자
- Public Claims : 사용자가 정의할 수 있는 클레임, 공개용 정보 전달 목적
- Private Claims : 사용자 지정 클레임, 당사자들 간에 정보를 공유하기 위한 목적
ex)
json
{
"sub": "1234567890",
"name": "Sparta",
"exp": 1682563600
}
- Signature
- Header와 Payload를 서버의
Secret Key로 서명하여 암호화
- 암호화는 Header에서 정의한 알고리즘(alg)을 활용
- 서명을 통해 서버는 Token이 변조되지 않았음을 확인할 수 있음
ex)
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret
)
✔base64UrlEncode는 값을 URL에서 사용할 수 있도록 +, /를 각각 -, _로 표기
✔Header와 Payload는 Encoding된 값이기 때문에 복호화 혹은 값을 수정할 수 있지만 Signature는 서버에서 관리하는 값이기 때문에 Secret Key가 유출되지 않는 이상 복호화X
JWT
JWT 인증
- JWT는 Base64로 인코딩되어 쉽게 복호화 할 수 있음
- Payload가 그대로 노출되기 때문에 비밀번호나 민감한 정보를 저장하지 않음
JWT 인증 과정

- 클라이언트의 로그인 요청
- 로그인에 성공했다면 Header, Payload에
Secret Key를 사용하여 Signature를 만듬
- 이후 Base64로 Encoding
- 일반적으로 Cookie에 담아 클라이언트에게 JWT를 발급
- 발급받은 JWT를 저장 후 서버에 요청할 때
Authorization Header에 JWT를 담아 보냄
- 서버에서 JWT의 유효성 검사를 통해 통과한다면 인증에 성공하여 요청을 처리
- JWT의 유효성 검사

✔JSON Web Token의 목적은 정보 보호가 아닌, 위조 방지에 있음
JWT 장단점
JWT 장점
- Signature로 서버의 보안성이 증가
- Token 자체가 필요한 정보(유저 및 검증 정보)들을 모두 가지고 있음
- 서버는 인증 정보와 관련된 별도의 저장소를 사용X
- 서버의 수평 확장성(Scale Out)이 높아짐
- Cookie가 없는 다른 환경에서도 인증/인가를 적용할 수 있음
- DB를 조회하지 않아도 됨
✔Mobile의 경우 App을 자주 닫거나 백그라운드로 전환하여 Session 방식을 사용X
JWT 단점
- Payload는 암호화 된 것이 아니라 민감한 정보를 다루지 못함
- Token의 길이가 길어서 트래픽이 증가하면 네트워크에 부하가 증가
- 클라이언트 측에서 Token을 관리하기 때문에 탈취당하면 대처하기 어려움
Access Token, Refresh Token
- Token은 클라이언트에서 관리하여 탈취당할 위험성이 높기 때문에 만료 시간 설정이 필요
- 이 때 발생하는 단점을 극복하기 위해 Access Token과 Refresh Token을 사용
Token의 유형
- Access Token
- 사용자 인증 후 서버가 발급하는 유저 정보가 담긴 토큰
- 유효 기간 동안 API나 리소스에 접근할 때 사용
- Refresh Token
- Access Token은 보안을 위해 짧은 수명을 가짐
- Access Token이 만료된 경우 재발급 받기위해 사용
- 주로 데이터베이스에 유저 정보와 같이 저장
Access Token, Refresh Token 인증
- 클라이언트의 로그인 요청
- 로그인에 성공했다면 Header, Payload에
Secret Key를 사용하여 Signature를 만듬
- 발급받은 JWT를 저장 후 서버에 요청할 때
Authorization Header에 JWT를 담아 보냄
- 서버에서 JWT의 유효성 검사를 통해 통과한다면 인증에 성공하여 요청을 처리
Access Token이 만료 되었다면 Refresh Token 으로 토큰 재발급을 요청
- 서버로부터
Access Token을 재발급
Filter 1강
공통 관심 사항
같은 말로 횡단 관심사 라고 하며 여러 위치에서 공통적으로 사용되는 부가 기능이고 Filter가 나오게 된 이유는 공통 관심사의 처리 때문
Servlet Filter
Servlet Filter는 보안, 로깅, 인코딩, 인증/인가 등 다양한 작업을 처리하기 위해 사용
Servlet Filter 특징
- 공통 관심사 로직 처리
- 공통된 로직을 중앙 집중적으로 구현하여 재사용성이 높고 유지보수가 쉬움
- 모든 요청이 하나의 입구를 통해 처리되어 일관성을 유지
- HTTP 요청 및 응답 필터링
Filter Chain
- 여러 개의 필터가 순차적으로 적용될 수 있음
filterChain.doFilter(request, response); 다음 필터로 제어를 전달
doFilter()
- 실제 필터링 작업을 수행하는 주요 메소드로 필터가 처리할 작업을 정의
- 다음 필터로 제어를 넘길지 여부를 결정
Servlet Filter 적용

- Filter를 적용하면 Servlet이 호출되기 이전에 Filter를 항상 거치게 됨
- 공통 관심사를 필터에만 적용하면 모든 요청 or 응답에 적용
- Filter는 특정 URL Pattern에 적용할 수 있음
- Spring을 사용하는 경우 Servlet은
Dispatcher Servlet
인증/인가 필터 동작
-
로그인 성공
-
로그인 실패
- Filter에서 적절하지 않은 요청이면 Controller를 호출X
-
Filter Chain

- Filter는 Chain 형식으로 구성
- Filter는 개발자가 자유롭게 추가할 수 있음
- Filter는 순서를 지정하여 추가할 수 있음
Filter 2강
Filter Interface
Java Servlet에서 HTTP 요청과 응답을 가로채고, 이를 기반으로 다양한 처리 작업을 수행하는 데 사용
jakarta.servlet.Filter
- Filter Interface를 Implements하여 구현하고 Bean으로 등록하여 사용
Servlet Container가 Filter를 Singleton 객체로 생성 및 관리
주요 메서드
init()
- Filter를 초기화하는 메서드
- Servlet Container가 생성될 때 호출
default method이기 때문에 implements 후 구현하지 않아도 됨
doFilter()
- Client에서 요청이 올 때 마다
doFilter() 메서드가 호출
doFilter() 내부에 필터 로직(공통 관심사 로직)을 구현
- WAS에서
doFilter() 를 호출해주고 하나의 필터의 doFilter()가 통과된다면 Filter Chain에 따라서 순서대로 doFilter() 를 호출
- 더이상
doFilter() 를 호출할 Filter가 없으면 Servlet이 호출
destroy()
- 필터를 종료하는 메서드
- Servlet Container가 종료될 때 호출
default method이기 때문에 implements 후 구현하지 않아도 된다.
Servlet Filter 구현
Filter 구현체
- 요청 URL을 Log로 출력하는 Filter
@Slf4j
public class CustomFilter implements Filter {
@Override
public void doFilter(
ServletRequest request,
ServletResponse response,
FilterChain chain
) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
String requestURI = httpRequest.getRequestURI();
log.info("request URI={}", requestURI);
chain.doFilter(request, response);
}
}
doFilter() 는 더 이상 호출할 Filter가 없다면 Servlet을 호출
✔ServletRequest 는 기능이 별로 없어서 대부분 기능이 많은 HttpServletRequest 를 다운 캐스팅 하여 사용
Filter 등록
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Bean
public FilterRegistrationBean customFilter() {
FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<>();
filterRegistrationBean.setFilter(new CustomFilter());
filterRegistrationBean.setOrder(1);
filterRegistrationBean.addUrlPatterns("/*");
return filterRegistrationBean;
}
}
setFilter()
setOrder()
- Filter는 Chain 형태로 동작
- 즉, 실행될 Filter들의 순서가 필요
- 파라미터로 전달될 숫자에 따라 우선순위가 정해짐
- 숫자가 낮을수록 우선순위가 높음
addUrlPatterns()
- 필터를 적용할 URL 패턴을 지정
- 여러개 URL 패턴을 한번에 지정할 수 있음
- 규칙은 Servlet URL Pattern과 같음
filterRegistrationBean.addUrlPatterns("/*")
- Controller보다 앞에서 Filter가 동작
느낀 점
여유롭게 하지 않고 타이트하게 하려고 해서 그런지 잠을 충분히 자도 금방 피곤해지는 것 같다. 머리를 너무 많이 써서 그런가? 나를 최대한으로 써야 이정도 속도인데 조금이라도 여유를 부리면 어떻게 될지.. 😭 집에 있는데 집 가고 싶네.. 어차피 인생에 있어서 지금 몇 개월은 찰나의 순간이니까 어떻게든 해보자 화이팅😤