Project/SmartFarm:개발일지

[개발일지] 프로젝트 8일차 : Spring Security (feat. MyBatis)

SuperDev 2025. 2. 18. 16:21

제가 맡은 영역 중 먼저 구현을 시작한 것은 DB연동 & Spring Security 입니다.

 

우선 DB 연동을 위해 이미 코드 작업 중인 백엔드 팀원과 대화면서 JPA와 MyBatis 중에 뭘 사용할까 고민했습니다. 결론적으로 MyBatis & HikariCP를 사용하기로 했습니다. JPA는 객체 중심의 개발이 가능하고, Lazy loading을 지원하는 등의 장점이 있지만, MyBatis가 SQL 쿼리문을 직관적으로 확인할 수 있고, 동적 쿼리문 작성도 비교적 깔끔하기 때문에 선택하게 되었습니다. (사실 아직은 SQL이 직접 보여야 마음이 편합니다...ㅎ)

 

 

[DB & MyBatis 연동 및 테스트]

MyBatis 기반의 DB 연동을 진행한 후, 회원 객체(DTO)를 생성하여 DB 연동이 실제 이루어지는지, 데이터를 조회 및 특정 형식의 데이터가 삽입이 되는지 테스트를 진행하였고, 아래 코드에서 오류가 발생하지는 않았습니다.

 

테스트 코드를 작성하면서, 코드 자체를 작성하는 것보다 어떤 테스트를 해야할지 고민하는데 시간을 더 쓴 것 같습니다. 테스트 코드에 대한 공부가 별도로 필요할 것 같습니다. 

@SpringBootTest
@Transactional
class MemberMapperTest {
    @Autowired
    private MemberMapper memberMapper;

    private MemberDto testMember;

    @BeforeEach
    void setUp() {
        // 테스트용 회원 정보 생성
        testMember = new MemberDto(
                null,
                "test@email.com",
                "password",
                "name",
                "nickname",
                "010-1234-5678",
                "2000-01-01",
                MemberRole.USER,
                "address",
                null,
                null
        );
    }

    @Test
    @DisplayName("DB연동_조회_삽입_테스트")
    void getAllMemberTest() {
        List<MemberDto> initialMembers = memberMapper.getAllMember();
        int initialSize = initialMembers.size();

        memberMapper.insertMember(testMember);
        List<MemberDto> updatedMembers = memberMapper.getAllMember();

        // 검증
        assertThat(updatedMembers.size()).isEqualTo(initialSize + 1);
    }

    @Test
    @DisplayName("회원_조회_byEmail")
    void getMemberByEmailTest() {
        memberMapper.insertMember(testMember);
        MemberDto foundMember = memberMapper.getMemberByEmail(testMember.getEmail());

        // 검증
        assertThat(foundMember)
                .isNotNull()
                .extracting(MemberDto::getEmail, MemberDto::getNickname)
                .containsExactly(testMember.getEmail(), testMember.getNickname());
    }
}

 

 

 

[Spring Security]

프로젝트에서 security를 구현하는 중 동작원리를 모르다보니, 허공에 삽질하는 듯한 느낌이 들어서 시큐리티에 대한 개념을 블로그와 유튜브를 여러개 보면서 정리해나갔는데, 내용이 괜찮은 블로그를 찾았습니다.

 

기본적인 동작원리는 클라이언트의 요청이 WAS(톰캣)의 필터를 통과한 후, 스프링 컨테이너의 컨트롤러에 도달하는데, 시큐리티는 이 WAS 필터에 하나의 필터를 만들어서 넣고, 해당 필터에서 클라이언트의 요청을 가로챕니다. 그리고 클라이언트의 요청은 스프링 컨테이너 내부에 구현된 스프링 시큐리티 로직(필터체인)을 거친 후, WAS의 다음 필터로 복귀합니다.

(실제 시큐리티의 CSRF, 로그인, 로그아웃, 인가와 같은 작업은 필터체인에서 수행 됩니다.)

 

- DelegatingFilterProxy : 요청을 넘겨주는 서블릿 필터 입니다.

- FilterChainProxy : DelegatingFilterProxy에 의해 호출되는 SecurityFilterChain들을 들고 있는 Bean

- SecurityFilterChain : 실제 시큐리티 로직이 처리되는 부분입니다.

 

 

그 다음으로 알아보았던 것은 실제 로직을 담당하는 SecurityFilterChain 입니다.

이 SecurityFilterChain 내부에 N개의 필터를 구성할 수 있고, 각 필터에 로그인, 로그아웃, 인가 등의 로직을 구현할 수 있는데, 모든 작업은 기능 단위로 분업하여 진행되므로 작업한 내용을 다음 필터가 알기 위한 저장소 개념이 필요합니다.

 

시큐리티는 유저의 정보를 Authentication 객체에 저장하여 전달하고, SecurityContext에 의해 관리됩니다. 그리고 N개의 SecurityContext는 하나의 SecurityContextHolder에 의해서 관리됩니다. (SecurityContextHolder의 메소드는 static 으로 선언되기 때문에 어디서든 접근할 수 있습니다.)

 

Principal  :  유저에 대한 정보

Credentials  :  증명 (password, token)

Authorities  :  유저의 권한 목록(role) 

 

추가로 톰캣(WAS)은 멀티 스레드 방식으로 동작하는데, 유저가 접속하면 유저에게 하나의 스레드를 할당하기 때문에 동시에 시큐리티 로직을 사용할 수 있습니다. (여기서 threadLocal에 대한 개념이 나오는데 이해가 잘 안되네요...추후에 별도로 공부하고 구현도 해보면 좋을 것 같습니다.)

 

728x90