API/이론

58. [ JAVA ] SpringBoot 에서 JWT적용 및 구현

천재단미 2025. 1. 12. 00:45
728x90
반응형

 

 

JWT(JSON Web Token)란?

 

JWT(JSON Web Token)는 당사자 간에 정보를 안전하게 전송하기 위한 콤팩트하고 독립적인 방식을 정의하는 개방형 표준입니다.

 

 

JWT의 특징

 

  • JSON 형식: 정보를 JSON 객체로 안전하게 전송합니다.
  • 구조: Header, Payload, Signature의 세 부분으로 구성됩니다.
  • 상태 비저장(Stateless): 서버에 별도의 저장소가 필요 없어 확장성이 뛰어납니다.
  • 자가 수용적(Self-contained): 토큰 자체에 필요한 모든 정보를 포함합니다.
  • 보안: 디지털 서명을 통해 데이터 무결성을 보장합니다.

 

보안을 강화하는 방법

 

  • 안전한 비밀 키 사용: JWT 서명에 사용되는 비밀 키를 안전하게 관리하고 정기적으로 교체합니다.
  • 짧은 만료 시간 설정: 토큰의 유효 기간을 짧게 설정하여 탈취된 토큰의 악용 가능성을 줄입니다.
  • HTTPS 사용: 모든 통신에 HTTPS를 적용하여 토큰 전송 시 암호화를 보장합니다.
  • 토큰 저장소 보안: 클라이언트 측에서 토큰을 안전하게 저장합니다 (예: HttpOnly 쿠키 사용).
  • 민감한 정보 제외: 토큰 페이로드에 민감한 정보를 포함하지 않습니다.
  • 토큰 무효화 메커니즘: 필요 시 토큰을 무효화할 수 있는 시스템을 구현합니다.
  • 적절한 서명 알고리즘 사용: 안전한 서명 알고리즘(예: RS256)을 사용합니다.
  • 토큰 재사용 방지: 각 요청마다 새로운 토큰을 발급하거나, nonce 값을 사용합니다.
  • 정기적인 보안 감사: JWT 구현을 주기적으로 검토하고 업데이트합니다.

 

장단점

 

장점

  1. 상태 비저장(Stateless): 서버에 별도의 저장소가 필요 없어 확장성이 뛰어납니다.
  2. 자가 수용적(Self-contained): 토큰 자체에 필요한 모든 정보를 포함하여 처리 속도가 빠릅니다.
  3. 보안성: 디지털 서명을 통해 데이터 무결성을 보장합니다.
  4. 다양한 플랫폼 지원: JSON 형식을 사용하여 여러 플랫폼에서 쉽게 사용할 수 있습니다.

단점

  1. 토큰 크기: 포함된 정보가 많을수록 토큰 크기가 커져 네트워크 부하가 증가할 수 있습니다.
  2. 보안 취약점: 토큰이 탈취되면 만료 전까지 대응이 어렵습니다.
  3. 토큰 관리: 한번 발급된 토큰은 서버에서 직접 제어가 어려워 관리에 주의가 필요합니다.
  4. 데이터 노출: 페이로드가 암호화되지 않아 중요 정보 포함 시 보안 위험이 있습니다.

 

구현방법

 

1. 의존성 추가

SpringBoot 를 이용하여 아래와 같이 추가를 진행하여 줍니다. 

 

2. JWT 유틸리티 클래스 생성

토큰 생성, 검증, 추출 등의 기능을 담당할 클래스를 만듭니다.

 

 
 

클래스생성

config 패키지 생성 

JWTConfig 클래스 생성 

 

 

@Configuration
public class JwtConfig {
    Key key;
    // 24시간 유효 토큰 만료 시간 설정 24 * 60 * 60 * 1000
    long tokenValidMillisecond = 1 * 60 * 60 * 1000;

    public JwtConfig(@Value("${jwt.secret}") String secretKey) {
        this.key = Keys.hmacShaKeyFor(secretKey.getBytes());
    }

    // 토큰 생성 함수
    public String createToken(Long userId) {
        // 현재 시간 년월일시분초 가져온다.
        Date now = new Date();
        // 만료 시간 계산한다.
        Date validity = new Date(now.getTime() + tokenValidMillisecond);

        return Jwts.builder()
                .subject(userId.toString())
                .issuedAt(now)
                .expiration(validity)
                .signWith(key, SignatureAlgorithm.HS256)
                .compact(); // 토큰 생성 함수
    }

    // 토큰에 저장된 데이터 가져오는 함수
    public Claims getTokenClaims(String token) {
        return Jwts.parser()
                .verifyWith((SecretKey) key)  // Key 를 SecretKey 로 캐스팅
                .build()
                .parseSignedClaims(token)
                .getPayload();
    }

 

3. 인증 필터 구현

요청에서 JWT를 추출하고 검증하는 필터를 만듭니다.

 

 
 

참조

   request.getRequestURI().equals("/api/v1/admin/login")

"/api/v1/admin/login" 와 동일한 

 request.getRequestURI().startsWith("/api/v1/admin/**")
"/api/v1/admin/"으로 시작하는 

 

public class JwtAuthenticationFilter extends OncePerRequestFilter {

    @Autowired
    private JwtConfig jwtConfig;

    @Override
    protected void doFilterInternal(HttpServletRequest request, 
    HttpServletResponse response, 
    FilterChain filterChain) throws ServletException, IOException {

        // signup, login 요청은 토큰 검증을 하지 않는다.
        if(request.getRequestURI().equals("/api/v1/admin/signup") ||
                request.getRequestURI().equals("/api/v1/admin/login") ) {
             
            filterChain.doFilter(request, response);
            return;
        }

        // 헤더에서 토큰을 가져온다.
        String bearerToken = request.getHeader("Authorization");
        // Bearer 토큰번호 입력

        if (bearerToken == null || bearerToken.isEmpty() ||
                !bearerToken.startsWith("Bearer ")) {
            response.setStatus(401);
            return;
        }

        // 토큰에서 Bearer 제거
        String token = bearerToken.substring(7);
        // 토큰 유효시간 검증
        Claims claims = jwtConfig.getTokenClaims(token);
        Date expiration = claims.getExpiration();
        if(expiration != null && expiration.before(new Date())) {
            response.setStatus(401);
            return;
        }

        UsernamePasswordAuthenticationToken authentication =
                new UsernamePasswordAuthenticationToken(claims.getSubject(),
                        null, null);
        // SecurityContextHolder 에 인증 정보를 저장한다.
        SecurityContextHolder.getContext().setAuthentication(authentication);
        filterChain.doFilter(request, response);

    }
}

 

 

4. Security 설정

JWT 필터를 Spring Security 설정에 추가합니다.

@Configuration
public class SecurityConfig {

    @Autowired
    private JwtAuthenticationFilter jwtAuthenticationFilter;

    // 암호화 처리 클래스 빈 등록
    // PasswordEncoder 는 Spring Security 에서 제공하는 인터페이스로
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) 
    throws Exception {
        http.csrf(AbstractHttpConfigurer::disable)
                .authorizeHttpRequests(auth -> auth
                        .requestMatchers("/api/v1/admin/signup",
                                "/api/v1/admin/login").permitAll()
                        .anyRequest().authenticated()  )
                .addFilterBefore(jwtAuthenticationFilter, 
                UsernamePasswordAuthenticationFilter.class);
        return http.build();
    }

}

 

 

5. 인증 컨트롤러 구현

사용자 인증 및 JWT 발급을 위한 엔드포인트를 만듭니다.

 

 

 

    @GetMapping("/api/v1/admin/crm/users/top-reviewers")
   public ResponseEntity<ReviewerListResponse> 
   getTopReviewers(@RequestHeader("Authorization") String token,
                    @RequestParam ("size")int size){
      ReviewerListResponse reviewerListResponse =
              crmService.getTopReviewers(token, size);
      return ResponseEntity.status(200).body(reviewerListResponse);

    }

 

728x90
반응형
home top bottom
}