Spring Security 인증과 인가
핵심 개념
인증(Authentication) = "너 누구야?"
로그인, 토큰 확인, 신분 확인 등 사용자가 누구인지 확인하는 과정이다.
인가(Authorization) = "너 이거 할 수 있어?"
권한 확인, 접근 제어 등 사용자가 특정 작업을 할 수 있는지 확인하는 과정이다.
쉬운 비유
- 인증: 놀이공원 입구에서 티켓 확인
- 인가: 각 놀이기구마다 키 제한, VIP 전용 확인
Spring Security 동작 흐름
사용자 요청
↓
Spring Security 필터 체인
↓
인증 확인 → 실패시 401
↓
권한 확인 → 실패시 403
↓
컨트롤러 실행
필터 체인이란
컨트롤러에 도달하기 전에 거치는 검문소다. 여러 필터가 순서대로 요청을 검사한다.\
차이점
1) 인증 -> 인가로 이어질 수 있지만 인가 -> 인증으로는 이어질 수 없습니다.
2) 인증은 권한 부여 결정의 일부 요소로 사용될 수 있습니다.
3) 권한 부여로 인증을 하기에는 유용하지 않을 수 있습니다.
- 예시를 들어보겠습니다. 비행기에 탑승하기 위해 신원을 증명하는 여권을 보여준 뒤 탑승 수속을 위한 비행기 티켓까지 보여주어야 탑승을 완료할 수 있습니다. 이처럼 신원 확인 이후 특정 권한을 얻기 위한 과정으로는 매끄럽게 이어질 수 있지만 반대로 비행기 티켓만 보유하고 있는 경우 탑승자 본인임을 증명할 수 없기 때문에 수속을 정상적으로 완료할 수 없게 됩니다.
401 vs 403
| 에러 | 의미 | 상황 예시 |
|---|---|---|
| 401 Unauthorized | 로그인 안 함 | 로그인 없이 마이페이지 접근 |
| 403 Forbidden | 권한 부족 | 일반 회원이 관리자 페이지 접근 |
핵심: 401은 인증 실패, 403은 인가 실패다.
권한 구조
Spring Security에서는 ROLE_ 접두사를 붙여 권한을 표현한다.
| 권한 | 설명 |
|---|---|
| ROLE_USER | 일반 회원 |
| ROLE_ADMIN | 관리자 |
| ROLE_MANAGER | 매니저 |
세션 방식 vs JWT 방식
세션 방식
서버가 로그인 정보를 기억한다.
- 로그인 → 서버에 세션 생성
- 브라우저는 세션 ID만 쿠키로 저장
- 다음 요청시 세션 ID로 사용자 확인
장점: 강제 로그아웃 가능
단점: 서버 메모리 사용, 여러 서버 운영시 세션 공유 필요
JWT 방식
토큰에 모든 정보를 담는다.
- 로그인 → 서버가 JWT 토큰 발급
- 브라우저는 토큰 저장
- 다음 요청시 토큰을 헤더에 포함
장점: 서버 부담 적음, 확장성 좋음
단점: 강제 로그아웃 어려움, 토큰 탈취 위험
언제 뭘 쓸까
| 상황 | 추천 방식 |
|---|---|
| 일반 웹사이트 | 세션 |
| 모바일 앱, API 서버 | JWT |
| 여러 서버 운영 | JWT |
| 강제 로그아웃 필요 | 세션 |
실제 설정
Spring Security는 설정 클래스에서 보안 규칙을 정의한다.
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) {
return http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/public/**").permitAll()
.requestMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.build();
}
설정 의미
permitAll(): 모두 접근 가능 (로그인 불필요)hasRole("ADMIN"): ADMIN 권한 필요authenticated(): 로그인 필요anyRequest(): 위에서 지정 안 한 모든 요청 (마지막에 작성)
주의사항
1. 순서가 중요하다
// ❌ 틀림: anyRequest()를 먼저 쓰면 뒤의 설정이 무시됨
.anyRequest().authenticated()
.requestMatchers("/public/**").permitAll()
// ✅ 맞음: 구체적인 규칙을 먼저, anyRequest()는 마지막
.requestMatchers("/public/**").permitAll()
.anyRequest().authenticated()
2. ROLE_ 접두사
// ❌ 틀림: ROLE_ROLE_ADMIN으로 인식됨
.hasRole("ROLE_ADMIN")
// ✅ 맞음: ROLE_는 자동으로 붙음
.hasRole("ADMIN")
3. 로그인 페이지는 열어둬야 한다
// 로그인 페이지를 permitAll()로 열어두지 않으면
// 로그인 자체가 불가능하다
.requestMatchers("/login").permitAll()
실전 예시
쇼핑몰
/ (메인) → 모두 접근 가능
/products (상품) → 모두 접근 가능
/cart (장바구니) → 로그인 필요
/mypage (마이페이지) → 로그인 필요
/admin (관리자) → ADMIN 권한 필요
커뮤니티
/ (메인) → 모두 접근 가능
/posts (게시글 보기) → 모두 접근 가능
/posts/write (글쓰기) → 로그인 필요
/posts/delete (삭제) → ADMIN 권한 필요
핵심 정리
1. 인증 vs 인가
| 구분 | 의미 | 실패시 |
|---|---|---|
| 인증 | 신원 확인 | 401 |
| 인가 | 권한 확인 | 403 |
2. 로그인 방식
| 방식 | 특징 | 추천 상황 |
|---|---|---|
| 세션 | 서버가 정보 저장 | 일반 웹사이트 |
| JWT | 토큰에 정보 포함 | API, 모바일 앱 |
3. 설정 순서
- 구체적인 경로 먼저 (
permitAll(),hasRole()) anyRequest()는 무조건 마지막- 로그인 페이지는
permitAll()필수
4. 기억할 메서드
permitAll(): 모두 허용authenticated(): 로그인 필요hasRole(): 특정 권한 필요anyRequest(): 나머지 모두
동작 흐름 다시 보기
1. 요청 들어옴
↓
2. Security 필터 체인 실행
↓
3. 인증 정보 확인
- 없으면 → 401
- 있으면 → 다음 단계
↓
4. 권한 확인
- 부족하면 → 403
- 충분하면 → 다음 단계
↓
5. 컨트롤러 실행
↓
6. 응답 반환
Spring Security는 이 모든 과정을 필터 체인에서 자동으로 처리한다. 개발자는 설정만 하면 된다.