(6월2일)
CSRF는 Cross-Site Request Forgery(사이트 간 요청 위조)의 줄임말로,
사용자가 자신의 의지와 무관하게 원치 않는 요청을 보내도록 유도하는 공격이다.
"로그인된 사용자의 권한을 도용해서 공격자가 대신 요청을 보내버리는 것"
https://mybank.com)https://evil.com)<img src="https://mybank.com/transfer?to=attacker&amount=1000000" />
mybank.com 세션을 자동으로 포함해서 요청 전송Spring Security 등에서는 CSRF Token으로 보호한다.
✔️ 토큰이 없거나 잘못되면 → 요청 거부
✔️ 토큰은 쿠키에만 담으면 안 되고, 폼 필드나 헤더에도 포함되어야 안전
http.csrf().disable(); // CSRF 보호 비활성화
✅ 보통 JWT 기반 API 서버에서는:
disable() 해도 OKcsrf().disable() 해도 괜찮다 (특히 JWT 사용하는 경우)..sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
이 코드는 Spring Security에서 세션 사용 방식을 설정하는 부분이다.
이건 다음과 같은 의미이다:
"나는 로그인한 사용자 정보를 세션에 저장하지 않고,
매 요청마다 인증 정보를 받아서 처리할 거야."
이 설정은 JWT 기반 인증 시스템에서 필수이다.
→ 따라서 **STATELESS (무상태)**로 설정해야 한다.
| 옵션 | 설명 |
|---|---|
STATELESS | 세션 생성 안 함. 매 요청마다 인증 필요 (✅ JWT 방식에 적합) |
STATEFUL (기본값) | 로그인 시 세션 생성, 이후 인증 상태 유지 |
IF_REQUIRED | 필요할 때만 세션 생성 (기본값) |
ALWAYS | 무조건 세션 생성 |
NEVER | Spring Security는 세션 안 만들지만, 다른 곳에서 만들어지면 사용 |
http.sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
➡️ ❌ Spring MVC + 세션 기반 로그인은 세션이 필요하므로 이 설정을 쓰면 안 됨.
.authorizeHttpRequests(auth -> auth
.requestMatchers("/auth/**", "/users/signup", "/error").permitAll()
.anyRequest().authenticated()
)
이 코드는 Spring Security에서 요청 URL 별 접근 권한을 설정하는 부분이다.
각 URL에 대해 "어떤 요청을 허용할지, 어떤 요청은 인증이 필요할지"를 지정한다.
| 설정 | 설명 |
|---|---|
.requestMatchers("/auth/**", "/users/signup", "/error") | 해당 경로들은 예외로 처리함 (로그인 없이 접근 허용) |
.permitAll() | 누구나 접근 가능 |
.anyRequest().authenticated() | 그 외의 모든 요청은 인증된 사용자만 접근 가능 |
| 요청 URL | 접근 가능 여부 |
|---|---|
/auth/login | ✅ 인증 없이 접근 가능 |
/users/signup | ✅ 인증 없이 접근 가능 |
/error | ✅ 인증 없이 접근 가능 |
/posts | ❌ JWT 토큰 없으면 401 에러 |
/users/me | ❌ 인증 필요 |
permitAll()authenticated().authorizeHttpRequests(auth -> auth
.requestMatchers("/", "/login", "/signup", "/oauth2/**").permitAll()
.requestMatchers(HttpMethod.GET, "/posts/**").permitAll() // 게시글 조회는 누구나
.requestMatchers("/admin/**").hasRole("ADMIN") // 관리자만 접근 가능
.anyRequest().authenticated()
)
.exceptionHandling(ex -> ex
.authenticationEntryPoint((request, response, authException) -> {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); // 401 에러
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write("{\"status\":401,\"message\":\"인증이 필요합니다.\"}");
})
)
이 코드는 Spring Security에서 "인증되지 않은 사용자가 보호된 리소스에 접근하려 할 때" 어떻게 대응할지 정의한 부분이다.
🔒 로그인 없이 접근 가능한 URL이 아닌데,
사용자(JWT 토큰 없음 등)가 요청을 보내면 → 이 코드가 실행됨
| 코드 | 설명 |
|---|---|
authenticationEntryPoint(...) | 인증이 안 된 사용자의 접근 시 동작 지정 |
response.setStatus(401) | HTTP 상태코드를 401 Unauthorized로 응답 |
setContentType(...) | 응답 형식을 JSON으로 설정 |
getWriter().write(...) | 사용자에게 JSON 메시지를 응답 |
/posts/1 요청은 인증이 필요함.anyRequest().authenticated() 조건에 걸림authenticationEntryPoint()를 호출{
"status": 401,
"message": "인증이 필요합니다."
}
.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class)
이 코드는 Spring Security의 필터 체인에 커스텀 JWT 필터(jwtFilter)를 등록하는 부분이다.
jwtFilter를 UsernamePasswordAuthenticationFilter보다 먼저 실행되도록 등록한다.
"스프링 시큐리티가 본격적으로 로그인 체크하기 전에,
내가 만든 JWT 필터를 먼저 실행시켜서
토큰 파싱 → 인증 처리까지 하고 넘기겠다!"
사용자가 클라이언트에서 JWT 포함된 요청을 보냄
jwtFilter가 먼저 실행됨
jwtFilter 내부에서:
Authentication)를 SecurityContext에 저장그 다음 필터들 (UsernamePasswordAuthenticationFilter 등)이 이어서 실행
UsernamePasswordAuthenticationFilter 앞에 둬야 하나?UsernamePasswordAuthenticationFilter는 폼 로그인(아이디/비번) 인증 처리용 필터필터는 순서대로 실행되며, 순서가 매우 중요하다.
예를 들어:
[SecurityContextPersistenceFilter]
↓
[CorsFilter]
↓
[jwtFilter] ← 우리가 만든 커스텀 필터
↓
[UsernamePasswordAuthenticationFilter]
↓
[ExceptionTranslationFilter]
↓
[FilterSecurityInterceptor]
addFilterBefore(A, B) → B 필터 전에 A 필터를 추가함UsernamePasswordAuthenticationFilter 전에 jwtFilter를 실행해야 함