JWT로 로그인/로그아웃 구현하기
목차
- 서론
- Spring Boot 소개
- JWT란?
- 프로젝트 설정
- 회원 가입 기능 구현
- 로그인 기능 구현
- JWT를 이용한 인증 및 인가
- JWT 토큰 생성 및 검증
- 로그아웃 기능 구현
- 결론
서론
요즘 웹 어플리케이션의 인증(authentication)과 인가(authorization)는 매우 중요한 이슈입니다. 특히, RESTful API를 개발할 때는 다양한 클라이언트를 지원해야 하기 때문에, 사용자 인증을 처리하는 방식에도 고려해야 할 사항이 많습니다. 오늘은 스프링 부트를 사용하여 JWT(JSON Web Token) 기반의 로그인 및 로그아웃 시스템을 구현하는 방법에 대해 알아보겠습니다.
Spring Boot 소개
스프링 부트(Spring Boot)는 자바 기반의 웹 애플리케이션 프레임워크인 스프링(Spring)에서 애플리케이션 초기화 및 설정을 간소화하기 위해 만들어진 프로젝트입니다. 스프링 부트는 필요한 설정과 종속성을 자동으로 관리하여 개발자는 비즈니스 로직에 집중할 수 있도록 도와줍니다.
스프링 부트는 다음과 같은 장점을 제공합니다:
- 빠른 프로토타입 개발이 가능하다.
- 기본적인 설정이 자동으로 이루어진다.
- 내장 서버 톰캣을 내장하고 있어 별도의 서버 설정이 필요 없다.
JWT란?
JWT(JSON Web Token)는 JSON 포맷을 사용하여 정보를 안전하게 전송하기 위한 컴팩트하고 독립적인 표준입니다. JWT는 주로 웹 애플리케이션에서 사용자 인증 및 인가를 처리하는데 많이 사용됩니다. JWT의 특징은 다음과 같습니다:
- 자체적으로 신뢰성을 가지고 있다: 전송되는 데이터가 서명되어 검증이 가능하다.
- 구조가 간단하여 쉽게 분석할 수 있다.
- HTTP 헤더를 통해 전송이 가능해 다양한 클라이언트와 호환성이 좋다.
JWT는 세 가지 구성요소로 이루어져 있습니다:
- 헤더(Header): 토큰 타입과 서명 알고리즘 정보를 포함합니다.
- 페이로드(Payload): 사용자 정보 및 클레임(Claims)을 포함하는 부분입니다.
- 서명(Signature): 헤더와 페이로드를 기반으로 서명하여 데이터의 무결성을 보장합니다.
프로젝트 설정
스프링 부트 프로젝트를 생성하기 위해 Spring Initializr 를 사용합니다. 필요한 의존성으로는 웹, JPA, Spring Security, Lombok, 그리고 JWT 라이브러리를 선택합니다.
com.example
jwt-demo
0.0.1-SNAPSHOT
jar
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-data-jpa
org.springframework.boot
spring-boot-starter-security
io.jsonwebtoken
jjwt
0.9.1
org.projectlombok
lombok
1.18.12
provided
com.h2database
h2
runtime
build.gradle 파일에 위 의존성을 추가한 후, 스프링 부트 애플리케이션을 설정합니다. application.properties
파일에 데이터베이스 연결 및 JPA 설정을 작성합니다.
회원 가입 기능 구현
회원 가입 기능은 기본적으로 사용자가 제공한 정보를 데이터베이스에 저장하는 기능입니다. 회원 정보를 저장하기 위해 JPA 엔티티 클래스를 생성합니다.
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import javax.persistence.*;
@Entity
@Table(name = "users")
@Getter @Setter @NoArgsConstructor @AllArgsConstructor
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true, nullable = false)
private String username;
@Column(nullable = false)
private String password;
private String role;
}
사용자가 회원가입 시 제공하는 사용자 정보를 처리하기 위한 리포지토리 인터페이스를 생성합니다.
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository {
User findByUsername(String username);
}
회원가입 요청을 처리하는 서비스 클래스를 생성합니다.
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 void registerUser(User user) {
user.setPassword(passwordEncoder.encode(user.getPassword()));
user.setRole("ROLE_USER");
userRepository.save(user);
}
}
로그인 기능 구현
로그인 기능은 사용자가 인증 정보를 제공하여 서버에서 JWT 토큰을 발급받는 과정입니다. 이를 위해 인증을 처리하는 필터 클래스를 작성합니다.
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private JwtTokenProvider jwtTokenProvider;
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) {
String username = request.getParameter("username");
String password = request.getParameter("password");
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
return authenticationManager.authenticate(authRequest);
}
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
String token = jwtTokenProvider.createToken(authResult.getName());
response.addHeader("Authorization", "Bearer " + token);
response.getWriter().write("로그인 성공");
}
}
JWT를 이용한 인증 및 인가
JWT를 사용하여 API 호출 시 사용자를 인증하는 방법을 구현합니다. JWT 토큰이 없거나 유효하지 않은 경우 요청을 차단하는 필터를 생성합니다.
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.GenericFilterBean;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
public class JwtAuthenticationFilter extends GenericFilterBean {
@Autowired
private JwtTokenProvider jwtTokenProvider;
@Override
public void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
String token = request.getHeader("Authorization");
if (token != null && jwtTokenProvider.validateToken(token)) {
Authentication authentication = jwtTokenProvider.getAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
chain.doFilter(request, response);
}
}
JWT 토큰 생성 및 검증
JWT 토큰을 생성하고 검증하기 위한 유틸리티 클래스를 생성합니다. 이 클래스는 JWT를 처리하는 다양한 메소드를 포함합니다.
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.JwtParser;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@Component
public class JwtTokenProvider {
private final String SECRET_KEY = "mySecretKey";
public String createToken(String username) {
Map claims = new HashMap<>();
return Jwts.builder()
.setClaims(claims)
.setSubject(username)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 10)) // 10시간
.signWith(SignatureAlgorithm.HS256, SECRET_KEY)
.compact();
}
public boolean validateToken(String token) {
try {
Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token);
return true;
} catch (Exception e) {
return false;
}
}
public Authentication getAuthentication(String token) {
// 사용자 인증 논리 구현
}
}
로그아웃 기능 구현
로그아웃 기능은 사용자가 인증을 종료하는 것으로, 보통 클라이언트에서 JWT 토큰을 삭제하는 방식으로 구현합니다. 더 나아가, 만약 서버에서 토큰을 무효화해야 한다면 블랙리스트를 관리해야 합니다.
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/auth")
public class AuthController {
@PostMapping("/logout")
public ResponseEntity> logout(HttpServletRequest request) {
String token = request.getHeader("Authorization");
// 토큰 블랙리스트 등록 로직
return ResponseEntity.ok("로그아웃 성공");
}
}
결론
이번 강좌를 통해 스프링 부트를 사용하여 JWT 기반의 로그인/로그아웃 기능을 구현하는 방법에 대해 살펴보았습니다. JWT는 웹 서비스의 인증 및 인가를 처리하는데 유용한 도구이며, 향후 애플리케이션을 개발할 때 큰 도움이 될 것입니다. 추가적으로, 보안 및 세션 관리에 관한 더 많은 정보를 연구하여 더 나은 보안성을 확보할 수 있습니다.
JW이 인증 및 인가와 관련된 지식을 확장하길 원하신다면, 보안 모범 사례와 더불어 OAuth2 및 OpenID Connect 같은 프로토콜을 학습하는 것을 추천합니다.