Notice
Recent Posts
Recent Comments
Link
«   2025/05   »
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
Tags
more
Archives
Today
Total
관리 메뉴

히콩쓰 개발 일지

[Spring Security] 권한을 올바르게 설정했는데 403 에러가 날 경우 고려해야 할 점 본문

Spring

[Spring Security] 권한을 올바르게 설정했는데 403 에러가 날 경우 고려해야 할 점

용히콩 2023. 12. 12. 23:32

카카오 OAuth로 로그인, 로그아웃 등의 기능을 구현하면서 인증받은 User 객체를 Userdetails에 담아 SecurityConfig에서 권한을 확인한 뒤 API 접근을 허가하고자 아래와 같이 WebSecurityConfig 코드를 작성하였다.

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class WebSecurityConfig {

    private final UserDetailsServiceImpl userDetailsService;
    private final AuthenticationConfiguration authenticationConfiguration;
    private final RestTemplate restTemplate;

...(중략)

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        // CSRF 설정
        http.csrf((csrf) -> csrf.disable());

        // 기본 설정인 Session 방식은 사용하지 않고 JWT 방식을 사용하기 위한 설정
        http.sessionManagement((sessionManagement) ->
            sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
        );

        http.authorizeHttpRequests((authorizeHttpRequests) ->
            authorizeHttpRequests
                .requestMatchers(PathRequest.toStaticResources().atCommonLocations())
                .permitAll() // resources 접근 허용 설정
                .requestMatchers("/").permitAll() // 로그인 페이지 요청 허가
                .requestMatchers("/v1/user/kakao/**").permitAll() // 카카오 로그인
                .requestMatchers(HttpMethod.PATCH, "/v1/users").hasRole("USER") // 프로필 수정

  ...(이하 생략)

USER 권한을 검사하여 /v1/users에 정상적으로 접근할 수 있어야 하는데, 403 에러가 발생했다

이 때, 원인을 고민해보니 아래와 같이 정리할 수 있었다.

  • UserDeatilsImpl(UserDetails)와 UserDetailsService를 구현을 잘못한 경우
  • Autentication Context에 저장하는 로직 구현을 잘못한 경우
  • UserDetails 구현체를 받아오지 못하는 경우 (첫번째 고민과 일맥상통함)

하지만 디버깅을 해보니, 코드에는 문제가 없었고 UserDetails도 잘 받아오는 것을 확인할 수 있었다.
이때, UserDetails는 인증받은 사용자 객체를 의미한다. UserEntity와 동일하다고 이해했다.

이 때부터 정신이 혼미해지기 시작했는데, User 객체를 잘 받아왔고, 권한도 USER로 동일한데 왜 자꾸 엑세스가 거부되는지 알 수 없었다.
심지어, 에러 로그도 자세히 나오지 않았고 Cache miss 만 반복할 뿐이었다.


Spring Security 내부적으로 놓친 부분이 있을 것이라고 생각하여, hasRole 메서드를 타고 들어가보았더니, 아래와 같이 구현되어 있었다.

hasRole 내부 구현

rolePrefix를 확인하고, 저게 뭘까 하고 한 번 더 들어가 보았다.

rolePrefix


Spring Security는 내부적으로 권한을 검사할 때, rolePrefix를 붙여 검사하고 있었는데, 실제로 아래와 같이 구현해서 계속 일치하지 않아 403 에러를 출력한 것이다.

public enum Role implements CommonEnum {
    ADMIN("ROLE_ADMIN", "관리자"),
    USER("ROLE_USER", "유저"),
    GUEST("ROLE_GUEST", "게스트");

해결 방법은 두 가지였다.

  • 인증 객체를 생성할 때 권한에 "ROLE_" 을 붙일 것
  • 애초에 Role을 작성한 Enum 클래스의 권한을 ROLE_ADMIN, ROLE_USER 와 같이 바꿀 것

시간이 촉박하고 혹시나 구현해놓은 Security 쪽을 잘못 건드렸다가 겉잡을 수 없는 오류가 발생할까 하는 생각에, 아래의 방법을 선택하여 코드를 수정하였다.

public enum Role implements CommonEnum {
    ROLE_ADMIN("ROLE_ADMIN", "관리자"),
    ROLE_USER("ROLE_USER", "유저"),
    ROLE_GUEST("ROLE_GUEST", "게스트");

 

위와 같이 코드를 수정했더니, 문제가 해결되었다.
하지만, 이는 그렇게 좋은 방법이 아닌 것 같다고 느꼈다. 아래와 같이 DB에 저장되는 것이 사실상 썩 예뻐보이지 않았기 때문이다.

ROLE ENUM 변경 후 DB에 저장된 결과물

따라서, 위 방식을 채택해서 나중에 리팩토링 할 계획이다.

 


Spring Security 내부적으로 어떤 동작이 이뤄지고 있는지 파악할 수 있는 계기가 되었다.

무작정 코드를 작성하기만 하는 사람이 될 것이 아니라, 내부 동작 원리를 파악하고 고민하며 구현하는 "개발자" 가 되어야겠다고 생각했다.

 

이상 끝 ! 👶🏻💕