스프링 부트 백엔드 개발 강좌, JWT로 로그인 로그아웃 구현, JWT 서비스 구현하기

JWT로 로그인/로그아웃 구현하기

목차

  1. 서론
  2. Spring Boot 소개
  3. JWT란?
  4. 프로젝트 설정
  5. 회원 가입 기능 구현
  6. 로그인 기능 구현
  7. JWT를 이용한 인증 및 인가
  8. JWT 토큰 생성 및 검증
  9. 로그아웃 기능 구현
  10. 결론

서론

요즘 웹 어플리케이션의 인증(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 같은 프로토콜을 학습하는 것을 추천합니다.