기술과 산업/언어 및 프레임워크

Spring Boot 시리즈 10편 – Spring Security로 인가 처리 확장: 권한 기반 API 보호 전략

B컷개발자 2025. 4. 25. 20:00
728x90

Spring Boot에서 Spring Security를 활용한 인가(Authorization) 전략을 소개합니다. 역할 기반 접근 제어, @PreAuthorize, 관리자/사용자 분리 구조 등 실무 적용 중심으로 설명합니다.


Spring Boot 시리즈 10편 – Spring Security로 인가 처리 확장: 권한 기반 API 보호 전략

API 인증이 끝났다고 해서 모든 보안이 끝난 것은 아닙니다.
**인증(Authentication)**은 “누구냐”를 확인하는 것이고,
**인가(Authorization)**는 “무엇을 할 수 있느냐”를 통제하는 일입니다.

이번 편에서는 JWT 기반 인증 후, **사용자 권한(Role)**을 기준으로
Spring Security를 통해 API 접근 권한을 어떻게 세밀하게 제어할 수 있는지 정리합니다.


📌 1. 인가(Authorization)의 중요성

✅ 인가가 필요한 상황 예시

  • 일반 사용자는 자신의 글만 수정 가능해야 함
  • 관리자는 모든 사용자 목록에 접근할 수 있어야 함
  • 특정 API는 관리자(Admin)만 호출 가능해야 함
  • 이벤트성 기능은 특정 기간에만 접근 가능해야 함

이러한 요구사항을 제대로 처리하지 않으면 권한 없는 사용자에게 민감한 데이터가 노출되는 보안 사고로 이어질 수 있습니다.


🧱 2. 사용자 권한(Role) 기반 구조 설계

1️⃣ JWT에 Role 포함하기

claims.put("role", "ROLE_USER"); // 또는 ROLE_ADMIN
  • Role은 JWT claim에 함께 포함하여 클라이언트가 별도 호출 없이도 인증 및 권한 정보를 활용할 수 있도록 설계

2️⃣ JWT → Authentication 객체 변환

UsernamePasswordAuthenticationToken auth =
    new UsernamePasswordAuthenticationToken(userId, "", List.of(new SimpleGrantedAuthority("ROLE_USER")));

SecurityContextHolder.getContext().setAuthentication(auth);
  • GrantedAuthority를 통해 Spring Security가 인가 판단을 수행할 수 있도록 구성

🔐 3. Security 설정에서 인가 정책 설정

1️⃣ WebSecurityConfigurer 또는 SecurityFilterChain에서 인가 설정

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http.csrf().disable()
        .authorizeHttpRequests()
        .requestMatchers("/api/admin/**").hasRole("ADMIN")
        .requestMatchers("/api/user/**").hasAnyRole("USER", "ADMIN")
        .anyRequest().authenticated();
    return http.build();
}
  • hasRole()은 내부적으로 "ROLE_" prefix를 자동으로 붙임
    (즉 hasRole("ADMIN") → "ROLE_ADMIN")

✅ 4. 메서드 레벨 보안 적용 (@PreAuthorize)

1️⃣ 메서드 기반 인가 활성화

@EnableMethodSecurity  // Spring Security 6.x 이상 (기존: @EnableGlobalMethodSecurity)
@Configuration
public class MethodSecurityConfig { }

2️⃣ @PreAuthorize 적용 예시

@PreAuthorize("hasRole('ADMIN')")
public void deleteUser(Long userId) {
    // 관리자만 삭제 가능
}

@PreAuthorize("#userId == authentication.name")
public UserDto getMyProfile(Long userId) {
    // 본인만 접근 가능
}
  • authentication.name은 Authentication.getName()으로부터 사용자 ID를 가져옴
  • SpEL(Spring Expression Language)을 통해 권한/조건을 유연하게 제어 가능

🔐 실무 예: 본인 소유 데이터만 수정 허용

@PreAuthorize("#request.userId == authentication.name")
@PostMapping("/api/user/update")
public ApiResponse<Void> updateUser(@RequestBody UpdateUserRequest request) {
    userService.update(request);
    return ApiResponse.success();
}

🛠️ 5. 실무 고려사항

항목 전략

ROLE 네이밍 "ROLE_USER", "ROLE_ADMIN" 일관된 규칙 사용
JWT 내 Claim 정합성 변조 방지를 위한 서명 검증 필수
사용자 ID 확인 토큰 내부 Subject or claim 활용 (getUserId)
예외 응답 처리 접근 거부 시 403 반환 + AccessDeniedHandler 커스터마이징
도메인 보호 정책 도메인 내부에서도 ID 비교 로직 이중 적용 권장

📦 예외 처리 확장 – 403 응답 제어

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http.exceptionHandling()
        .accessDeniedHandler((request, response, accessDeniedException) -> {
            response.setStatus(HttpStatus.FORBIDDEN.value());
            response.setContentType("application/json");
            response.getWriter().write(
                new ObjectMapper().writeValueAsString(
                    ApiResponse.fail("A403", "접근 권한이 없습니다.", null)
                )
            );
        });
    return http.build();
}
  • 클라이언트는 403 응답을 명확하게 파악하고 UX 처리를 할 수 있음

✅ 마무리 요약

항목 요약

인증 vs 인가 인증은 사용자 확인, 인가는 권한 확인
Role 기반 보호 API 경로별 or 메서드별로 hasRole, @PreAuthorize로 제어
JWT 내 권한 정보 role claim 활용 + GrantedAuthority 설정
세밀한 제어 SpEL을 활용한 사용자 ID 비교, 조건부 접근 제어 가능
실무 주의 사용자 ID 일치 확인은 Controller + Domain 양쪽에서 보완

📌 다음 편 예고

Spring Boot 시리즈 11편: 이메일 인증 및 비밀번호 재설정 – 보안성을 높이는 사용자 인증 흐름 구현

728x90