23-08-09 TIL (@WithMockCustomUser, WithMockCustomUserSecurityContextFactory)

more·2023년 8월 9일
0

문제

  • Controller 테스트 코드를 만드는 도중에 현재 로그인 된 유저가 존재해야 하는데 해당 유저가 존재하는 상태를 넘겨주는 것을 어떻게 하는지 몰라서 많이 헤맸다.
    • 회원 가입을 할 때에는 그저 정보를 넘겨주면 되었는데, 다른 기능들 (유저 프로필 변경, 로그 아웃, 프로필 보기 등)을 할 때에는 현재 로그인 된 유저, 다시 말하면 인증된 유저 값이 필요했다.
    • 테스트 코드 작성은 처음이라서 어떻게 하는지 몰라 거의 하루 다 쓴듯...

시도

  • 해당 문제를 해결하기 위해서 사용하는 방법이 여러가지 있는데 그 중에서 @WithMockCustomUser라는 가짜 유저를 만들어서 사용하는 방법이 있어서 사용해보았다.
    • 해당 방법은 custom 어노테이션을 만들어서 하는 방법으로 보이는데, 이것만 만들어서 되는 것이 아니라 WithMockCustomUserSecurityContextFactory라는 클래스를 따로 만들어서 인증 절차를 진행해야 했다.
    • 해당 클래스 안에는 createSecurityContext라는 WithSecurityContextFactory에서 override된 메서드가 존재하는데 해당 메서드에서 authentication을 만들어서 넘겨주는 방식이었다.
    • 여기에서 또 문제가 생겼는데 이렇게 만든 Mock 유저를 넘겨 주었는데도 Authentication이 null이라고 나왔다.

해결

  • 해결하는 방법은 다음과 같았다.
    • createSecurityContext에서 내가 사용한 방법은 UserDetails를 만들어서 해당 userdetails안에 WithMockCustomUser에서 default로 설정한 유저를 넣어주는 방식이었다.
    • 하지만 이 방법이 아니라 UserDetails안에 UserDetailsService에 존재하는 loadUserByUsername 메서드에 WithMockCustomUser의 username을 집어넣고, 이를 UsernamePasswordAuthenticationToken.authenticated에 할당하는 방법을 사용하자 제대로 SecurityContext에 setAuthentication이 작동하여 Authentication이 null이 아니게 되었다.

해당 코드들

package com.winner.trelloimplementation;

import org.springframework.security.test.context.support.WithSecurityContext;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
@WithSecurityContext(factory = WithMockCustomUserSecurityContextFactory.class)
public @interface WithMockCustomUser {

    String username() default "testuser";

    String nickname() default "testnick";

    String password() default "testpass";

    String email() default "test@test.com";

    String introduction() default "testintro";
}
package com.winner.trelloimplementation;

import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.test.context.support.WithSecurityContextFactory;

public class WithMockCustomUserSecurityContextFactory implements WithSecurityContextFactory<WithMockCustomUser> {
    private final UserDetailsService userDetailsService;

    public WithMockCustomUserSecurityContextFactory(UserDetailsService userDetailsService) {
        this.userDetailsService = userDetailsService;
    }
    @Override
    public SecurityContext createSecurityContext(WithMockCustomUser annotation) {
        SecurityContext context = SecurityContextHolder.createEmptyContext();
        UserDetails principal = userDetailsService.loadUserByUsername(annotation.username());
        Authentication auth = UsernamePasswordAuthenticationToken.authenticated(principal, principal.getPassword(), principal.getAuthorities());
        context.setAuthentication(auth);
        return context;
    }
}
profile
조금 더

1개의 댓글

comment-user-thumbnail
2023년 8월 9일

좋은 글 감사합니다. 자주 올게요 :)

답글 달기