Hello, Freakin world!

스프링 시큐리티, 스프링 MVC 통합 테스트 작성하기 본문

Spring boot/Security

스프링 시큐리티, 스프링 MVC 통합 테스트 작성하기

johnna_endure 2021. 4. 18. 12:15

간단하게 몇 개의 url 접근에 필요한 권한을 설정하고 mock 테스트를 작성해보자.

 

아래와 같이 url 권한을 설정, 요청에 csrf 토큰을 추가하도록 설정했다.

csrf 설정을 하게 되면 위험할 수 있는 요청(POST, DELETE, PUT)의 경우 반드시 csrf 토큰을 요청에 포함해야 된다.

 

 

WebSecurityConfig

import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers(HttpMethod.POST,"/member/csrf").hasAnyRole("USER", "ADMIN")
                .antMatchers(HttpMethod.POST,"/member/user").hasRole("USER")
                .antMatchers(HttpMethod.POST,"/member/admin").hasRole("ADMIN")
                .antMatchers(HttpMethod.GET,"/member/**").hasRole("USER")
                .anyRequest().authenticated()

                .and()
                .csrf();
//                .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
    }
}

컨트롤러 메서드도 만들어 주고~

 

 

MemberRestController

import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

@RequiredArgsConstructor
@RestController
public class MemberRestController {

    private final MemberRepository memberRepository;

    @PostMapping("/member/csrf")
    public String postCsrf() {
        return "csrf";
    }

    @PostMapping("/member/user")
    public String postWithUserRole() {
        return "with USER role";
    }

    @PostMapping("/member/admin")
    public String postWithAdminRole() {
        return "with ADMIN role";
    }

    @GetMapping("/member")
    public String get() {
        return "get";
    }
}

 

 

이와 같은 mvc 요청들을 스프링 시큐리티와 연계해서 어떻게 테스트할 수 있을까?

 

다행히도 스프링 시큐리티에서 편리한 방법들을 제공해준다.

 

우선, spring-securirt-test 의존성을 추가하자.

    testImplementation 'org.springframework.security:spring-security-test'

이제 테스트를 작성해보자.

 

테스트는 MockMvc를 이용한다.

중요한 부분은 MockMvc 인스턴스를 초기화하는 부분이다.

클래스 레벨에 @SpringbootTest 와 @WebConfuguration 이 붙은 것도 주의!

(공식 레퍼런스에는 다르게 나와있는데 이거 때문에 몇 시간동안 헤맸다...)

 

그리고 또 중요한 부분은 SecurityMockMvcRequestPostProcessors 의 메서드를 이용해

필요한 보안 관련 부분을 처리하는 점.

 

이 두 가지만 알면 된다. 나머지 보안 관련 테스트는 맨 밑의 공식 레퍼런스를 참고하자.

다른 방식도 크게 다르지 않다.

 

 

MemberRestControllerTest

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@SpringBootTest
@WebAppConfiguration
public class MemberRestControllerTest {

    @Autowired
    private WebApplicationContext context;

    private MockMvc mvc;

    @BeforeEach
    public void setup() {
        mvc = MockMvcBuilders
                .webAppContextSetup(context)
                .alwaysDo(print())
                .apply(springSecurity())
                .build();
    }

    @WithMockUser(roles = {"USER", "ADMIN"})
    @Test
    public void testPost() throws Exception {
        mvc.perform(post("/member/csrf")
                .characterEncoding("UTF-8")
                .with(csrf()))
                .andExpect(status().isOk())
                .andExpect(content().string("csrf"));
    }

    @Test
    public void postWithUserRoleTest() throws Exception {
        mvc.perform(post("/member/user")
                .characterEncoding("UTF-8")
                .with(csrf())
                .with(user("mockUsername").roles("USER")))
                .andExpect(status().isOk())
                .andExpect(content().string("with USER role"));
    }

    @Test
    public void postWithAdminRoleTest() throws Exception {
        mvc.perform(post("/member/admin")
                .characterEncoding("UTF-8")
                .with(csrf())
                .with(user("mockUsername").roles("ADMIN")))
                .andExpect(status().isOk())
                .andExpect(content().string("with ADMIN role"));
    }

    @WithMockUser(roles = "USER")
    @Test
    public void testGet() throws Exception {
        mvc.perform(get("/member")
                .characterEncoding("UTF-8")
                .with(user("my").roles("USER")))
                .andExpect(status().isOk());
    }

}

 

 

 

Spring Security Reference

In Spring Security 3.0, the codebase was sub-divided into separate jars which more clearly separate different functionality areas and third-party dependencies. If you use Maven to build your project, these are the modules you should add to your pom.xml. Ev

docs.spring.io

 

Comments