본문 바로가기
Today I Learned 2024. 6. 25.

(24.06.25)[11주차] Spring Security의 User 권한 설정

팀 과제를 진행하면서 User 엔터티에 Role 부분에 USER와 ADMIN 두 권한을 분리를 했고,

Spring Security 환경에서 ADMIN 유저에게 Controller단에서의 특정 API 접근을 제어하는 조건을 주고 싶었다.

그래서 학습한 내용중에 가볍게 넘어가서 기억하지 못하는 내용 그리고 추가적으로 알아본 내용을 합쳐서 

User 권한에 따라 권한을 설정하는 방법을 간단하게 정리를 하고자 한다.

 

(팀 과제가 오늘까지 진행을 했어야했기 때문에 간단히 알아보고 스스로 학습해서 정리하는 시간만 가지는 것으로 한다.)


Spring Security의 User 권한 설정

  • 가정
    • Spring Security의 Authentication Manager 를 통해 로그인을 한 유저를 통해 이미 인가/검증이 끝난 상태로, Controller 의 API로 User 상세정보 = UserDetails를 줄 수 있는 상황으로 가정
    • User 에게는 여러가지 권한을 부여할 수 있지만, 현재는 일반 USER 와 ADMIN 으로 설정할 수 있는 상황으로 가정

각 API 별 접근 권한을 부여하기

@Secured("권한이름")

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class WebSecurityConfig {
...
  • Security Config 클래스를 추가해서(@Configuration으로 설정) 해당 설정에서 @EnableWebSecurity로 하여 Spring Security의 메서드 수준에서의 보안설정을 활성화 해야함
    • default값은 true 
 @Secured(UserRoleEnum.Authority.ADMIN)
    @DeleteMapping("aparts/{apartId}/qna/{qnaId}")
    public ResponseEntity<?> deleteQnA(@PathVariable Long qnaId, @AuthenticationPrincipal UserDetailsImpl userDetails) {
        qnAService.deleteQnA(qnaId, userDetails.getUser());
...
 @Secured({"ROLE_USER", "ROLE_ADMIN", "ROLE_MANAGER"})
    @DeleteMapping("aparts/{apartId}/qna/{qnaId}")
    public ResponseEntity<?> deleteQnA(@PathVariable Long qnaId, @AuthenticationPrincipal UserDetailsImpl userDetails) {
        qnAService.deleteQnA(qnaId, userDetails.getUser());
...
  • Controller에 존재하는 API마다 @Secured Annotation으로 권한별로 제어가 가능, 클래스, 메서드 단위로도 사용가능
  • 여러 권한을 주고 싶으면 해당 배열을 통해서 지정을 해줄 수 있음
    • 해당 user의 role은UserRoleEnum enum에서 사용되기 때문에 해당 권한 enum value를 넣은것

@PreAuthorized()

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
@RequiredArgsConstructor
public class WebSecurityConfig {
...
  • Secured와 다르게 @EnableGlobalMethodSecurity(prePostEnabled = true) 를 추가해야지만 사용할 수 있는 Spring Security의 권한 제어 Annotation
@PreAuthorize("hasAnyAuthority('USER', 'ADMIN')")
    @DeleteMapping("aparts/{apartId}/qna/{qnaId}")
    public ResponseEntity<?> deleteQnA(@PathVariable Long qnaId, @AuthenticationPrincipal UserDetailsImpl userDetails) {
        qnAService.deleteQnA(qnaId, userDetails.getUser());
        ...
  • hasAnyAuthority() 를 통해서 권한을 설정할 수 있음
  • @Secured와 다르게 조건을 부여해서 동적인 권한을 제어할 수 있는 것이 큰 장점
 @PreAuthorize("hasRole('ADMIN') or principal.age >= 20")
    @DeleteMapping("aparts/{apartId}/qna/{qnaId}")
    public ResponseEntity<?> deleteQnA(@PathVariable Long qnaId, @AuthenticationPrincipal UserDetailsImpl userDetails) {
        qnAService.deleteQnA(qnaId, userDetails.getUser());
        ...
  • SpEL =  Spring Expression Language를 사용해서 조건을 생성이 가능
  • 위 코드는 ADMIN 권한을 가진 사용자 중 나이가 20세 이상인 User만 접근할 수 있는 권한을 주는 것
    • 여기서의 principal은 SecurityContextHolder에 있는 UserTails의 principal이 맞기 때문에 user의 정보라고 볼 수 있음

 

일괄적으로 API 접근 권한을 부여하기

Security Config의 SecurityFilterChain에서의 설정 후 Bean 등록

@Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        ...
        http.authorizeHttpRequests((authorizeHttpRequests) ->
                authorizeHttpRequests
                        .requestMatchers("/api/login").permitAll()
                        .requestMatchers( "/api/signup").permitAll()
                        .requestMatchers(HttpMethod.GET, "/api/**").permitAll()
                        .requestMatchers("/api/admin/**").hasRole("ADMIN")
                        .anyRequest().authenticated()
      ...

 

  • authorizeHttpRequests에서 requestMatchers() 메서드를 활용해서  Ant 스타일을 기본으로 해서 (* 표시로 하위까지 포함한다는 식) 사용가능
    • hasRole() 메서드와 같이 써서 해당 권한만 위 requestMatchers()에 지정한 API에 일괄적으로 권한을 부여할 수 있음
 
 

지금은 권한설정이 USER ADMIN MANAGER 정도에 그치지만, 때에 따라서 동적으로 기능접근에 변동이 일어날 수도 있고, 또는 많은 권한이 존재할 수 있다.

따라서 해당 SpringSecurity에서 제공해주는 Annotation 을 잘 알아두고 권한을 제어할 수 있는 것을 좀더 주의깊게 살펴볼 필요가 있고 기억을 해야할 것.