既存のテストにセキュリティを適用


既存のテストでは,セキュリティの適用により問題となる部分がある.

  • 既存
    直ちにAPIを呼び出すことができ、テストコードを構成しても直ちにAPIを呼び出すことができる.

  • セキュリティオプションの有効化
    認証されたユーザーのみがAPIを呼び出すことができます
  • そのため、既存のAPIテストコードには認証権限がないため、認証されたユーザが呼び出すように動作するようにテストコードごとに修正する必要がある.
    をクリックして全体テストを行います.
    ニンジンテストに失敗

    失敗の原因

  • カスタマーID 2カスタマーサービス
  • が見つかりませんでした.

    CustomAuthe 2 UserServiceの作成に必要なソーシャルログイン設定はありません.
    =>src/mainは設定されていますが、src/testでは設定されていません.
    元のテスト時のアプリケーション.propertiesは自動的にインポートされます.(テスト中ではなくmain中の場合)
    ただし、他のアプリケーション-oauth.properties(ソーシャルログインに関連する設定値ファイル)などのファイルをインポートしないため、エラーが発生しました.
    だから私はあなたに作ってあげます.テストなので実際のGoogle連動は行わないので仮定定値を登録します.
    src/test/resources/application.properties
    spring.jpa.show_sql=true
    spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
    spring.h2.console.enabled=true
    spring.session.store-type=jdbc
    
    # TEST OAuth
    spring.security.oauth2.client.registration.google.client-id=test
    spring.security.oauth2.client.registration.google.client-secret=test
    spring.security.oauth2.client.registration.google.scope=profile,email
  • 302 Status Code
  • 200(正常応答)ではなく302です.
    これは、スプリングセキュリティ設定が許可されていないユーザーの要求を移動するためです.
    認証ユーザをランダムに追加し、APIのみをテストします.
    build.gradeにspring-security-testを追加します.

    次に、PostsApiControllerTestの2つのテスト方法にランダムユーザー認証を追加します.

    仮想ロールは、認証された偽のユーザーを作成して使用し、ロールに権限を追加できます.
    これは、ROLE USER権限を持つユーザがAPIを要求するのと同じ効果である.
    これでもすべてのテストに合格できません.
    @WithMockUserはMockMvcでのみ動作するためです.
    現在PostsApiControllerTestは@SpringBootTestのみで、MockMvcは全く使用されていません.

    @SpringBootTestでMockMvcを使用します。

  • PostsApiControllerTest
  • package com.chanmi.book.springboot.web;
    
    import com.chanmi.book.springboot.domain.posts.Posts;
    import com.chanmi.book.springboot.domain.posts.PostsRepository;
    import com.chanmi.book.springboot.web.dto.PostsSaveRequestDto;
    import com.chanmi.book.springboot.web.dto.PostsUpdateRequestDto;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import org.junit.After;
    import org.junit.Before;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.boot.test.web.client.TestRestTemplate;
    import org.springframework.boot.web.server.LocalServerPort;
    import org.springframework.http.*;
    import org.springframework.security.test.context.support.WithMockUser;
    import org.springframework.test.context.junit4.SpringRunner;
    import org.springframework.test.web.servlet.MockMvc;
    import org.springframework.test.web.servlet.MockMvcBuilder;
    import org.springframework.test.web.servlet.setup.MockMvcBuilders;
    import org.springframework.web.context.WebApplicationContext;
    
    import java.util.List;
    
    import static org.assertj.core.api.Assertions.assertThat;
    //@SpringBootTest에서 MockMvc를 사용하기 위한 추가
    import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
    import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
    import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
    
    @RunWith(SpringRunner.class)
    @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
    public class PostsApiControllerTest {
    
        @LocalServerPort
        private int port;
    
        //@WebMvcTest 사용하지 않고
        @Autowired
        private TestRestTemplate restTemplate;
    
        @Autowired
        private PostsRepository postsRepository;
    
        //@SpringBootTest에서 MockMvc를 사용하기 위한 =========
        @Autowired
        private WebApplicationContext context;
    
        private MockMvc mvc;
    
        //매번 테스트 시작 전에 MockMvc 인스턴스 생성
        @Before
        public void setup(){
            mvc = MockMvcBuilders
                    .webAppContextSetup(context)
                    .apply(springSecurity())
                    .build();
        }
    
    
    
        @After
        public void tearDown() throws Exception{
            postsRepository.deleteAll();
        }
    
        @Test
        @WithMockUser(roles = "USER")//임의 사용자 인증 추가
        public void save_Posts() throws Exception{
            //given
            String title = "title";
            String content = "content";
    
            //데이터를 넣어서 DTO 하나 생성
            PostsSaveRequestDto requestDto = PostsSaveRequestDto.builder()
                    .title(title)
                    .content(content)
                    .author("author")
                    .build();
    
            String url = "http://localhost:" + port + "api/v1/posts";
    
            //when, 생성한 Dto가지고 url로 post/Long으로 반환받음
            //ResponseEntity<Long> responseEntity = restTemplate.postForEntity(url, requestDto, Long.class); //MockMvc 사용 추가 후 주석 처리
            //MockMvc 사용 추가
            //생성된 MockMvc를 통해 API 테스트, 본문(Body) 영역을 ObjectMapper를 통해 문자열 JSON으로 변환
            mvc.perform(post(url)
                    .contentType(MediaType.APPLICATION_JSON_UTF8)
                    .content(new ObjectMapper().writeValueAsString(requestDto)))
                    .andExpect(status().isOk());
    
            //then
            //MockMvc 사용 추가 후 주석 처리
    //        assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK);
    //        assertThat(responseEntity.getBody()).isGreaterThan(0L);//바디 > 0L
    
            //데이터 잘 등록됐는지 확인
            List<Posts> all = postsRepository.findAll();
            assertThat(all.get(0).getTitle()).isEqualTo(title);
            assertThat(all.get(0).getContent()).isEqualTo(content);
    
        }
    
        @Test
        @WithMockUser(roles = "USER")//임의 사용자 인증 추가
        public void update_posts() throws Exception{
            //근데 내가 빡대갈이라 그러는데, 여기서 Posts 클래스로 만들었네..?왜..?
            //given
            Posts savedPosts = postsRepository.save(Posts.builder()
            .title("title")
            .content("content")
            .author("author")
            .build());
    
            Long updateId = savedPosts.getId();
            String expectedTitle = "title2";
            String expectedContent = "content2";
    
            PostsUpdateRequestDto requestDto = PostsUpdateRequestDto.builder()
                    .title(expectedTitle)
                    .content(expectedContent)
                    .build();
    
            String url = "http://localhost:" + port + "/api/v1/posts/" + updateId;
    
            HttpEntity<PostsUpdateRequestDto> requestEntity = new HttpEntity<>(requestDto);
    
            //when
    //        ResponseEntity<Long> responseEntity = restTemplate.exchange(url, HttpMethod.PUT, requestEntity, Long.class); //MockMvc 사용 추가 후 주석 처리
            //MockMvc 사용 추가
            //생성된 MockMvc를 통해 API 테스트, 본문(Body) 영역을 ObjectMapper를 통해 문자열 JSON으로 변환
            mvc.perform(put(url)
                    .contentType(MediaType.APPLICATION_JSON_UTF8)
                    .content(new ObjectMapper().writeValueAsString(requestDto)))
                    .andExpect(status().isOk());
    
            //then
            //MockMvc 사용 추가 후 주석 처리
    //        assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK);
    //        assertThat(responseEntity.getBody()).isGreaterThan(0L);
    
            List<Posts> all = postsRepository.findAll();
            assertThat(all.get(0).getTitle()).isEqualTo(expectedTitle);
            assertThat(all.get(0).getContent()).isEqualTo(expectedContent);
        }
    }
    追加された部分とコメント処理された部分にはコメントが付けられています.これによりposts部分を迂回するだけでテストは成功した.
  • @WebMvcTestクライアントIDが見つかりません2 UserService
  • 1とは異なり@WebMvTestを使用します.
    1番スプリングの安全設定は正常に動作していますが、@WebMvcTestはCustomOuth 2 UserServiceをスキャンしないため、問題が発生しています
    @WebMvcTest@ControllerAddiceと@Controllerを読み込みます.WebSecurityコンフィギュレータアダプタとWebMvcConfigureが含まれます.つまり、@Repository、@Service、@Componentはスキャンターゲットではないため、セキュリティ構成が読み込まれました.ただし、セキュリティ構成の作成に必要なCustomOuth 2 UserServiceを読み込めないため、エラーが発生しました
    質疑応答:スキャンターゲットからSecurityConfigを削除
  • HelloControllerTest
  • 既存

    変更

    @WithMockUserが偽認証ユーザーを生成

    でもそれは間違いだ

    このエラーは、アプリケーションクラスの@EnableJpaAuditingによって発生します.
    @EnableJpaAuditingには少なくとも1つの@Entityクラスが必要です.でも@WebMcTestなのでもちろんありません
    @EnableJpaAuditingは@SpringBootApplicationと一緒なので@WebMcTestでもスキャンします.
    だから@EnableJpaAuditingと@SpringBootApplicationを分けます.

  • Application.Javaから構文を削除する


  • コンフィグパッケージにJpaConfig->@EnableJpaAuditingを作成する

    @WebMvcTestは通常の@Configurationをスキャンしません.
  • すべてのテストに成功しました.次のtestImplementationセクションでは、上記の問題を解決しましたが、テストエラー、buildが発生しました.gradleに追加されたコード.


    スプリングの安全性テストが可能になりました.