0106

BE

[일지] 250311

이번에는 TokenFactory를 사용하여 RefreshToken을 생성한 뒤에, 해당 토큰 값을 제공하는 기능을 구현해보고자 한다.

오늘 스터디의 중요 내용은 아래와 같다.

  • TokenFactory.java

  • generateToken / verifyToken

  • header


TokenFactory.java

0) 전체 코드

Java
package com.thc.sprboot.util;

import java.util.Arrays;

public class TokenFactory {

    String prefix = "Bearer/u0020"; // 토큰의 접두사 정의

    // 사용자 ID를 기반으로 토큰을 생성하는 메서드
    public String generateToken(Long userId) {
        String token = null;
        // 토큰에 포함할 정보: 사용자 ID와 만료 일시
        try {
            String dueDate = new NowDate().due(60); // 현재 시간으로부터 60분 후의 만료 일시 계산
            System.out.println(dueDate);

            String data = userId + "_" + dueDate; // 사용자 ID와 만료 일시를 언더스코어로 구분하여 결합
            System.out.println("data : " + data);
            // AES256 암호화를 사용하여 데이터 인코딩 후 접두사와 함께 토큰 생성
            token = prefix + AES256Cipher.AES_Encode(null, data);
        } catch (Exception e) {
            // 예외 발생 시 아무 작업도 수행하지 않음 (예외 처리 필요)
        }
        return token; // 생성된 토큰 반환
    }

    // 주어진 토큰을 검증하고 사용자 ID를 반환하는 메서드
    public String verifyToken(String token) {
        String returnData = null;

        try {
            // 토큰에서 접두사를 제거하여 순수한 암호화된 데이터만 추출
            token = token.substring(prefix.length());
            // AES256 복호화를 사용하여 암호화된 데이터 복원
            String data = AES256Cipher.AES_Decode(null, token);

            // 복원된 데이터를 언더스코어로 분리하여 배열에 저장
            String[] arrayData = data.split("_");
            if (arrayData.length == 2) { // 배열의 길이가 2인지 확인
                Long userId = Long.parseLong(arrayData[0]); // 사용자 ID 추출
                String dueDate = arrayData[1]; // 만료 일시 추출
                String nowDate = new NowDate().getNow(); // 현재 시간 조회

                // 만료 일시와 현재 시간을 배열에 담아 정렬
                String[] dates = {dueDate, nowDate};
                Arrays.sort(dates);

                // 현재 시간이 만료 일시보다 빠른지 확인
                if (nowDate.equals(dates[0])) {
                    // 유효한 토큰인 경우 사용자 ID 반환
                    returnData = userId + "";
                }
            }
        } catch (Exception e) {
            // 예외 발생 시 아무 작업도 수행하지 않음 (예외 처리 필요)
        }
        return returnData; // 검증 결과로 사용자 ID 반환 (유효하지 않으면 null 반환)
    }

}

1) generateToken

Java
// 사용자 ID를 기반으로 토큰을 생성하는 메서드
    public String generateToken(Long userId) {
        String token = null;
        // 토큰에 포함할 정보: 사용자 ID와 만료 일시
        try {
            String dueDate = new NowDate().due(60); // 현재 시간으로부터 60분 후의 만료 일시 계산
            System.out.println(dueDate);

            String data = userId + "_" + dueDate; // 사용자 ID와 만료 일시를 언더스코어로 구분하여 결합
            System.out.println("data : " + data);
            // AES256 암호화를 사용하여 데이터 인코딩 후 접두사와 함께 토큰 생성
            token = prefix + AES256Cipher.AES_Encode(null, data);
        } catch (Exception e) {
            // 예외 발생 시 아무 작업도 수행하지 않음 (예외 처리 필요)
        }
        return token; // 생성된 토큰 반환
    }

(1) 의미

: 사용자 ID와 만료 일시를 기반으로 토큰을 생성하는 메서드

(2) 중요 변수

- dueDate : 토큰값의 유효 시간

- data : 사용자 ID와 만료일시를 언더스코어로 구분하여 결합 (이 방식은 추후 복호화할 때 필요한 정보를 포함하는 구조를 제공)

- token : 인코딩후 접두사와 함께 토큰 생성

2) verifyToken

Java
// 주어진 토큰을 검증하고 사용자 ID를 반환하는 메서드
    public String verifyToken(String token) {
        String returnData = null;

        try {
            // 토큰에서 접두사를 제거하여 순수한 암호화된 데이터만 추출
            token = token.substring(prefix.length());
            // AES256 복호화를 사용하여 암호화된 데이터 복원
            String data = AES256Cipher.AES_Decode(null, token);

            // 복원된 데이터를 언더스코어로 분리하여 배열에 저장
            String[] arrayData = data.split("_");
            if (arrayData.length == 2) { // 배열의 길이가 2인지 확인
                Long userId = Long.parseLong(arrayData[0]); // 사용자 ID 추출
                String dueDate = arrayData[1]; // 만료 일시 추출
                String nowDate = new NowDate().getNow(); // 현재 시간 조회

                // 만료 일시와 현재 시간을 배열에 담아 정렬
                String[] dates = {dueDate, nowDate};
                Arrays.sort(dates);

                // 현재 시간이 만료 일시보다 빠른지 확인
                if (nowDate.equals(dates[0])) {
                    // 유효한 토큰인 경우 사용자 ID 반환
                    returnData = userId + "";
                }
            }
        } catch (Exception e) {
            // 예외 발생 시 아무 작업도 수행하지 않음 (예외 처리 필요)
        }
        return returnData; // 검증 결과로 사용자 ID 반환 (유효하지 않으면 null 반환)
    }

(1) 의미

: 어진 토큰을 검증하고, 유효한 경우 해당 토큰에 연관된 사용자 ID를 반환하는 메서드

(2) 중요 변수

- token : 토큰에서 접두사를 제거하여 순수한 암호화된 데이터

- data : 복호화를 사용하여 암호화된 데이터

- arrayData : 복원된 데이터를 언더스코어로 분리하여 저장된 배열

(3) 토큰의 유효성 체크

Java
if (arrayData.length == 2) { // 복원된 데이터 배열의 길이가 2인지 확인 (사용자 ID와 만료 일시가 포함되어야 함)
   Long userId = Long.parseLong(arrayData[0]); // 배열의 첫 번째 요소에서 사용자 ID를 추출하여 Long 타입으로 변환
   String dueDate = arrayData[1]; // 배열의 두 번째 요소에서 만료 일시를 추출
   String nowDate = new NowDate().getNow(); // 현재 시간을 조회하여 nowDate 변수에 저장

   // 만료 일시와 현재 시간을 배열에 담아 정렬하여 비교 준비
   String[] dates = {dueDate, nowDate};
   Arrays.sort(dates); // 정렬된 배열의 첫 번째 요소는 더 이른 날짜가 됨

   // 현재 시간이 만료 일시보다 빠른지 확인
   if (nowDate.equals(dates[0])) { // nowDate가 dueDate보다 빠르면 유효한 토큰
       // 유효한 토큰인 경우 사용자 ID를 문자열로 변환하여 반환할 데이터에 저장
       returnData = userId + ""; 
   }
}

: 재 시간과 만료 일시를 비교하여 토큰의 유효성을 확인하여, 만료된 토큰을 사용하지 않도록 방지할 수 있으며, 시스템의 보안을 강화하는 데 기여한다.

- 핵심1 : 배열에서 추출하여 변수에 할당

- 핵심2 : 추출한 만료 일시와 현재 시간이 담긴 변수를 sort

- 핵심3 : 현재 시간이 만료 일시보다 빠른지 확인

UserServiceImpl.java

Java
@Override
public UserDto.LoginResDto login(UserDto.LoginReqDto params) {
    // 사용자 이름과 비밀번호를 사용하여 데이터베이스에서 사용자 정보를 조회
    User user = userRepository.findByUsernameAndPassword(params.getUsername(), params.getPassword());
    String refreshToken = null; // 리프레시 토큰을 저장할 변수 초기화
    UserDto.LoginResDto resDto = null; // 로그인 응답 DTO 초기화

    if (user == null) {
        // 사용자 정보가 없으면 (로그인 실패 시) 아무 작업도 수행하지 않음
    } else {
        // 사용자가 존재하는 경우, 사용자 ID를 기반으로 리프레시 토큰 생성
        refreshToken = new TokenFactory().generateToken(user.getId());
        // 생성된 리프레시 토큰을 검증하여 결과를 확인
        String afterValue = new TokenFactory().verifyToken(refreshToken);
        System.out.println("afterValue: " + afterValue); // 검증 결과 출력
    }

    // 로그인 응답 DTO를 빌더 패턴을 사용하여 생성하고 리프레시 토큰을 설정하여 반환
    return UserDto.LoginResDto.builder().refreshToken(refreshToken).build();
}

1) 의미

: 사용자 로그인을 처리하는 메서드로, 주어진 사용자 이름과 비밀번호를 사용하여 사용자의 인증을 수행하고, 인증이 성공하면 리프레시 토큰을 생성하여 반환하는 기능을 한다.

2) 키포인트

(1) 사용자 인증

- userRepository.findByUsernameAndPassword(params.getUsername(), params.getPassword())를 통해 데이터베이스에서 사용자 이름과 비밀번호에 해당하는 사용자를 조회한다.

(2) 리프레시 토큰 생성

- 사용자가 존재하는 경우, TokenFactory의 generateToken 메서드를 호출하여 사용자 ID를 기반으로 리프레시 토큰을 생성한다.

(3) 리프레시 토큰 검증

- 생성된 리프레시 토큰을 TokenFactory의 verifyToken 메서드를 사용하여 검증하고, 검증 결과를 출력한다.

UserRestController.java

Java
@PostMapping("/login") // 클라이언트의 POST 요청을 "/login" 경로에서 처리
public ResponseEntity<Void> login(@RequestBody UserDto.LoginReqDto params) {
    // 로그인 요청을 처리하고, 리프레시 토큰을 응답 헤더에 포함하여 반환
    return ResponseEntity.ok() // HTTP 200 OK 응답 생성
            .header("RefreshToken", userService.login(params).getRefreshToken()) // 리프레시 토큰을 응답 헤더에 추가
            .build(); // 응답 본문은 비어 있는 상태로 응답 생성
}

1) 의미

: 클라이언트의 로그인 요청을 받아 사용자를 인증하고, 성공적인 경우 리프레시 토큰을 응답 헤더에 포함하여 반환함으로써 사용자의 세션 관리를 가능하게 한다

2) 키포인트

(1) 로그인 처리 및 리프레시 토큰 반환:

- getRefreshToken() : LoginResDto으로 반한된 response값의 refreshToken을 getter로 가져온다.

- .header() : 가져온 값을 응답 헤더에 "RefreshToken"이라는 이름으로 추가한다.