본 강좌에서는 스프링 부트를 사용하여 백엔드 애플리케이션을 개발하는 방법을 자세히 설명하겠습니다. 주된 내용으로는 회원 가입 및 로그인/로그아웃 기능을 구현하고, 이를 통해 스프링 시큐리티를 활용하는 방법과 테스트 환경 변수를 설정하는 방법을 다루겠습니다.
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. 결론
이 강좌에서는 스프링 부트를 기반으로 하여 백엔드 애플리케이션에서 회원 가입, 로그인 및 로그아웃 기능을 통해 스프링 시큐리티를 적용하는 방법을 배웠습니다. 또한, 테스트 환경 변수를 추가하고, 테스트 케이스를 작성하여 애플리케이션의 품질을 높이는 방법도 알아보았습니다. 이러한 기초를 바탕으로 더 복잡한 기능을 추가하고, 실전 프로젝트에 적용해보길 바랍니다.