최근 몇 년간 마이크로서비스 아키텍처와 SPA(Single Page Application)가 점점 더 많은 인기를 끌면서, 웹 애플리케이션에서 보안 방안을 찾는 것이 매우 중요해졌습니다. 이 글에서는 스프링 부트를 활용해 JWT(JSON Web Token)를 이용한 로그인/로그아웃 기능과 리프레시 토큰을 사용하는 도메인을 구현하는 방법에 대해 자세히 설명하겠습니다.
목차
1. 서론
사용자 인증(authentication)과 인가(authorization)는 현대 웹 애플리케이션에서 중요한 문제입니다. 이러한 문제를 해결하기 위한 여러 가지 기술이 있지만, JWT는 그 중 가장 많이 사용되는 방법 중 하나입니다. 본 강좌에서는 스프링 부트를 활용하여 사용자 로그인 및 로그아웃 기능을 구축하고, JWT와 리프레시 토큰을 이용한 인증 메커니즘을 구현하는 방법을 배우게 됩니다.
2. 스프링 부트란?
스프링 부트(Spring Boot)는 스프링 프레임워크의 확장으로, Spring 기반의 애플리케이션을 쉽게 개발할 수 있도록 도와주는 편리한 도구입니다. 초기 설정이나 복잡한 XML 설정 없이 간단한 어노테이션으로 구현이 가능하여, 개발자들이 자주 사용하는 기능을 미리 구성해놓은 상태로 제공됩니다.
스프링 부트의 주요 특징은 다음과 같습니다:
- 자체 내장 서버: 톰캣, 제티 등의 웹 서버를 포함하여, 별도의 서버 설치 없이 실행할 수 있습니다.
- 자동 구성: 프로젝트에 포함된 라이브러리와 빈 설정에 따라 필요한 설정을 자동으로 구성합니다.
- 쉬운 배포: Jar 파일로 패키징하여 쉽게 배포 가능하며, 클라우드 환경에서도 용이하게 사용할 수 있습니다.
- 강력한 커뮤니티: 풍부한 문서와 예제, 다양한 플러그인으로 지원되는 큰 커뮤니티를 갖추고 있습니다.
3. JWT란?
JWT(JSON Web Token)는 주로 인증과 인가를 위한 솔루션으로 사용되는 compact, URL-safe means of representing claims to be transferred between two parties. 여기서 ‘claims’는 사용자에 대한 정보로, 이를 통해 서버와 클라이언트 간의 신뢰 관계를 유지할 수 있습니다.
JWT는 크게 세 부분으로 구성됩니다:
- Header: 토큰의 타입(typ)과 해싱 알고리즘(alg)의 정보가 포함됩니다.
- Payload: 사용자에 대한 정보와 클레임 데이터를 포함합니다. 여기서는 사용자 ID, 권한 등의 정보가 들어갑니다.
- Signature: Header와 Payload를 조합한 후, 비밀키를 사용해 서명합니다. 이로써 토큰의 무결성과 진위를 보장할 수 있습니다.
JWT의 장점으로는 다음과 같은 것들이 있습니다:
- 무상태적 특징: 서버는 JWT를 변조할 수 없으므로 상태를 유지하지 않고, 인증이 필요할 때마다 클라이언트가 JWT를 전송합니다.
- 보안성: 클라이언트가 JWT를 보유하므로 서버의 데이터베이스를 조회할 필요가 없어, 데이터베이스의 부하를 줄일 수 있습니다.
- CRUD 작업의 단순화: 인증 정보와 상태를 쉽게 관리할 수 있으며, IAM(Identity and access management)과 같은 복잡한 로직을 단순화할 수 있습니다.
4. 개발 환경 설정
이제 스프링 부트를 이용한 프로젝트를 위한 개발 환경을 설정해보겠습니다. 이를 위해 다음과 같은 도구를 사용합니다:
- Java 11: JDK 11 이상을 설치합니다.
- IDE: IntelliJ IDEA 또는 Eclipse를 사용하는 것이 좋습니다.
- Gradle 또는 Maven: 의존성 관리를 위해 Gradle 또는 Maven을 선택합니다.
- Postman: API 테스트 도구로 Postman을 이용합니다.
프로젝트를 생성할 때는 Spring Initializr(start.spring.io)를 통해 기본 설정을 해줄 수 있습니다. 필요한 의존성으로는 다음과 같은 것들을 추가합니다:
- Spring Web
- Spring Security
- Spring Data JPA
- H2 Database (또는 여러분이 선택한 RDBMS)
- Spring Boot DevTools (개발 편의를 위한 도구)
- jjwt (Java JWT 라이브러리)
프로젝트 생성 후 Viewer, Controller, Service, Repository 등 필요한 클래스를 생성합니다. `application.yml` 파일에 데이터베이스 관련 설정을 추가해야 합니다.
5. 로그인/로그아웃 기능 구현
로그인 및 로그아웃 기능을 구현하기 위해 다음의 구성 요소가 필요합니다:
5.1. User Entity
사용자 정보를 저장하기 위해 User 엔티티를 생성합니다.
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String password;
private String role;
// Getter, Setter, Constructor
}
5.2. User Repository
User 엔티티에 대한 CRUD 작업을 수행할 UserRepository를 생성합니다.
@Repository
public interface UserRepository extends JpaRepository {
Optional findByUsername(String username);
}
5.3. User Service
사용자 등록 및 인증 로직을 구현하기 위한 UserService를 생성합니다.
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
public User register(User user) {
// 여기에 비밀번호 암호화 로직을 추가하고 사용자 정보를 저장합니다.
}
public Optional findByUsername(String username) {
return userRepository.findByUsername(username);
}
}
5.4. Security Configuration
스프링 시큐리티를 설정하여 JWT 인증을 수행할 수 있도록 합니다.
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// 사용자 인증 및 보안 관련 설정을 정의합니다.
}
5.5. JWT Utility 클래스
JWT를 생성하고 검증하는 역할을 하는 JWTUtil 클래스를 작성합니다.
@Component
public class JwtUtil {
private String secretKey = "yourSecretKey"; // 비밀키를 안전하게 관리해야 합니다.
public String generateToken(String username) {
// JWT 생성 로직 구현
}
public boolean validateToken(String token, String username) {
// JWT 유효성 검사
}
}
5.6. Authentication Controller
API 엔드포인트를 정의하여 로그인 및 로그아웃 기능을 제공합니다.
@RestController
@RequestMapping("/api/auth")
public class AuthController {
@Autowired
private UserService userService;
@Autowired
private JwtUtil jwtUtil;
@PostMapping("/login")
public ResponseEntity<String> login(@RequestBody UserCredentials userCredentials) {
// 로그인 로직
}
@PostMapping("/logout")
public ResponseEntity<String> logout() {
// 로그아웃 로직
}
}
6. 리프레시 토큰 구현
리프레시 토큰은 액세스 토큰과 별도로 사용되며, 사용자가 계속해서 로그인 상태를 유지하도록 도와줍니다. 리프레시 토큰을 구현하기 위해 다음과 같은 단계를 수행합니다:
6.1. Refresh Token Entity
새로운 엔티티를 생성하여 리프레시 토큰을 관리합니다.
@Entity
public class RefreshToken {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String token;
@ManyToOne
private User user;
private LocalDateTime expiryDate;
// Getter, Setter, Constructor
}
6.2. RefreshToken Repository
리프레시 토큰에 대한 CRUD 작업을 수행하기 위한 리포지토리를 생성합니다.
@Repository
public interface RefreshTokenRepository extends JpaRepository<RefreshToken, Long> {
Optional<RefreshToken> findByToken(String token);
}
6.3. Refresh Token Service
리프레시 토큰의 생성을 관리하는 서비스를 생성합니다.
@Service
public class RefreshTokenService {
@Autowired
private RefreshTokenRepository refreshTokenRepository;
public RefreshToken createRefreshToken(User user) {
// 새로운 리프레시 토큰 생성 로직
}
public boolean validateRefreshToken(String token) {
// 리프레시 토큰 유효성 검사
}
}
6.4. Refresh Token Controller
리프레시 토큰을 이용하여 새로운 액세스 토큰을 생성하는 API를 추가합니다.
@RestController
@RequestMapping("/api/auth")
public class AuthController {
@Autowired
private RefreshTokenService refreshTokenService;
@PostMapping("/refresh-token")
public ResponseEntity<String> refreshToken(@RequestBody String refreshToken) {
// 리프레시 토큰을 이용한 새로운 액세스 토큰 생성 로직
}
}
7. 결론
이 강좌에서는 스프링 부트를 활용하여 JWT 기반의 로그인/로그아웃 기능을 구현하고, 추가적으로 리프레시 토큰을 이용하여 액세스 토큰을 관리하는 방법을 살펴보았습니다. 이런 방식으로 구현한 인증 시스템은 더욱 안전하고 유연하게 다양한 클라이언트 요청을 처리할 수 있습니다.
이제 여러분의 애플리케이션에서 JWT와 리프레시 토큰을 구현하여 보다 향상된 사용자 경험을 제공할 수 있게 될 것입니다. 더 나아가 이 기술을 활용해 다양한 추가적인 보안 기법들과 통합할 수 있습니다. 앞으로의 발전을 기대합니다!