일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- 1차원 DP
- 2차원 dp
- 99클럽
- @GeneratedValue
- @GenericGenerator
- @Transactional
- Actions
- Amazon EFS
- amazon fsx
- Android Studio
- ANSI SQL
- async/await
- AVG
- AWS
- Azure
- bind
- builder
- button
- c++
- c++ builder
- c03
- Callback
- case when
- CCW
- chat GPT
- CICD
- Collections
- Combination
- combinations
- Comparator
- Today
- Total
기록
[Spring] URI 기반 권한제어 : Spring Boot 필터 및 Swagger 설정 본문
1. 시작하면서
Spring Boot를 사용하여 API 서버를 구축하는 과정에서, 사용자의 유형에 따라 권한을 제어하는 것이 필요했습니다. 이 프로젝트에서는 다음과 같은 사용자 유형을 정의했습니다:
- BOSS: 관리자 역할로, 시스템 전반에 대한 접근 권한을 가집니다. 이 사용자는 주요 관리 기능을 수행하는 데 필요한 모든 권한을 갖습니다.
- CUSTOMER: 일반 고객으로, 특정 서비스에만 접근할 수 있습니다. 고객은 자신의 계정 정보나 주문 상태 등을 조회할 수 있습니다.
- ADMIN: 시스템 관리자로, 사용자 관리 및 시스템 설정을 담당합니다. 이 사용자는 시스템의 운영과 관련된 여러 작업을 수행할 수 있는 권한을 가집니다.
- OPEN: 권한이 필요 없는 요청으로, 모든 사용자에게 허용됩니다. 이 카테고리는 일반적인 정보 제공이나 공지사항과 같은 공개된 리소스에 해당합니다.
각 사용자 유형에 따라 서비스는 다르게 존재하였으며, "여러 종류의 클라이언트에서 API서버에 요청한다"를 전제로 서버를 설계하였습니다. 또한 "서비스별로 필요한 API를 모아서 보고 싶다"는 클라이언트 개발자들의 요청이 있었습니다.
- BOSS는 관리자 모바일 앱을 통해 BOSS 권한 API와 OPEN 권한 API를 사용합니다.
- CUSTOMER는 일반 고객용 모바일 앱을 통해 CUSTOMER 권한 API와 OPEN 권한 API를 사용합니다.
- ADMIN은 별도의 웹 애플리케이션을 통해 ADMIN 권한 API와 OPEN 권한 API를 사용합니다.
이 프로젝트에서는 API 문서를 위해 Swagger를 사용하고 있었고, Swagger에서 엔드포인트의 URI를 기반으로 그룹을 나눌 수 있다는 점에 주목했습니다. 여기서 아이디어를 얻어 URI에 사용자 권한을 포함하고 검증하는 방법을 채택했습니다.
2. Filter: URI 기반 권한 체크
(1) Concept
Controller의 엔드포인트에 접근하기 전에 필터에서 권한을 체크하도록 구성합니다.
(2) RoleBasedUrilFilter Source
URI 기반으로 사용자 권한을 체크하는 필터를 구현합니다. 이 필터는 사용자의 요청 URI를 분석하여 해당 사용자의 권한에 맞는 접근인지 확인합니다. 사용자가 권한이 없는 URI에 접근하려 할 경우, 적절한 오류 메시지를 반환하여 사용자에게 접근이 거부되었음을 알립니다.
더보기
import com.meokq.api.auth.enums.UserType
import com.meokq.api.auth.request.AuthReq
import jakarta.servlet.FilterChain
import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse
import org.springframework.http.HttpStatus
import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.stereotype.Component
import org.springframework.web.filter.OncePerRequestFilter
@Component
class RoleBasedUriFilter : OncePerRequestFilter() {
override fun doFilterInternal(
request: HttpServletRequest,
response: HttpServletResponse,
filterChain: FilterChain
) {
val userType = getUserTypeFromSecurityContext()
if (!isAuthorized(request.requestURI, userType)) {
response.sendError(HttpStatus.FORBIDDEN.value(), "접근이 거부되었습니다.")
return
}
filterChain.doFilter(request, response)
}
private fun isAuthorized(requestUri: String, userType: UserType?): Boolean {
return when (userType) {
UserType.BOSS -> requestUri.startsWith("/api/boss/")
UserType.CUSTOMER -> requestUri.startsWith("/api/customer/")
UserType.ADMIN -> requestUri.startsWith("/api/admin/")
UserType.UNKNOWN -> false
else -> true // OPEN 리소스 접근 허용
}
}
private fun getUserTypeFromSecurityContext(): UserType? {
val authentication = SecurityContextHolder.getContext().authentication
return (authentication?.principal as? AuthReq)?.userType
}
}
주요 기능 설명
- URI 기반 권한 확인: 요청 URI가 사용자의 역할에 맞는지 확인합니다. 예를 들어, BOSS 타입 사용자는
/api/boss
로 시작하는 URI에만 접근할 수 있습니다. 이러한 구조는 역할에 따른 접근 제어를 명확하게 하여 보안성을 높입니다. - 권한 거부 처리: 사용자가 권한이 없는 URI에 접근하려 할 경우,
403 Forbidden
오류를 반환합니다. 이는 클라이언트에게 정확한 오류 메시지를 제공하여 문제를 이해하는 데 도움을 줍니다.
4. Security Configuration
Spring Security 설정을 통해 URI 기반 권한 필터를 적용합니다. 이 설정은 보안 관리의 핵심이 되며, 필터를 통해 요청이 들어올 때마다 권한 검증이 이루어집니다.
@Configuration
@EnableWebSecurity
class SecureConfig(
private val roleBasedUriFilter: RoleBasedUriFilter,
) {
@Bean
fun filterChain(http: HttpSecurity): SecurityFilterChain {
http.csrf().disable()
.authorizeHttpRequests()
.anyRequest().permitAll() // 기본적으로 모든 요청 허용
.and()
.addFilterBefore(roleBasedUriFilter, UsernamePasswordAuthenticationFilter::class.java)
return http.build()
}
}
설정 내용
- CSRF 보호 비활성화: REST API에서는 CSRF 공격을 고려하지 않기 때문에 CSRF 보호를 비활성화합니다. 그러나 웹 애플리케이션에서는 이 기능이 필요할 수 있으므로, 상황에 맞게 조정해야 합니다.
- URI 기반 권한 필터 추가: 모든 요청을 기본적으로 허용하되, 필터에서 권한을 검증하여 적절한 접근 제어를 수행합니다. 이를 통해 특정 URI에 대한 접근 권한을 효과적으로 관리할 수 있습니다.
5. Swagger Group
Swagger를 통해 API 문서를 자동으로 생성하고, 사용자 유형에 따라 그룹화된 API를 제공할 수 있습니다. 이를 통해 개발자들은 각 그룹에 대한 API 문서를 쉽게 확인하고 사용할 수 있습니다.
(1) Swagger Config Source
더보기
import io.swagger.v3.oas.annotations.enums.SecuritySchemeIn
import io.swagger.v3.oas.annotations.enums.SecuritySchemeType
import io.swagger.v3.oas.annotations.security.SecurityScheme
import io.swagger.v3.oas.models.OpenAPI
import io.swagger.v3.oas.models.info.Info
import io.swagger.v3.oas.models.security.SecurityRequirement
import org.springdoc.core.models.GroupedOpenApi
import org.springframework.beans.factory.annotation.Value
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
@Configuration
@SecurityScheme(
type = SecuritySchemeType.APIKEY, `in` = SecuritySchemeIn.HEADER,
name = "authorization", description = "Auth Token"
)
class SwaggerConfig {
@Value("\${spring.profiles.active:local}")
private lateinit var profile: String
@Value("\${apiProject.version:V.0.0.0}")
private lateinit var version: String
@Bean
fun openResourceApi(): GroupedOpenApi =
GroupedOpenApi.builder()
.group("open-resource") // 그룹 이름
.pathsToMatch("/api/open/**") // 해당 경로의 API를 포함
.build()
@Bean
fun bossResourceApi(): GroupedOpenApi =
GroupedOpenApi.builder()
.group("boss-resource") // 그룹 이름
.pathsToMatch("/api/boss/**") // 해당 경로의 API를 포함
.build()
@Bean
fun adminResourceApi(): GroupedOpenApi =
GroupedOpenApi.builder()
.group("admin-resource") // 그룹 이름
.pathsToMatch("/api/admin/**") // 해당 경로의 API를 포함
.build()
@Bean
fun customerResourceApi(): GroupedOpenApi =
GroupedOpenApi.builder()
.group("customer-resource") // 그룹 이름
.pathsToMatch("/api/customer/**") // 해당 경로의 API를 포함
.build()
@Bean
fun openApi(): OpenAPI =
OpenAPI()
.info(
Info()
.title("[$profile] Meok-q Api Document")
.description("$profile 환경에서의 API 문서입니다.")
.version("$version")
)
.security(
listOf(
SecurityRequirement()
.addList("authorization") // 보안 요구 사항 설정
)
)
}
(2) 결과
6. 장단점
구분 | 장점 | 단점 |
---|---|---|
1 | 명확한 권한 관리: URI에 역할 정보를 포함시켜 각 리소스에 대한 접근 권한을 명확하게 정의할 수 있습니다. | URI 길이 제한: 너무 많은 역할 정보를 URI에 포함시키면 URI가 길어질 수 있으며, 이는 가독성을 떨어뜨릴 수 있습니다. 최대한 간결한 URI 설계를 고려해야 합니다. |
2 | 유지보수 용이: URI 패턴을 통해 권한을 관리하므로, 새로운 역할이나 리소스가 추가될 때 코드 수정을 최소화할 수 있습니다. 이는 개발 효율성을 높입니다. | 보안 취약점: URI를 통해 권한을 제어할 경우, URI를 조작하여 접근할 수 있는 가능성이 존재하므로 추가적인 보안 검토가 필요합니다. |
3 | 직관적인 접근 제어: 개발자나 운영자가 어떤 URI가 어떤 역할에 의해 접근 가능한지 쉽게 이해할 수 있습니다. | 복잡성 증가: 역할과 URI 매핑이 복잡해질 경우, 관리가 어려워질 수 있으며, 실수로 잘못된 권한 설정이 발생할 수 있습니다. |
7. 마무리하면서
Filter, Interceptor, AOP
마지막으로, Spring Framework에서 Filter, Interceptor, AOP(Aspect-Oriented Programming)는 요청 처리 과정에서 특정 작업을 수행하는 메커니즘입니다. Filter는 javax.servlet.Filter
를 구현하여 HTTP 요청/응답을 가로채고, 인증 및 로깅 등의 작업을 수행합니다. Interceptor는 HandlerInterceptor
를 구현하여 컨트롤러 호출 전후에 처리를 하며, 주로 세션 관리나 권한 체크에 사용됩니다. AOP는 비즈니스 로직과 분리된 공통 관심사를 모듈화하여 트랜잭션 관리나 로깅을 수행하며, 포인트컷을 통해 적용됩니다. 이 세 가지는 각각의 목적과 사용 사례가 다르므로 필요에 따라 적절히 선택하여 사용할 수 있습니다.
역할기반 권한 제어방법 : 어노테이션 활용/URI 활용
URI에 역할을 명시하는 것은 권한 제어를 위한 한 가지 접근 방식이지만, 일반적인 방법이라고 보기는 어렵습니다.
어노테이션 기반 권한 관리를 사용하면 각 엔드포인트에 권한을 명시하여 접근 제어를 구현할 수 있습니다. Spring Security에서 주로 사용하는 어노테이션은 @PreAuthorize, @PostAuthorize, @Secured 등이 있습니다. Api 서버를 리뉴얼 하게 된다면, 어노테이션 기반으로 변경하는 것을 고려하려고 합니다.
(1) @PreAuthorize
@RestController
@RequestMapping("/api")
class NoticeController {
@PreAuthorize("hasRole('ADMIN')") // ADMIN 역할을 가진 사용자만 접근 가능
@GetMapping("/settings")
fun getSettings(): ResponseEntity<String> {
return ResponseEntity.ok("Admin Settings")
}
}
(2) URI 활용
@RestController
@RequestMapping("/api")
class NoticeController {
@GetMapping("/admin/settings")
fun getSettings(): ResponseEntity<String> {
return ResponseEntity.ok("Admin Settings")
}
}
'Web > Spring' 카테고리의 다른 글
CORS 오류 해결하기: Spring Boot와 Swagger (0) | 2024.11.11 |
---|---|
MyBatis/ bind 태그 사용법 (0) | 2024.08.19 |
경험치 시스템 구현(2): 인터페이스를 사용한 동적 경험치 부여 (0) | 2024.06.17 |
경험치 시스템 구현(1): 어드바이스와 커스텀 어노테이션 활용 (0) | 2024.06.10 |
[SpringBoot] 작업 환경 분리: spring.profiles.active 옵션 활용 (0) | 2024.03.18 |