반응형

전자정부프레임워크Spring Security를 활용하여 REST API 보안 강화를 구현하는 방법을 학습합니다. REST API는 클라이언트-서버 통신의 핵심으로, 안전한 인증과 데이터 보호가 필수적입니다.


1. REST API 보안의 필요성

REST API는 주로 클라이언트 애플리케이션(웹, 모바일)과 통신합니다. 이러한 통신은 네트워크를 통해 이루어지기 때문에 다음과 같은 보안 요구 사항이 필요합니다:

  1. 인증(Authentication): API 호출자가 신뢰할 수 있는지 확인합니다.
  2. 권한 부여(Authorization): 사용자의 요청이 특정 작업을 수행할 권한이 있는지 확인합니다.
  3. 데이터 보호(Data Protection): 민감한 정보를 안전하게 전달합니다.

2. 구현 계획

2-1. 주요 보안 강화 방법

  1. **JWT(JSON Web Token)**를 사용한 인증.
  2. API 요청에 대한 인증 및 권한 확인.
  3. HTTPS 프로토콜 사용 권장.

2-2. 구현 흐름

  1. JWT 토큰 발급 및 검증 구현.
  2. Spring Security와 연동하여 API 요청 보호.
  3. 인증 및 권한 부여 필터 설정.

3. 구현 코드

3-1. Maven 의존성 추가

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-api</artifactId>
    <version>0.11.5</version>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-impl</artifactId>
    <version>0.11.5</version>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-jackson</artifactId>
    <version>0.11.5</version>
</dependency>

3-2. JWT 유틸리티 클래스

import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import org.springframework.stereotype.Component;

import java.security.Key;
import java.util.Date;

@Component
public class JwtUtil {

    private static final String SECRET_KEY = "mySecretKey12345678901234567890"; // 반드시 안전하게 저장
    private static final long EXPIRATION_TIME = 86400000; // 1일

    private final Key key = Keys.hmacShaKeyFor(SECRET_KEY.getBytes());

    public String generateToken(String username) {
        return Jwts.builder()
                   .setSubject(username)
                   .setIssuedAt(new Date())
                   .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
                   .signWith(key, SignatureAlgorithm.HS256)
                   .compact();
    }

    public Claims validateToken(String token) {
        return Jwts.parserBuilder()
                   .setSigningKey(key)
                   .build()
                   .parseClaimsJws(token)
                   .getBody();
    }
}

3-3. JWT 필터 클래스

import io.jsonwebtoken.Claims;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

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 final JwtUtil jwtUtil;
    private final UserDetailsService userDetailsService;

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

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain) throws ServletException, IOException {
        String authHeader = request.getHeader("Authorization");
        if (authHeader != null && authHeader.startsWith("Bearer ")) {
            String token = authHeader.substring(7);
            try {
                Claims claims = jwtUtil.validateToken(token);
                String username = claims.getSubject();

                UserDetails userDetails = userDetailsService.loadUserByUsername(username);
                SecurityContextHolder.getContext().setAuthentication(
                        new JwtAuthenticationToken(userDetails, null, userDetails.getAuthorities())
                );
            } catch (Exception e) {
                response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Invalid Token");
                return;
            }
        }
        filterChain.doFilter(request, response);
    }
}

3-4. 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.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    private final JwtAuthenticationFilter jwtAuthenticationFilter;

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

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

        return http.build();
    }
}

4. API 테스트

4-1. JWT 발급

  1. /api/auth/login 엔드포인트에 POST 요청으로 사용자 정보를 전송하여 JWT를 발급받습니다.
  2. 발급받은 JWT는 이후 모든 API 요청의 Authorization 헤더에 추가합니다.

4-2. 보호된 API 호출

  • 보호된 경로(/api/**) 호출 시 JWT를 헤더에 포함하지 않으면 401 Unauthorized 응답을 받습니다.
  • 올바른 JWT를 제공하면 요청이 성공적으로 처리됩니다.

5. 마무리

이번 학습에서는 전자정부프레임워크와 Spring Security를 사용하여 REST API의 보안을 강화했습니다. JWT를 사용하여 클라이언트와 서버 간의 안전한 통신을 구현했으며, 향후 확장성과 유지보수성을 고려한 보안 체계를 설계하였습니다.
다음에서는 REST API와 CORS 설정을 학습합니다.

반응형

+ Recent posts