팀원들과의 회의 끝에 필자는 로그인, 로그아웃 기능을 구현하기로 했다.
로그인 기능은 사용자 인증에 사용할 jwt 토큰을 생성해서 반환하는 api이다.
String userId = requestDto.getUserId();
String password = requestDto.getPassword();
User user = userRepository.findByUserId(userId).orElseThrow(
() -> new IllegalArgumentException("등록된 사용자가 없습니다.")
);
if((!passwordEncoder.matches(password, user.getUserPassword()))) {
throw new IllegalArgumentException("비밀번호가 일치하지 않습니다.");
}
return jwtUtil.createToken(userId, UserRoleEnum.USER);
// Header KEY 값
public static final String AUTHORIZATION_HEADER = "Authorization";
// 사용자 권한 값의 KEY
public static final String AUTHORIZATION_KEY = "auth";
// Token 식별자
public static final String BEARER_PREFIX = "Bearer ";
// 토큰 만료시간
private final long TOKEN_TIME = 60 * 60 * 1000L; // 60분
Date date = new Date();
return BEARER_PREFIX +
Jwts.builder()
.setSubject(username) // 사용자 식별자값(ID)
.claim(AUTHORIZATION_KEY, role) // 사용자 권한
.setExpiration(new Date(date.getTime() + TOKEN_TIME)) // 만료 시간
.setIssuedAt(date) // 발급일
.signWith(key, signatureAlgorithm) // 암호화 알고리즘
.compact();
아이디와 비밀번호가 db의 내용과 일치하면 회원 아이디가 담긴 jwt accesstoken을 생성한다.
로그아웃은 jwt토큰을 헤더에서 삭제하는 api이다.
public String logout(@AuthenticationPrincipal UserDetailsImpl userDetails, HttpServletRequest request, HttpServletResponse response) {
request.removeAttribute(JwtUtil.AUTHORIZATION_HEADER);
return "로그아웃 성공" + response.getStatus();
}
로그인에는 문제없지만 accesstoken이 만료되면 서비스를 이용할 수 없는 문제가 생긴다.
그래서 accesstoken이 만료됐을 때 새로 발급하기 위한 수단으로 refreshtoken을 구현할 거다.
@Transactional
public TokenDto login(LoginRequestDto requestDto, HttpServletResponse response) {
String userId = requestDto.getUserId();
String password = requestDto.getPassword();
User user = userRepository.findByUserId(userId).orElseThrow(
() -> new IllegalArgumentException("등록된 사용자가 없습니다.")
);
if((!passwordEncoder.matches(password, user.getUserPassword()))) {
throw new IllegalArgumentException("비밀번호가 일치하지 않습니다.");
}
if(user.getRole().equals(UserRoleEnum.WITHDRAW)) {
throw new CustomException(ErrorCode.USER_NOT_VALID);
}
TokenDto token = jwtUtil.createToken(userId, UserRoleEnum.USER);
user.setRefreshToken(token.getRefreshToken());
userRepository.save(user);
return jwtUtil.createToken(userId, UserRoleEnum.USER);
}
// Header KEY 값
public static final String AUTHORIZATION_HEADER = "Authorization";
// 사용자 권한 값의 KEY
public static final String AUTHORIZATION_KEY = "auth";
// Token 식별자
public static final String BEARER_PREFIX = "Bearer ";
// 토큰 만료시간
private final long ACCESSTOKEN_TIME = 60 * 60 * 1000L; // 60분
private final long REFRESHTOKEN_TIME = 60 * 60 * 1000L * 72; // 720시간(3일)
public TokenDto createToken(String userId, UserRoleEnum role) {
Date date = new Date();
String accessToken = BEARER_PREFIX +
Jwts.builder()
.setSubject(userId) // 사용자 식별자값(ID)
.claim(AUTHORIZATION_KEY, role) // 사용자 권한
.setExpiration(new Date(date.getTime() + ACCESSTOKEN_TIME)) // 만료 시간
.setIssuedAt(date) // 발급일
.signWith(key, signatureAlgorithm) // 암호화 알고리즘
.compact();
String refreshToken = BEARER_PREFIX +
Jwts.builder()
.setSubject(userId) // 사용자 식별자값(ID)
.claim(AUTHORIZATION_KEY, role) // 사용자 권한
.setExpiration(new Date(date.getTime() + REFRESHTOKEN_TIME)) // 만료 시간
.setIssuedAt(date) // 발급일
.signWith(key, signatureAlgorithm) // 암호화 알고리즘
.compact();
return TokenDto.builder().accessToken(accessToken).refreshToken(refreshToken).key(userId).build();
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class TokenDto {
private UserRoleEnum grantType;
private String accessToken;
private String refreshToken;
private String key;
}
우선 반환할 토큰이 2개로 늘었기 때문에 accesstoken과 refreshtoken2개를 저장하는 TokenDto라는 dto객체를 만들었다.
거기에 원래 토큰을 생성하던 createToken메소드도 토큰 2개를 반환해야 하기 때문에 TokenDto로 반환하게 했다.
토큰의 만료시간은 createToken은 1시간, refreshToken은 3일로 설정했다.
토큰이 정상적으로 생성되면 헤더에 두가지의 토큰을 저장한다.
이제 refresh토큰의 유효성을 검사하고 새 access토큰을 발급하는 api를 만들어야 한다.
처음에는 refresh토큰을 헤더에서 불러오려고 했는데 swagger-ui에서 헤더를 2개 이상 보내는 법을 몰라서 body에 보내도록 바꾸었다.
@PostMapping("/reauth")
public String reAuth(@RequestBody HashMap<String, String> bodyJson, HttpServletResponse response) {
String refreshtoken = bodyJson.get("refreshToken");
String newAccessToken = authService.reAuth(refreshtoken);
response.setHeader(JwtUtil.AUTHORIZATION_HEADER, newAccessToken);
return "토큰 갱신 성공" + response.getStatus();
}
public String reAuth(String refreshtoken) {
String subToken = jwtUtil.substringToken(refreshtoken);
User user = userRepository.findByRefreshToken(refreshtoken).orElseThrow(
() -> new IllegalArgumentException("등록된 사용자가 없습니다.")
);
if(!jwtUtil.validateRefreshToken(subToken)) {
throw new IllegalArgumentException("토큰이 만료되었습니다.");
}
if(jwtUtil.substringToken(refreshtoken).equals(user.getRefreshToken())) {
throw new IllegalArgumentException("토큰이 일치하지 않습니다.");
}
String userId = jwtUtil.getUserInfoFromToken(subToken).getSubject();
return jwtUtil.createToken(userId, UserRoleEnum.USER).getAccessToken();
}
refresh토큰과 연결되어있는 id가 있는가? refresh토큰이 만료되었가? request로 보낸 refresh토큰과 db에 저장된 토큰이 일치하는가? 세 가지의 조건문으로 토큰의 유효성을 검사하고 검사가 완료되면 새 access 토큰을 만들어서 리턴한다.
다음날에는 팀원들과 구현이 완료된 기능들을 가지고 테스트를 해보면서 수정할 부분이 있는지 확인하고 있으면 수정할 계획이다.
'내일배움캠프' 카테고리의 다른 글
뉴스피드 프로젝트 5일차 (0) | 2024.06.11 |
---|---|
뉴스피드 프로젝트 4일차 (0) | 2024.06.11 |
뉴스피드 프로젝트 1일차 (0) | 2024.06.04 |
Spring 숙련주차 Part4 (2) | 2024.06.03 |
3진법 뒤집기 (0) | 2024.05.31 |