Skip to content

Commit

Permalink
fix: 토큰 발급 로직 수정 및 발급 토큰을 쿠키로 관리하도록 개선 #171 (#174)
Browse files Browse the repository at this point in the history
* feat: 쿠키 유틸리티 구현

* feat: 토큰 재발급 로직 구현

* feat: CORS 설정에 헤더 추가

* feat: 기존 컨트롤러 쿠키에 토큰 추가

* fix: 임시 회원가입 API로 다시 호출하여 토큰 받을 수 있게 수정

* fix: 오타 수정
  • Loading branch information
uwoobeat authored Jan 17, 2024
1 parent 674b975 commit 50e1230
Show file tree
Hide file tree
Showing 5 changed files with 95 additions and 17 deletions.
17 changes: 15 additions & 2 deletions src/main/java/com/depromeet/domain/auth/api/AuthController.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@
import com.depromeet.domain.auth.dto.request.MemberRegisterRequest;
import com.depromeet.domain.auth.dto.request.UsernamePasswordRequest;
import com.depromeet.domain.auth.dto.response.TokenPairResponse;
import com.depromeet.global.util.CookieUtil;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
Expand All @@ -22,6 +24,7 @@
public class AuthController {

private final AuthService authService;
private final CookieUtil cookieUtil;

@Operation(summary = "회원가입", description = "회원가입을 진행합니다.")
@PostMapping("/register")
Expand All @@ -35,14 +38,24 @@ public ResponseEntity<Void> memberRegister(@Valid @RequestBody MemberRegisterReq
public ResponseEntity<TokenPairResponse> memberTempRegister(
@Valid @RequestBody UsernamePasswordRequest request) {
TokenPairResponse response = authService.registerWithUsernameAndPassword(request);
return ResponseEntity.status(HttpStatus.CREATED).body(response);

String accessToken = response.accessToken();
String refreshToken = response.refreshToken();
HttpHeaders tokenHeaders = cookieUtil.generateTokenCookies(accessToken, refreshToken);

return ResponseEntity.status(HttpStatus.CREATED).headers(tokenHeaders).body(response);
}

@Operation(summary = "로그인", description = "토큰 발급을 위해 로그인을 진행합니다.")
@PostMapping("/login")
public ResponseEntity<TokenPairResponse> memberLogin(
@Valid @RequestBody UsernamePasswordRequest request) {
TokenPairResponse response = authService.loginMember(request);
return ResponseEntity.ok(response);

String accessToken = response.accessToken();
String refreshToken = response.refreshToken();
HttpHeaders tokenHeaders = cookieUtil.generateTokenCookies(accessToken, refreshToken);

return ResponseEntity.ok().headers(tokenHeaders).body(response);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import com.depromeet.global.error.exception.CustomException;
import com.depromeet.global.error.exception.ErrorCode;
import com.depromeet.global.util.MemberUtil;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.crypto.password.PasswordEncoder;
Expand All @@ -33,19 +34,21 @@ public void registerMember(MemberRegisterRequest request) {
}

public TokenPairResponse registerWithUsernameAndPassword(UsernamePasswordRequest request) {
validateUniqueUsername(request.username());

String encodedPassword = passwordEncoder.encode(request.password());
final Member member = Member.createGuestMember(request.username(), encodedPassword);

Member savedMember = memberRepository.save(member);
return getLoginResponse(savedMember);
}

private void validateUniqueUsername(String username) {
if (memberRepository.existsByUsername(username)) {
throw new CustomException(ErrorCode.MEMBER_ALREADY_REGISTERED);
Optional<Member> member = memberRepository.findByUsername(request.username());

// 첫 회원가입
if (member.isEmpty()) {
String encodedPassword = passwordEncoder.encode(request.password());
final Member savedMember =
Member.createGuestMember(request.username(), encodedPassword);
memberRepository.save(savedMember);
return getLoginResponse(savedMember);
}

// 토큰 만료된, 이미 임시 회원가입한 게스트 회원
Member existMember = member.get();
validatePasswordMatches(existMember, request.password());
return getLoginResponse(existMember);
}

public TokenPairResponse loginMember(UsernamePasswordRequest request) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ public CorsConfigurationSource corsConfigurationSource() {
configuration.addAllowedHeader("*");
configuration.addAllowedMethod("*");
configuration.setAllowCredentials(true);
configuration.addExposedHeader("Set-Cookie");

UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import com.depromeet.domain.auth.application.JwtTokenService;
import com.depromeet.domain.auth.dto.response.AccessToken;
import com.depromeet.global.util.CookieUtil;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
Expand All @@ -23,6 +24,7 @@
public class JwtAuthenticationFilter extends OncePerRequestFilter {

private final JwtTokenService jwtTokenService;
private final CookieUtil cookieUtil;

@Override
protected void doFilterInternal(
Expand Down Expand Up @@ -50,22 +52,23 @@ protected void doFilterInternal(
// ATK 만료되었고 RTK 만료되지 않았으면 RTK로 ATK, RTK 재발급
if (isAccessTokenExpired && !isRefreshTokenExpired) {
accessToken = jwtTokenService.reissueAccessToken(refreshToken);
jwtTokenService.reissueRefreshToken(refreshToken);
refreshToken = jwtTokenService.reissueRefreshToken(refreshToken);
}

// ATK 만료되지 않았고 RTK 만료되었으면 ATK로 ATK, RTK 재발급
if (!isAccessTokenExpired && isRefreshTokenExpired) {
AccessToken accessTokenDto = jwtTokenService.parseAccessToken(accessToken);
accessToken = jwtTokenService.reissueAccessToken(accessTokenDto);
jwtTokenService.reissueRefreshToken(accessTokenDto);
refreshToken = jwtTokenService.reissueRefreshToken(accessTokenDto);
}

// ATK, RTK 둘 다 만료되지 않았으면 RTK 재발급
if (!isAccessTokenExpired && !isRefreshTokenExpired) {
AccessToken accessTokenDto = jwtTokenService.parseAccessToken(accessToken);
jwtTokenService.reissueRefreshToken(accessTokenDto);
refreshToken = jwtTokenService.reissueRefreshToken(accessTokenDto);
}

cookieUtil.addTokenCookies(response, accessToken, refreshToken);
Authentication authentication = jwtTokenService.getAuthentication(accessToken);
SecurityContextHolder.getContext().setAuthentication(authentication);

Expand Down
58 changes: 58 additions & 0 deletions src/main/java/com/depromeet/global/util/CookieUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package com.depromeet.global.util;

import com.depromeet.infra.config.jwt.JwtProperties;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseCookie;
import org.springframework.stereotype.Component;

@Component
@RequiredArgsConstructor
public class CookieUtil {

private final SpringEnvironmentUtil springEnvironmentUtil;
private final JwtProperties jwtProperties;

public void addTokenCookies(
HttpServletResponse response, String accessToken, String refreshToken) {
HttpHeaders headers = generateTokenCookies(accessToken, refreshToken);
headers.forEach((key, value) -> response.addHeader(key, value.get(0)));
}

public HttpHeaders generateTokenCookies(String accessToken, String refreshToken) {

String sameSite = determineSameSitePolicy();

ResponseCookie accessTokenCookie =
ResponseCookie.from("accessToken", accessToken)
.path("/")
.maxAge(jwtProperties.accessTokenExpirationTime())
.secure(true)
.sameSite(sameSite)
.httpOnly(false)
.build();

ResponseCookie refreshTokenCookie =
ResponseCookie.from("refreshToken", refreshToken)
.path("/")
.maxAge(jwtProperties.refreshTokenExpirationTime())
.secure(true)
.sameSite(sameSite)
.httpOnly(false)
.build();

HttpHeaders headers = new HttpHeaders();
headers.add(HttpHeaders.SET_COOKIE, accessTokenCookie.toString());
headers.add(HttpHeaders.SET_COOKIE, refreshTokenCookie.toString());

return headers;
}

private String determineSameSitePolicy() {
if (springEnvironmentUtil.isProdProfile()) {
return "Strict";
}
return "None";
}
}

0 comments on commit 50e1230

Please sign in to comment.