티스토리 뷰
스프링 시큐리티 완전 처음부터 5편 (Kotlin)
ROLE 기반 권한 제어 + @PreAuthorize 실전 사용 (이제 ‘보안 설계’를 한다)
여기까지 왔다면, 솔직히 말해서
로그인/인증은 이미 끝났다.
- JWT 발급 ✔
- JWT 검증 필터 ✔
- Stateless 구조 ✔
이제 남은 진짜 실무 포인트는 이거다.
“누가 이 API를 호출할 수 있는가?”
로그인만 되면 다 접근 가능?
→ 그건 보안이 아니다. 그냥 인증일 뿐이다.
이번 글에서는:
- ROLE 기반 권한 설계
- URL 단위 권한 제어
- 메서드 단위 권한 제어 (@PreAuthorize)
- 실무에서 절대 하면 안 되는 실수들
까지 한 번에 정리한다.
이 편이 끝나면
“시큐리티는 이제 구조적으로 이해한다”
라고 말해도 된다.
✔ 이번 글에서 만들 최종 그림
ROLE_USER → 일반 사용자
ROLE_ADMIN → 관리자
API권한
| GET /public/** | 모두 |
| GET /user/** | ROLE_USER |
| GET /admin/** | ROLE_ADMIN |
그리고 코드 레벨에서는:
@PreAuthorize("hasRole('ADMIN')")
fun adminOnly()
이걸 실제로 동작하게 만든다.
0. 스프링 시큐리티에서 ROLE 개념, 헷갈리는 포인트부터 정리
❗ 제일 많이 헷갈리는 것
- DB에는 USER, ADMIN 저장
- 시큐리티 내부에서는 반드시 ROLE_ prefix
즉,
DB 값시큐리티 권한
| USER | ROLE_USER |
| ADMIN | ROLE_ADMIN |
이건 규칙이다. 외우자.
hasRole("ADMIN")
→ 내부적으로는 ROLE_ADMIN을 찾는다
1. User Entity에 role 이미 있다고 가정 (복습)
2편에서 만들었던 User Entity 기억날 거다.
@Entity
@Table(name = "users")
class User(
@Column(nullable = false, unique = true)
val username: String,
@Column(nullable = false)
val password: String,
@Column(nullable = false)
val role: String = "USER",
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long? = null
)
여기서 중요한 건 딱 하나.
role 컬럼에는 USER, ADMIN 처럼 prefix 없는 값만 저장
2. CustomUserDetails에서 권한 매핑 다시 보기 (중요)
시큐리티는 UserDetails.getAuthorities() 결과만 본다.
그래서 여기서 ROLE prefix를 붙여줘야 한다.
override fun getAuthorities(): MutableCollection<out GrantedAuthority> {
return mutableListOf(
SimpleGrantedAuthority("ROLE_${user.role}")
)
}
이 한 줄 덕분에:
- DB: ADMIN
- 시큐리티 내부: ROLE_ADMIN
으로 인식된다.
👉 이게 빠지면
hasRole("ADMIN") 은 무조건 실패한다.
3. URL 기반 권한 제어 (가장 직관적인 방식)
먼저 SecurityConfig 에서 URL 기준으로 나눈다.
3-1. SecurityConfig – URL 권한 설정
.authorizeHttpRequests {
it.requestMatchers("/public/**").permitAll()
it.requestMatchers("/user/**").hasRole("USER")
it.requestMatchers("/admin/**").hasRole("ADMIN")
it.anyRequest().authenticated()
}
이 설정을 말로 풀면 이렇다.
- /public/**
→ 로그인 없이 접근 가능 - /user/**
→ ROLE_USER 이상 - /admin/**
→ ROLE_ADMIN 만 가능 - 그 외
→ 로그인은 필요
📌 주의
hasRole("USER") 는
ROLE_USER 가 있는지만 본다.
3-2. 테스트용 컨트롤러 만들기
📄 RoleTestController.kt
@RestController
@RequestMapping
class RoleTestController {
@GetMapping("/public/hello")
fun publicApi() = "public ok"
@GetMapping("/user/hello")
fun userApi() = "user ok"
@GetMapping("/admin/hello")
fun adminApi() = "admin ok"
}
3-3. 테스트 시나리오
로그인 유저/user/admin
| ROLE_USER | ⭕ | ❌ (403) |
| ROLE_ADMIN | ⭕ | ⭕ |
👉 403 Forbidden 이 나오면 정상이다.
(401 은 인증 실패, 403 은 권한 부족)
4. 메서드 레벨 권한 제어 – @PreAuthorize (실무에서 더 많이 씀)
URL 기준 제어는 단순하지만,
실무에서는 이런 요구가 많다.
- “같은 URL인데, 조건에 따라 접근 허용”
- “서비스 로직 단위로 권한 체크”
- “컨트롤러 말고 서비스 레벨에서 막고 싶다”
그래서 쓰는 게 @PreAuthorize 다.
4-1. 메서드 보안 활성화 (필수)
이거 안 하면 아무리 @PreAuthorize 써도 동작 안 한다.
@EnableMethodSecurity(prePostEnabled = true)
@Configuration
class MethodSecurityConfig
📌 Spring Boot 3.x 기준
@EnableGlobalMethodSecurity ❌
@EnableMethodSecurity ⭕
4-2. @PreAuthorize 기본 예제
@PreAuthorize("hasRole('ADMIN')")
@GetMapping("/admin-only")
fun adminOnlyApi(): String {
return "관리자만 접근 가능"
}
이 한 줄의 의미:
“이 메서드는 ROLE_ADMIN 이 있는 사용자만 실행 가능”
4-3. 서비스 레벨에서 권한 제어 (실무 추천)
컨트롤러 말고
Service 레벨에서 막는 게 더 안전하다.
@Service
class AdminService {
@PreAuthorize("hasRole('ADMIN')")
fun deleteUser(userId: Long) {
// 진짜 위험한 로직
}
}
👉 이유?
- 컨트롤러는 바뀌기 쉽다
- 서비스는 핵심 비즈니스 로직이다
- 실수로 다른 컨트롤러에서 호출해도 권한이 자동으로 막힌다
5. 표현식 조금만 더 (실무에서 자주 쓰는 것들)
✔ 여러 권한 허용
@PreAuthorize("hasAnyRole('ADMIN', 'MANAGER')")
✔ 인증된 사용자만
@PreAuthorize("isAuthenticated()")
✔ 본인 데이터만 접근
@PreAuthorize("#userId == authentication.name")
📌 이건 JWT payload에 userId 를 넣는 구조에서 자주 쓴다.
6. 실무에서 진짜 많이 터지는 사고들
❌ ROLE prefix 직접 비교
hasAuthority("ADMIN") // 실패
👉 항상 ROLE_ADMIN
❌ 컨트롤러에서만 권한 체크
- 서비스에 권한 체크 없음
- 다른 API에서 실수로 호출 → 보안 사고
❌ JWT payload에 너무 많은 정보 저장
- role 하나면 충분
- 개인정보 ❌
- 민감 정보 ❌
JWT는 서명된 공개 데이터다.
7. 이 시리즈를 여기까지 따라왔다면
이제 이런 말을 해도 된다.
- “JWT 기반 인증/인가 구조 구현해봤습니다”
- “Spring Security 필터 체인 이해하고 있습니다”
- “ROLE 기반 권한 설계 경험 있습니다”
- “@PreAuthorize 사용 가능합니다”
이건 주니어 레벨 답변이 아니다.
출처
- Spring Security 공식 문서
- Spring Boot 3.x Reference
- 실무 Kotlin + JWT 기반 인증 서버 운영 경험
스프링시큐리티, springsecurity, 코틀린, kotlin, jwt, rolebasedaccess, preauthorize, 권한관리, 백엔드개발, 개발블로그
'study > kotlin' 카테고리의 다른 글
| 스프링 시큐리티 완전 처음부터 7편 (Kotlin) (0) | 2025.12.26 |
|---|---|
| 스프링 시큐리티 완전 처음부터 6편 (Kotlin) (0) | 2025.12.22 |
| 스프링 시큐리티 완전 처음부터 4편 (Kotlin) (0) | 2025.12.17 |
| 스프링 시큐리티 완전 처음부터 3편 (Kotlin) (0) | 2025.12.15 |
| 스프링 시큐리티 완전 처음부터 2편 (Kotlin) (0) | 2025.12.12 |
- Total
- Today
- Yesterday
- JWT
- Next.js
- PostgreSQL
- REACT
- node.js
- rag
- kotlin
- 프론트엔드개발
- JAX
- DevOps
- NestJS
- Redis
- 웹개발
- Python
- 백엔드개발
- seo 최적화 10개
- Express
- 개발블로그
- ai철학
- 딥러닝
- CI/CD
- SEO최적화
- nextJS
- llm
- 쿠버네티스
- flax
- 압박면접
- Docker
- Prisma
- fastapi
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | 7 |
| 8 | 9 | 10 | 11 | 12 | 13 | 14 |
| 15 | 16 | 17 | 18 | 19 | 20 | 21 |
| 22 | 23 | 24 | 25 | 26 | 27 | 28 |
| 29 | 30 | 31 |

