BE
[일지] 250311
이번에는 TokenFactory를 사용하여 RefreshToken을 생성한 뒤에, 해당 토큰 값을 제공하는 기능을 구현해보고자 한다.
오늘 스터디의 중요 내용은 아래와 같다.
TokenFactory.java
generateToken / verifyToken
header
TokenFactory.java
0) 전체 코드
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
// 사용자 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
// 주어진 토큰을 검증하고 사용자 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) 토큰의 유효성 체크
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
@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
@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"이라는 이름으로 추가한다.