스프링 부트 백엔드 개발 강좌, 스프링 시큐리티로 로그인 로그아웃, 회원 가입 구현, 테스트를 위한 환경 변수 추가하기

본 강좌에서는 스프링 부트를 사용하여 백엔드 애플리케이션을 개발하는 방법을 자세히 설명하겠습니다. 주된 내용으로는 회원 가입 및 로그인/로그아웃 기능을 구현하고, 이를 통해 스프링 시큐리티를 활용하는 방법과 테스트 환경 변수를 설정하는 방법을 다루겠습니다.

1. 스프링 부트란?

스프링 부트(Spring Boot)는 스프링 프레임워크를 더욱 쉽게 사용할 수 있도록 도와주는 도구로, 빠르고 간편하게 애플리케이션을 개발할 수 있게 해줍니다. 스프링 부트를 사용하면 설정과 구성이 적은 상태로도 생성된 애플리케이션을 쉽게 실행할 수 있으며, 다양한 스타터 의존성을 제공하여 개발의 편리함을 더합니다.

2. 스프링 시큐리티 소개

스프링 시큐리티(Spring Security)는 스프링 기반 애플리케이션의 보안을 책임지는 프레임워크입니다. 인증(Authentication)과 인가(Authorization) 기능을 제공하여 애플리케이션의 보안을 강화할 수 있게 해줍니다. 이번 강좌에서는 스프링 시큐리티를 통해 회원 가입, 로그인 및 로그아웃을 구현할 예정입니다.

3. 프로젝트 설정

프로젝트를 시작하기 위해 스프링 부트 초기화 웹사이트(Spring Initializr)를 방문하여 필요한 의존성을 추가한 후 새로운 프로젝트를 생성합니다. 다음은 추가할 주요 의존성입니다:

  • Spring Web
  • Spring Security
  • Spring Data JPA
  • Spring Boot DevTools
  • H2 Database (테스트 용으로 사용)

4. 회원 가입 기능 구현

4.1. 엔티티 클래스 작성

회원 가입 기능을 위해 User 엔티티 클래스를 작성합니다. 이 클래스는 사용자 정보를 저장하는데 사용됩니다.

package com.example.demo.model;

import javax.persistence.*;

@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(nullable = false, unique = true)
    private String username;
    
    @Column(nullable = false)
    private String password;
    
    public User() {}
    
    public User(String username, String password) {
        this.username = username;
        this.password = password;
    }
    
    // getters and setters
}

4.2. 레포지토리 인터페이스 생성

User 엔티티를 위한 JPA 레포지토리를 생성합니다.

package com.example.demo.repository;

import com.example.demo.model.User;
import org.springframework.data.jpa.repository.JpaRepository;

public interface UserRepository extends JpaRepository {
    User findByUsername(String username);
}

4.3. 서비스 클래스 작성

사용자 관련 비즈니스 로직을 처리하는 서비스 클래스를 작성합니다.

package com.example.demo.service;

import com.example.demo.model.User;
import com.example.demo.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;
    
    @Autowired
    private PasswordEncoder passwordEncoder;
    
    public User registerUser(String username, String password) {
        User user = new User(username, passwordEncoder.encode(password));
        return userRepository.save(user);
    }
}

4.4. 회원 가입 API 구현

RESTful API를 구현하여 사용자 요청을 처리합니다.

package com.example.demo.controller;

import com.example.demo.model.User;
import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/auth")
public class AuthController {
    @Autowired
    private UserService userService;
    
    @PostMapping("/register")
    public User register(@RequestParam String username, @RequestParam String password) {
        return userService.registerUser(username, password);
    }
}

5. 로그인 및 로그아웃 기능 구현

5.1. 스프링 시큐리티 설정

스프링 시큐리티를 설정하고, 사용자 인증을 처리하는 필터를 추가합니다.

package com.example.demo.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
            .withUser("user")
            .password(passwordEncoder().encode("password"))
            .roles("USER");
    }
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
            .authorizeRequests()
            .antMatchers("/api/auth/register").permitAll()
            .anyRequest().authenticated()
            .and()
            .httpBasic();
    }
    
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

5.2. 로그인 API 구현

로그인 기능은 HTTP Basic Authentication을 통해 구현됩니다. 사용자가 `/api/auth/login` 엔드포인트로 인증 정보(아이디, 비밀번호)를 보내면 스프링 시큐리티가 이를 처리합니다.

5.3. 로그아웃 API 구현

로그아웃은 HTTP 세션을 무효화하여 구현됩니다. 사용자가 `/api/auth/logout` 엔드포인트로 요청을 보낼 때 세션이 무효화됩니다.

6. 테스트를 위한 환경 변수 추가하기

테스트 환경에서 민감한 정보나 설정을 관리하기 위해 `application-test.properties` 파일을 생성하고 환경 변수를 설정합니다.

# application-test.properties
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
spring.jpa.hibernate.ddl-auto=create-drop

7. 테스트 케이스 작성하기

JUnit과 Mockito를 사용하여 작성한 API에 대한 테스트 케이스를 추가합니다. 테스트 케이스는 UserService와 AuthController에서 작성할 수 있습니다.

7.1. UserService 테스트

package com.example.demo.service;

import com.example.demo.model.User;
import com.example.demo.repository.UserRepository;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.crypto.password.PasswordEncoder;

import static org.mockito.Mockito.*;

@SpringBootTest
public class UserServiceTest {
    @InjectMocks
    private UserService userService;
    
    @Mock
    private UserRepository userRepository;
    
    @Mock
    private PasswordEncoder passwordEncoder;
    
    @BeforeEach
    public void setUp() {
        MockitoAnnotations.openMocks(this);
    }
    
    @Test
    public void testRegisterUser() {
        when(passwordEncoder.encode("password")).thenReturn("encodedPassword");
        when(userRepository.save(any(User.class))).thenReturn(new User("username", "encodedPassword"));
        
        User user = userService.registerUser("username", "password");
        
        assertEquals("username", user.getUsername());
        verify(userRepository).save(any(User.class));
    }
}

8. 결론

이 강좌에서는 스프링 부트를 기반으로 하여 백엔드 애플리케이션에서 회원 가입, 로그인 및 로그아웃 기능을 통해 스프링 시큐리티를 적용하는 방법을 배웠습니다. 또한, 테스트 환경 변수를 추가하고, 테스트 케이스를 작성하여 애플리케이션의 품질을 높이는 방법도 알아보았습니다. 이러한 기초를 바탕으로 더 복잡한 기능을 추가하고, 실전 프로젝트에 적용해보길 바랍니다.

9. 참고 자료