반응형

JWT (JSON Web Token)과 OAuth 2.0을 조합하여 다중 인증 방식을 구현하는 방법을 다룹니다.
이 통합 방식은 다양한 인증 요구 사항을 처리하며, 확장성과 보안성을 동시에 제공합니다.


1. 다중 인증 방식의 필요성

1-1. JWT와 OAuth 2.0의 특징

  • JWT
    • 클라이언트와 서버 간에 인증 정보를 주고받기 위해 사용.
    • 간단하고 독립적인 인증 토큰으로 사용.
    • 보안 민감도가 낮은 서비스에 적합.
  • OAuth 2.0
    • 인증 및 권한 부여를 포함한 복잡한 보안 요구 사항을 지원.
    • 외부 API 및 서드파티 앱과 통합이 용이.
    • 보안 민감도가 높은 서비스에 적합.

1-2. 다중 인증 방식 통합의 필요성

  • OAuth 2.0을 통해 초기 사용자 인증을 처리한 후, JWT를 사용해 지속적인 요청을 처리합니다.
  • JWT는 빠르고 간단한 인증 처리에 적합하며, OAuth 2.0은 고급 보안을 제공합니다.

2. 구현 계획

  1. OAuth 2.0을 통해 Access Token 발급.
  2. Access Token으로 JWT 생성.
  3. JWT를 사용하여 API 요청 처리.
  4. 특정 요청에 대해 OAuth 2.0 토큰 재검증 지원.

3. 구현 코드

3-1. OAuth 2.0에서 JWT 생성

import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.stereotype.Service;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

@Service
public class JwtTokenService {

    private static final String SECRET_KEY = "jwtSecretKey";

    public String generateJwtFromOauthToken(String oauthAccessToken, String username) {
        Map<String, Object> claims = new HashMap<>();
        claims.put("oauthAccessToken", oauthAccessToken);

        return Jwts.builder()
                .setClaims(claims)
                .setSubject(username)
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60)) // 1시간 유효
                .signWith(SignatureAlgorithm.HS256, SECRET_KEY)
                .compact();
    }
}

3-2. JWT 기반 요청 처리

커스텀 필터를 사용해 JWT를 검증하고 사용자 정보를 로드합니다.

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;

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 OncePerRequestFilter {

    private static final String SECRET_KEY = "jwtSecretKey";
    private final UserDetailsService userDetailsService;

    public JwtAuthenticationFilter(UserDetailsService userDetailsService) {
        this.userDetailsService = userDetailsService;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
            throws ServletException, IOException {
        String authorizationHeader = request.getHeader("Authorization");

        if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
            String token = authorizationHeader.substring(7);

            try {
                Claims claims = Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody();
                String username = claims.getSubject();

                UserDetails userDetails = userDetailsService.loadUserByUsername(username);
                JwtAuthenticationToken authentication = new JwtAuthenticationToken(
                        userDetails, null, userDetails.getAuthorities());
                authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));

                SecurityContextHolder.getContext().setAuthentication(authentication);
            } catch (Exception e) {
                SecurityContextHolder.clearContext();
            }
        }

        chain.doFilter(request, response);
    }
}

3-3. 다중 인증 설정

Spring Security 설정 파일에서 두 가지 인증 방식을 통합합니다.

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
public class SecurityConfig {

    private final JwtAuthenticationFilter jwtAuthenticationFilter;
    private final CustomAuthenticationFilter oauthAuthenticationFilter;

    public SecurityConfig(JwtAuthenticationFilter jwtAuthenticationFilter, CustomAuthenticationFilter oauthAuthenticationFilter) {
        this.jwtAuthenticationFilter = jwtAuthenticationFilter;
        this.oauthAuthenticationFilter = oauthAuthenticationFilter;
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.csrf().disable()
            .authorizeRequests()
            .antMatchers("/public/**").permitAll()
            .anyRequest().authenticated()
            .and()
            .addFilterBefore(oauthAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
            .addFilterBefore(jwtAuthenticationFilter, CustomAuthenticationFilter.class);

        return http.build();
    }
}

4. 테스트

  1. OAuth 2.0 토큰 발급:
    • 권한 서버에서 Access Token을 발급받습니다.
  2. JWT 생성:
    • OAuth 2.0 Access Token으로 JWT를 생성합니다.
  3. API 호출:
    • JWT를 사용하여 인증된 API 요청을 테스트합니다.
    • 잘못된 JWT는 403 Forbidden을 반환해야 합니다.
  4. 다중 인증 확인:
    • 요청이 OAuth 2.0 또는 JWT 중 하나로 성공적으로 인증되는지 확인합니다.

5. 마무리

이번 학습에서는 JWT와 OAuth 2.0을 통합한 다중 인증 시스템을 설계하고 구현했습니다. 이를 통해 다양한 보안 요구 사항을 처리할 수 있는 확장성 높은 인증 체계를 구축할 수 있었습니다.
다음에서는 전자정부프레임워크의 인증 로그 관리에 대해 다룹니다.

반응형

+ Recent posts