Spring Security - 2 (CORS, CSRF, 인가)

·2024년 5월 26일
post-thumbnail

오늘은 저번 spring security - 1 에 이어서 CORS, CSRF, 인가에 관해 공부해 보자.

1. CORS

CORS란 Corss-Origin Resource Sharing의 약자로 다른 출처를 허용해준다는 의미이다. ccors 에러가 나는 이유를 알기 위해서는 SOP에 대한 개념이 필요한데, SOP란 same-orign policy로 같은 출처만 허용한다는 정책이다. 여기서 출처는 url에서 프로토콜, 도메인, 포트가 같으면 같은 출처라고 본다. 즉 CORS 에러를 해결하는 방법은 프론트(다른 도메인)에서 백엔드(우리 도메인)으로 올 때 다른 도메인의 접근을 허용해서 해결해주는 것을 의미한다. 이정도만 알아보고 SecurityFilterChain에 설정하는 코드를 보자.

위와 같이 간단하게 cors 설정을 하여 cors에러를 해결할 수 있다.

2. CSRF

csrf 공격이란 사용자가 자신의 의지와 무관하게 공격자가 의도한 행동을 해서 특정 웹페이지를 보안에 취약하게 한다거나 수정, 삭제 등의 작업을 하게 만드는 공격 방법이다. 이 일이 어떻게 일어나는지 이해하기 위해 세션을 이용하는 서버에 대해 알아보자.

2-1. 세션을 사용하는 서버

세션을 이용하는 서버에서는 로그인 후에 세션에 정보를 저장하며 session ID를 쿠키로 클라이언트로 보낸다. 그렇게 되면 다음부터는 클라이언트의 session ID가 보내지면서 로그인 없이 서비스 이용이 가능해진다.

2-2. CSRF 공격

위와 같의 세션 서버에서 사용자가 악성 서버에 들어가서 사용자의 권한이 필요한 요청을 누르게 유도를 한다. 그러면 결국 사용자의 클라이언트에서 요청을 보내는 것이기 때문에 쿠키에서 session ID와 악성 요청이 나가게 되는 것이다.
우리는 JWT를 사용할 것이고, session을 STATELESS로 관리할 거라서 크게 의미가 없긴 하다.

3. 인가

인가를 하는 데는 Authority와 role 두가지로 나뉜다. Authority는 일종의 권한인데, user를 볼 수 있는 권한, post를 볼 수 있는 권한 등 세부적으로 권한을 주는 것을 의미한다. 그에 반에 role은 권한을 가진 역할 이라고 볼수 있는데, 예를 들어 USER는 post와 user를 볼 수 있는 권한을 가지고 있고 ADMIN은 수정할 수 있는 권한까지 가지고 있는 것이다. Authority는 세부적인 설정이 가능하지만 서비스가 많아질 경우 복잡해질 수 있다. 따라서 여기서는 role을 사용해보자. (여기서는 하나의 유저에 하나의 role만 가질 수 있다고 가정하자.)

3-1. 저장한 role을 userDetails에 반영하기

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        List<String> roles = new ArrayList<>();
        roles.add("ROLE_USER");
        return roles.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());
    }

위와 같이 기존 코드는 roles.add에 "ROLE_USER"를 강제로 넣어주었는데 role을 user에서 받아오는것으로 바꿔보자.

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        List<String> roles = new ArrayList<>();
        roles.add(user.getRole());
        return roles.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());
    }

3-2. role에 따른 인가 설정하기

post쓰기는 USER와 ADMIN이 가능하고 replise 쓰기는 ADMIN만 가능하게 설정을 해보자.

    @Bean
    SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
        http.csrf(AbstractHttpConfigurer::disable);
        http.authorizeHttpRequests((requests) -> requests
                .requestMatchers(allowUrl).permitAll()
                .requestMatchers(HttpMethod.POST, "/api/v1/users").permitAll()
                .requestMatchers(HttpMethod.POST, "/api/v1/users/{userId}/posts").hasAnyRole("USER", "ADMIN")
                .requestMatchers(HttpMethod.POST, "/api/v1/replies").hasAnyRole("ADMIN")
                .anyRequest().authenticated());
        http.formLogin(withDefaults());
        http.httpBasic(withDefaults());
        return http.build();
    }

3-3. USER에 ROLE 넣어주기

회원 가입시에 role도 받아주자

    @Getter
    public static class JoinDTO {
        private String name;
        private String email;
        private String password;
        private String role;
    }
    public static User toUser(UserRequestDTO.JoinDTO joinDTO, PasswordEncoder passwordEncoder) {
        return User.builder()
                .name(joinDTO.getName())
                .password(passwordEncoder.encode(joinDTO.getPassword()))
                .email(joinDTO.getEmail())
                .role(joinDTO.getRole())
                .build();
    }

3-4 Test

우선 아무 역할이 아닌 아이디로 시도해보자.

401 에러가 발생하는 것을 볼 수 있다.
이번에는 ROLE_USER를 가지고 있는 아이디로 시도해 보자

성공!
이번에는 replies등록을 해보자. 이거는 ADMIN만 가능하게 했었다.
ROLE_USER로 시도해보자.

필터체인에서 ADMIN만 가능하게 설정했어서 403에러가 뜨는 것을 볼 수 있다.
ROLE_ADMIN으로 해보면 정상작동하는 것을 볼 수 있다.

4. 커스텀 필터 설정

이미 여러개의 필터들이 존재하지만 이것만으로 부족하다 싶으면 커스텀 필터를 추가로 달아서 사용할 수 있다.

4-1. 필터 생성하기

TestFilter를 만들어주자

public class TestFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        Filter.super.init(filterConfig);
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        
    }

    @Override
    public void destroy() {
        Filter.super.destroy();
    }
}

그 다음 doFilter안에 원하는 코드를 작성하자.

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        System.out.println(authentication.getName());
        chain.doFilter(request, response);
    }

4-2. 생성한 필터 배치하기

필터를 배치하는 방법에는 3가지가 있다.
원하는 필터 앞에 배치하는 addFilterBefore, 뒤에 배치하는 addFilterAfter, 같은 곳에 배치하는 addFilterAt이 있다.

여기서는 SecurityContextHolder 안에 있는 authentication이 잘 있는지 확인하기 위해 넣을 것이기 때문에 인증 후에 작동하게 하겠다.

그 후 아무 API를 작동 시켜보면

체인을 돌면서 출력 화면과 같이 출력되는 것을 볼 수 있다.

profile
고민0

0개의 댓글