[React + SpringBoot] JWT 인증 구현 ⑤ - WebSecurityConfig

SihoonCho·2023년 4월 21일
0
post-thumbnail

※ 읽기에 앞서


본 시리즈는 작성자의 이해와 경험을 바탕으로 실습 위주의 설명을 기반으로 작성되었습니다.
실습 위주의 이해를 목표로 하기 때문에 다소 과장이 많고 생략된 부분이 많을 수 있습니다.
따라서, 이론적으로 미흡한 부분이 있을 수 있는 점에 대해 유의하시기 바랍니다.

또한, 본 시리즈는 ChatGPT의 도움을 받아 작성되었습니다.
수 차례의 질문을 통해 도출된 여러가지 다양한 방식의 코드를 종합하여
작성자의 이해와 경험을 바탕으로 가장 정석으로 생각되는 코드를 재정립하였습니다.


📌 WebSecurityConfig.java


WebSecurityConfig.java

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class WebSecurityConfig {
    private final JwtTokenFilter jwtTokenFilter;

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
        return httpSecurity.csrf().disable()
                .authorizeRequests()
                // 관리자 관련 모든 요청에 대해 승인된 사용자 중 ADMIN 권한이 있는 사용자만 허용
                .antMatchers("/api/v1/admin/**").hasRole("ADMIN")
                .antMatchers("/api/v2/admin/**").hasRole("ADMIN")
                // 회원가입 및 로그인 관련 모든 요청에 대해 아무나 승인
                .antMatchers("/api/v1/auth/**").permitAll()
                .antMatchers("/api/v2/auth/**").permitAll()
                // 중복체크 관련 모든 요청에 대해 아무나 허용
                .antMatchers("/api/v1/user/check/**").permitAll()
                .antMatchers("/api/v2/user/check/**").permitAll()
                // 유저정보 관련 모든 요청에 대해 승인된 사용자만 허용
                .antMatchers("/api/v1/user/**").authenticated()
                .antMatchers("/api/v2/user/**").authenticated()
                // 첨부파일 관련 GET 요청에 대해 아무나 승인
                .antMatchers(HttpMethod.GET, "/api/v1/attachment/**").permitAll()
                .antMatchers(HttpMethod.GET, "/api/v2/attachment/**").permitAll()
                // 댓글 관련 GET 요청에 대해 아무나 승인
                .antMatchers(HttpMethod.GET, "/api/v1/comment/**").permitAll()
                .antMatchers(HttpMethod.GET, "/api/v2/comment/**").permitAll()
                // 게시글 관련 GET 요청에 대해 아무나 승인
                .antMatchers(HttpMethod.GET, "/api/v1/post/**").permitAll()
                .antMatchers(HttpMethod.GET, "/api/v2/post/**").permitAll()
                // 기타 모든 요청에 대해 승인된 사용자만 허용
                .antMatchers("/api/v1/**").authenticated()
                .antMatchers("/api/v2/**").authenticated()
                .and()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .addFilterBefore(this.jwtTokenFilter, UsernamePasswordAuthenticationFilter.class)
                .build();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

접근권한에 대해 실질적으로 기준을 세우는 설정입니다.
antMatchers를 통해 어떤 API에 어떤 사용자를 허가할 것인지 결정합니다.
addFilterBefore에는 jwtTokenFilter를 적용하여 Token의 유효성을 검사합니다.


📖 passwordEncoder()


passwordEncoder()

@Bean
public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
}

password를 암호화하는 기능으로 @Bean으로 등록하여 사용이 용이하도록 합니다.


📖 securityFilterChain()


securityFilterChain()

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
    return httpSecurity.csrf().disable()
            .authorizeRequests()
            .antMatchers("/api/v1/**").hasRole("ADMIN")
            .antMatchers("/api/v1/**").authenticated()
            .antMatchers("/api/v1/**").permitAll()
            .and()
            .sessionManagement()
            .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()
            .addFilterBefore(this.jwtTokenFilter, UsernamePasswordAuthenticationFilter.class)
            .build();
}

예전에는 WebSecurityConfigurerAdapter를 상속받아 구현했었는데, 현재는 Deprecated 되어 SecurityFilterChain@Bean으로 등록해서 사용해야합니다.

기본적인 형태는 위와 같고,
여기에 antMatchers()를 통해 필요한 조건을 추가합니다.

antMatchers()

            // 게시글 관련 GET 요청에 대해 아무나 승인
            .antMatchers(HttpMethod.GET, "/api/v1/post/**").permitAll()
            // 게시글 관련 POST 요청에 대해 승인된 모든 사용자 허용
            .antMatchers(HttpMethod.POST, "/api/v1/post/**").authenticated()
            
            // 모든 API 요청에 대해 승인된 사용자 중 ADMIN 권한이 있는 사용자만 허용
            .antMatchers("/api/v1/**").hasRole("ADMIN")
            // 모든 API 요청에 대해 승인된 모든 사용자 허용
            .antMatchers("/api/v1/**").authenticated()
            // 모든 API 요청에 대해 모든 사용자 허용
            .antMatchers("/api/v1/**").permitAll()

여기서 승인된(authenticated) 사용자라는 것은
로그인하여 Token을 발급받은 사용자 요청을 의미합니다.

antMatchers()를 설정할 때는 순서에 유의하여야 합니다.
조건의 적용 순서는 위에서부터 아래로 흘러가며 접근 조건을 확인합니다.

잘못된 예 (X)

            // 모든 API 요청에 대해 모든 사용자 허용
            .antMatchers("/api/v1/**").permitAll()
            // 관리자 관련 API 요청에 대해 ADMIN 권한을 가진 사용자만 허용
            .antMatchers("/api/v1/admin/**").hasRole("ADMIN")
            // 게시글 관련 GET 요청에 대해 아무나 승인
            .antMatchers(HttpMethod.GET, "/api/v1/post/**").permitAll()
            // 게시글 관련 POST 요청에 대해 승인된 모든 사용자 허용
            .antMatchers(HttpMethod.POST, "/api/v1/post/**").authenticated()

올바른 예 (O)

            // 관리자 관련 API 요청에 대해 ADMIN 권한을 가진 사용자만 허용
            .antMatchers("/api/v1/admin/**").hasRole("ADMIN")
            // 게시글 관련 POST 요청에 대해 승인된 모든 사용자 허용
            .antMatchers(HttpMethod.POST, "/api/v1/post/**").authenticated()
            // 모든 API 요청에 대해 모든 사용자 허용
            .antMatchers("/api/v1/**").permitAll()

예를 들어 잘못된 예(X)의 경우,
제일 먼저 /api/v1/** 으로 시작하는 모든 요청에 대해 모든 사용자를 허용하므로 이후에 나오는 /api/v1/admin/**, /api/v1/post/** 요청 조건은 무시되고, 로그인하지 않은 사용자도 글을 작성하거나 관리자 페이지에 접근할 수 있게 됩니다.

반면 올바른 예(O)의 경우,
/api/v1/admin/**을 먼저 확인하고, /api/v1/post/**를 확인한 뒤, 어디에도 해당이 안 된다면, 그 외 다른 모든 요청에 대해서 모든 사용자를 허용하게 됩니다.

즉, 로그인을 헀더라도 ADMIN 권한이 없으면 관리자 페이지에 접근할 수 없고,
로그인을 해야만 게시글 관련하여 게시글 작성과 같은 POST 요청을 보낼 수 있게 되며, 그 외에 메인 페이지나 회원가입, 로그인 페이지 등은 모든 사용자가 접근할 수 있게 됩니다.

조건이 많아지게 되면 점점 조건의 순서를 정하는 것이 헷갈리게 되는데,
조건의 순서를 정하는 좋은 방법은 중요도가 높은 순서대로 작성하는 것입니다.

profile
꾸준히 노력하는 개발자

0개의 댓글