스프링 부트 백엔드 개발 강좌, JWT로 로그인 로그아웃 구현, 토큰 기반 인증이란

1. 서론

현대 웹 애플리케이션에서는 사용자 인증과 권한 관리를 효율적으로 수행하기 위한 여러 가지 방법이 존재합니다. 이 중에서 JSON Web Token(JWT)은 널리 사용되는 인증 방법 중 하나입니다. 특히 스프링 부트(Spring Boot)를 사용하는 백엔드 개발자에게는 JWT를 통해 로그인과 로그아웃 기능을 구현하여 보다 안전하고 효율적인 사용자 인증 시스템을 구축할 수 있습니다. 본 강좌에서는 JWT의 개념, 스프링 부트를 이용한 JWT 기반 인증 구현 방법, 로그인과 로그아웃의 절차에 대해 상세히 설명하겠습니다.

2. JWT란 무엇인가?

JWT는 JSON 웹 토큰의 약자로, 사용자와 서버 간의 정보를 안전하게 전송하기 위한 개방형 표준(RFC 7519)입니다. JWT는 보통 다음과 같은 경우에 사용됩니다:

  • 사용자 인증
  • 정보 교환
  • 권한 부여

JWT의 특징은 다음과 같습니다:

  • 자체적으로 정보를 담을 수 있다.
  • 서명(Signature)으로 변조를 방지할 수 있다.
  • HTTP 전송에서 쉽게 사용할 수 있다.

JWT는 세 부분으로 구성되어 있습니다: 헤더(Header), 페이로드(Payload), 서명(Signature). 이들 각각에 대한 자세한 설명은 다음과 같습니다.

2.1 헤더 (Header)

JWT의 헤더는 두 가지 정보를 포함합니다. 즉, 토큰의 유형(type)과 사용할 알고리즘(예: HMAC SHA256 또는 RSA) 입니다.

{
    "alg": "HS256",
    "typ": "JWT"
}

2.2 페이로드 (Payload)

페이로드는 JWT의 두 번째 부분으로, 사용자에 대한 정보와 클레임(claims)을 담고 있습니다. 클레임은 사용자의 정보, 권한 및 기타 메타데이터를 포함할 수 있습니다. 예를 들어:

{
    "sub": "1234567890",
    "name": "John Doe",
    "iat": 1516239022
}

이 예시에서 “sub”는 사용자의 고유 ID, “name”은 사용자명, “iat”는 발행된 시간을 나타냅니다.

2.3 서명 (Signature)

마지막으로 서명은 헤더와 페이로드를 결합하고 비밀 키를 사용하여 해시를 생성한 것입니다. 이렇게 하면 데이터의 무결성을 확인할 수 있습니다. 예를 들어:

HMACSHA256(
    base64UrlEncode(header) + "." +
    base64UrlEncode(payload),
    secret)

이 과정을 통해 JWT를 생성하고, 수신자는 소유한 비밀 키를 사용하여 토큰의 유효성을 검사할 수 있습니다.

3. 스프링 부트 설정

스프링 부트를 이용하여 JWT 기반의 사용자 인증 시스템을 구현하기 위해, 먼저 스프링 부트 프로젝트를 설정해야 합니다. Spring Initializr를 사용하여 새로운 프로젝트를 생성하고 필요한 종속성(Ddependencies)을 추가해야 합니다. 이번 강좌에서는 Spring Web, Spring Security, 그리고 jjwt 라이브러리를 사용할 것입니다.

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-security'
    implementation 'io.jsonwebtoken:jjwt:0.9.1'
}

4. 스프링 시큐리티와 JWT 설정

스프링 시큐리티(Spring Security)를 사용하여 기본 인증 및 권한 부여를 설정할 수 있습니다. 이를 위해 SecurityConfig 클래스를 작성하고 HTTP 요청에 대한 보안 규칙을 정의해야 합니다.

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
            .authorizeRequests()
            .antMatchers("/api/auth/**").permitAll()
            .anyRequest().authenticated()
            .and()
            .sessionManagement()
            .sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    }
}

위 설정에서는 /api/auth/** 라우트를 모든 요청에 대해 허용하고, 나머지 요청은 인증이 필요하도록 설정했습니다. 세션 관리는 STATLESS로 설정하여 JWT를 사용한 토큰 기반 인증을 활용합니다.

5. 사용자 인증과 JWT 발급

사용자가 로그인하면, 서버는 해당 사용자의 정보를 기반으로 JWT를 발급합니다. 사용자 인증을 위한 AuthController 클래스를 작성하고, 로그인 요청을 처리하는 메서드를 구현해야 합니다.

@RestController
@RequestMapping("/api/auth")
public class AuthController {
    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private JwtProvider jwtProvider;

    @PostMapping("/login")
    public ResponseEntity authenticateUser(@RequestBody LoginRequest loginRequest) {
        Authentication authentication = authenticationManager.authenticate(
            new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword()));

        SecurityContextHolder.getContext().setAuthentication(authentication);

        String jwt = jwtProvider.generateToken(authentication);
        return ResponseEntity.ok(new JwtResponse(jwt));
    }
}

6. 로그아웃 구현

JWT는 상태 값을 서버에 저장하지 않기 때문에 로그아웃 구현이 다른 방식과는 다릅니다. 일반적으로 클라이언트 측에서 JWT를 삭제하거나 만료시키는 방식으로 로그아웃을 처리합니다. 예를 들어 클라이언트가 로그아웃 요청을 보내면, JWT 토큰을 삭제하고 이후 요청에서는 더 이상 이 토큰을 사용하지 않도록 합니다.

@PostMapping("/logout")
    public ResponseEntity logoutUser() {
        // 클라이언트 토큰 삭제를 위한 응답 처리 전달
        return ResponseEntity.ok(new MessageResponse("User logged out successfully!"));
    }

7. JWT 유효성 검증

서버에서 JWT를 검증하는 방법은 토큰의 서명을 확인하고, 유효 기간(expiration)을 체크하는 것입니다. 이 과정을 위해 JwtProvider를 작성할 수 있습니다.

@Component
public class JwtProvider {
    @Value("${jwt.secret}")
    private String jwtSecret;

    public String generateToken(Authentication authentication) {
        return Jwts.builder()
            .setSubject(authentication.getName())
            .setIssuedAt(new Date())
            .setExpiration(new Date((new Date()).getTime() + 86400000)) // 1일
            .signWith(SignatureAlgorithm.HS512, jwtSecret)
            .compact();
    }

    public boolean validateToken(String token) {
        try {
            Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token);
            return true;
        } catch (Exception e) {
            return false;
        }
    }
}

8. 결론

JWT를 통해 스프링 부트에서 효율적으로 사용자 인증을 구현할 수 있음을 보았습니다. 이와 같은 토큰 기반 인증 방식을 통해 서버의 상태를 유지하지 않고도 보다 유연한 애플리케이션 구조를 만들 수 있습니다. 본 강좌에서 설명한 내용을 바탕으로 여러분의 프로젝트에 JWT를 활용하여 사용자 인증 시스템을 구현해 보시기 바랍니다.

앞으로도 다양한 웹 개발 관련 주제로 여러분께 유익한 정보와 강좌를 제공할 예정입니다. 감사합니다.