프로젝트

Spring Security를 이용하여 로그인을 구현해보자

ummchicken 2023. 1. 26. 18:37

UserDetailService

UserDetailService 인터페이스는 DB에서 유저 정보를 가져오는 역할을 한다.
UserDetailsService는 DaoAuthenticationProvider와 협력하는 인터페이스이다.

DaoAuthenticationProvider는 요청받은 유저의 ID, Password와 
저장된 ID, Password의 검증하는 책임을 가지고 옴.

저장된 ID, Password를 갖고오기 위해 UserDetailsService와 협력한다.

 

 

 

 

UserDetailsService 인터페이스

public interface UserDetailsService {

	UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;

}
  • 파라미터로 username(유저를 식별할 수 있는 ID)을 받고, 리턴값으로 UserDetails를 반환한다.
  • loadUserByUsername() 메소드가 존재하며, 회원 정보를 조회하여 사용자의 정보와 권한을 갖는 UserDetails 인터페이스를 반환한다.

 

즉, 스프링 시큐리티에서 UserDetailService를 구현하고 있는 클래스를 통해 

로그인 기능을 구현한다고 보면 된다.

 

 


 

UserDetails

Spring Security에서 회원의 정보를 담는 인터페이스는 UserDetiails 인터페이스이다.

이 (1)인터페이스를 직접 구현하거나,
(2) 스프링 시큐리티에서 제공하는 User 클래스를 사용한다.
(※ User 클래스 : UserDetails 인터페이스를 구현하고 있는 클래스)
이 인터페이스를 구현하면, Spring Security에서 구현한 클래스를 
사용자 정보로 인식하고 인증 작업을 한다.

즉, UserDetails 인터페이스는 VO 역할을 한다.
(사용자의 정보를 모두 담아둠)

 

 


 

로그인 구현해보기

 

0. 조건

  • 이메일로 회원을 식별한다.

 

본인 마음대로 설정해도 됨.

이름으로 식별해도 되고...

 

 

1. MemberRepository 추가

Member 엔티티의 속성 중 하나인 email로 회원 정보 찾아오기

 

[MemebrRepository.java]

public interface MemberRepository extends JpaRepository<Member, Long> {

    Member findByEmail(String email);

}

→ email로 Member를 가져온다.

 

 

 

2. UserDetailService 인터페이스를 구현하는 MemberService 만들기

[MemberService.java]

@Transactional
@RequiredArgsConstructor
@Service
public class MemberService implements UserDetailsService {

    public final MemberRepository memberRepository;
    
    /**
     * UserDetails : 스프링 시큐리티에서 회원의 정보를 담기 위해 사용하는 인터페이스
     * */
    @Override
    public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException { // 1
        Member member = memberRepository.findByEmail(email);

        if(member == null){
            throw new UsernameNotFoundException(email); // 2
        }

        return User.builder() // 3
                .username(member.getEmail())
                .password(member.getPassword())
                .roles(member.getRole().toString())
                .build();
    }
}
  1. UserDetailService 인페이스의 loadUserByUsername() 메소드를 오버라이딩 한다. (로그인 할 유저의 email을 파라미터로 받는다.)
  2. 만약 일치하는 member가 없으면, UsernameNotFoundException을 발생시킨다.
  3. UserDetails을 구현하고 있는 User 객체를 반환한다.

 

 


 

로그인 테스트 해보기

 

들어가기 전...

Spring Security의 테스트코드를 작성하려면 의존성을 추가해야 한다.

 

  • Gradle (build.gradle) 추가
testImplementation 'org.springframework.security:spring-security-test'

 

  • Maver (pom.xml) 추가
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-test</artifactId>
   <scope>test</scope>
</dependency>

 

 

테스트 코드 작성

1. 로그인 성공 테스트

@SpringBootTest
@AutoConfigureMockMvc // MockMvc 테스트를 위해 선언
@Transactional
@TestPropertySource(locations="classpath:application-test.properties")
class MemberControllerTest {

    @Autowired
    private MemberService memberService;


    /**
     * MockMvc 클래스를 이용해 실제 객체와 비슷하지만 테스트에 필요한 기능만 가지는 가짜 객체.
     * MockMvc 객체를 이용하면 웹 브라우저에서 요청을 하는 것처럼 테스트를 할 수 있음.
     * */
    @Autowired
    private MockMvc mockMvc;

    @Autowired
    PasswordEncoder passwordEncoder;

    public Member createMember(String email, String password){ // 1
        MemberFormDto memberFormDto = new MemberFormDto();
        memberFormDto.setEmail(email);
        memberFormDto.setName("김길동");
        memberFormDto.setAddress("경기도 성남시 분당구");
        memberFormDto.setPassword(password);
        Member member = Member.createMember(memberFormDto, passwordEncoder);
        return memberService.saveMember(member);
    }

    @Test
    @DisplayName("로그인 성공 테스트")
    public void loginSuccessTest() throws Exception{
        String email = "test@email.com";
        String password = "1234";
        this.createMember(email, password);
        mockMvc.perform(formLogin().userParameter("email")
                        .loginProcessingUrl("/members/login") // 2
                        .user(email).password(password))
                .andExpect(SecurityMockMvcResultMatchers.authenticated()); // 3
    }

}
  1. 로그인 전, 테스트 할 회원을 생성한다.
  2. userParameter()를 이용하여 이메일과 비밀번호를 세팅하고 로그인 URL에 요청한다.
  3. 로그인이 성공하여 인증되었다면, 테스트코드가 통과한다.

 

테스트가 통과한다.

 

 

 

2. 로그인 실패 테스트

위 코드에 추가

@Test
@DisplayName("로그인 실패 테스트")
public void loginFailTest() throws Exception{
    String email = "test@email.com";
    String password = "1234";
    this.createMember(email, password);
    mockMvc.perform(formLogin().userParameter("email")
                    .loginProcessingUrl("/members/login")
                    .user(email).password("12345"))
            .andExpect(SecurityMockMvcResultMatchers.unauthenticated()); // 인증되지 않은 결과값 출력

}

 

 

통과한다.

 

 

 

 

 


참고