Skip to content

Commit

Permalink
feat: 토큰 반환 시 cookie가 아닌 body 사용하도록 변경 (#206)
Browse files Browse the repository at this point in the history
* feat: 발급한 토큰을 header가 아닌 body로 반환하도록 수정

* refactor: 사용안하는 클래스와 메서드 제거

* test: 바뀐 API 스펙에 맞게 명세 수정
  • Loading branch information
ChooSeoyeon authored Aug 7, 2024
1 parent 5822571 commit d2c5876
Show file tree
Hide file tree
Showing 13 changed files with 123 additions and 95 deletions.
2 changes: 1 addition & 1 deletion backend/http/auth.http
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ POST {{base-url}}/auth/signup
Content-Type: application/json

{
"ci": "poke12345678"
"ci": "poke123456783"
}

### 토큰 재발급 API
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,12 @@

import com.zzang.chongdae.auth.service.AuthService;
import com.zzang.chongdae.auth.service.dto.LoginRequest;
import com.zzang.chongdae.auth.service.dto.LoginResponse;
import com.zzang.chongdae.auth.service.dto.RefreshRequest;
import com.zzang.chongdae.auth.service.dto.RefreshResponse;
import com.zzang.chongdae.auth.service.dto.SignupRequest;
import com.zzang.chongdae.auth.service.dto.SignupResponse;
import com.zzang.chongdae.auth.service.dto.TokenDto;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
Expand All @@ -21,16 +19,12 @@
public class AuthController {

private final AuthService authService;
private final CookieProducer cookieExtractor;
private final CookieConsumer cookieConsumer;

@PostMapping("/auth/login")
public ResponseEntity<Void> login(
@RequestBody @Valid LoginRequest request, HttpServletResponse servletResponse) {
TokenDto tokenDto = authService.login(request);
List<Cookie> cookies = cookieExtractor.extractAuthCookies(tokenDto);
cookieConsumer.addCookies(servletResponse, cookies);
return ResponseEntity.ok().build();
public ResponseEntity<LoginResponse> login(
@RequestBody @Valid LoginRequest request) {
LoginResponse response = authService.login(request);
return ResponseEntity.ok(response);
}

@PostMapping("/auth/signup")
Expand All @@ -41,11 +35,9 @@ public ResponseEntity<SignupResponse> signup(
}

@PostMapping("/auth/refresh")
public ResponseEntity<Void> refresh(
@RequestBody RefreshRequest request, HttpServletResponse servletResponse) {
TokenDto tokenDto = authService.refresh(request);
List<Cookie> cookies = cookieExtractor.extractAuthCookies(tokenDto);
cookieConsumer.addCookies(servletResponse, cookies);
return ResponseEntity.ok().build();
public ResponseEntity<RefreshResponse> refresh(
@RequestBody RefreshRequest request) {
RefreshResponse response = authService.refresh(request);
return ResponseEntity.ok(response);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@
import com.zzang.chongdae.auth.exception.AuthErrorCode;
import com.zzang.chongdae.global.exception.MarketException;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletResponse;
import java.util.Arrays;
import java.util.List;
import org.springframework.stereotype.Component;

@Component
Expand All @@ -14,12 +12,6 @@ public class CookieConsumer {
private static final String ACCESS_TOKEN_COOKIE_NAME = "access_token";
private static final String REFRESH_TOKEN_COOKIE_NAME = "refresh_token";

public void addCookies(HttpServletResponse servletResponse, List<Cookie> cookies) {
for (Cookie cookie : cookies) {
servletResponse.addCookie(cookie);
}
}

public String getAccessToken(Cookie[] cookies) {
return getTokenByCookieName(ACCESS_TOKEN_COOKIE_NAME, cookies);
}
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

import com.zzang.chongdae.auth.exception.AuthErrorCode;
import com.zzang.chongdae.auth.service.dto.LoginRequest;
import com.zzang.chongdae.auth.service.dto.LoginResponse;
import com.zzang.chongdae.auth.service.dto.RefreshRequest;
import com.zzang.chongdae.auth.service.dto.RefreshResponse;
import com.zzang.chongdae.auth.service.dto.SignupRequest;
import com.zzang.chongdae.auth.service.dto.SignupResponse;
import com.zzang.chongdae.auth.service.dto.TokenDto;
Expand All @@ -24,11 +26,12 @@ public class AuthService {
private final JwtTokenProvider jwtTokenProvider;
private final NicknameGenerator nickNameGenerator;

public TokenDto login(LoginRequest request) {
public LoginResponse login(LoginRequest request) {
String password = passwordEncoder.encode(request.ci());
MemberEntity member = memberRepository.findByPassword(password)
.orElseThrow(() -> new MarketException(AuthErrorCode.INVALID_PASSWORD));
return jwtTokenProvider.createAuthToken(member.getId().toString());
TokenDto tokenDto = jwtTokenProvider.createAuthToken(member.getId().toString());
return new LoginResponse(tokenDto);
}

@Transactional
Expand All @@ -39,12 +42,14 @@ public SignupResponse signup(SignupRequest request) {
}
MemberEntity member = new MemberEntity(nickNameGenerator.generate(), password);
MemberEntity savedMember = memberRepository.save(member);
return new SignupResponse(savedMember);
TokenDto tokenDto = jwtTokenProvider.createAuthToken(savedMember.getId().toString());
return new SignupResponse(savedMember, tokenDto);
}

public TokenDto refresh(RefreshRequest request) {
public RefreshResponse refresh(RefreshRequest request) {
Long memberId = jwtTokenProvider.getMemberIdByRefreshToken(request.refreshToken());
return jwtTokenProvider.createAuthToken(memberId.toString());
TokenDto tokenDto = jwtTokenProvider.createAuthToken(memberId.toString());
return new RefreshResponse(tokenDto);
}

public MemberEntity findMemberByAccessToken(String token) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.zzang.chongdae.auth.service.dto;

public record LoginResponse(String accessToken, String refreshToken) {

public LoginResponse(TokenDto tokenDto) {
this(tokenDto.accessToken(), tokenDto.refreshToken());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.zzang.chongdae.auth.service.dto;

public record RefreshResponse(String accessToken, String refreshToken) {

public RefreshResponse(TokenDto tokenDto) {
this(tokenDto.accessToken(), tokenDto.refreshToken());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.zzang.chongdae.auth.service.dto;

import com.zzang.chongdae.member.repository.entity.MemberEntity;

public record SignupMemberResponseItem(Long id, String nickname) {

public SignupMemberResponseItem(MemberEntity member) {
this(member.getId(), member.getNickname());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

import com.zzang.chongdae.member.repository.entity.MemberEntity;

public record SignupResponse(Long memberId, String nickname) {
public record SignupResponse(SignupMemberResponseItem member, SignupTokenResponseItem token) {

public SignupResponse(MemberEntity member) {
this(member.getId(), member.getNickname());
public SignupResponse(MemberEntity savedMember, TokenDto tokenDto) {
this(new SignupMemberResponseItem(savedMember), new SignupTokenResponseItem(tokenDto));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.zzang.chongdae.auth.service.dto;

public record SignupTokenResponseItem(String accessToken, String refreshToken) {

public SignupTokenResponseItem(TokenDto tokenDto) {
this(tokenDto.accessToken(), tokenDto.refreshToken());
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
package com.zzang.chongdae.auth.integration;

import static com.epages.restdocs.apispec.ResourceDocumentation.headerWithName;
import static com.epages.restdocs.apispec.ResourceDocumentation.resource;
import static com.epages.restdocs.apispec.ResourceSnippetParameters.builder;
import static com.epages.restdocs.apispec.Schema.schema;
import static io.restassured.RestAssured.given;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.restassured.RestAssuredRestDocumentation.document;

import com.epages.restdocs.apispec.HeaderDescriptorWithType;
import com.epages.restdocs.apispec.ResourceSnippetParameters;
import com.zzang.chongdae.auth.service.dto.LoginRequest;
import com.zzang.chongdae.auth.service.dto.RefreshRequest;
Expand Down Expand Up @@ -37,18 +35,17 @@ class Login {
List<FieldDescriptor> requestDescriptors = List.of(
fieldWithPath("ci").description("회원 식별자 인증 정보")
);
List<HeaderDescriptorWithType> responseHeaderDescriptors = List.of(
headerWithName("Set-Cookie").description("""
access_token=a.b.c; Path=/; HttpOnly \n
refresh_token=a.b.c; Path=/; HttpOnly
""")
List<FieldDescriptor> responseDescriptors = List.of(
fieldWithPath("accessToken").description("accessToken"),
fieldWithPath("refreshToken").description("refreshToken")
);
ResourceSnippetParameters successSnippets = builder()
.summary("회원 로그인")
.description("회원 식별자 인증 정보로 로그인 합니다.")
.requestFields(requestDescriptors)
.responseFields(responseDescriptors)
.requestSchema(schema("LonginRequest"))
.responseHeaders(responseHeaderDescriptors)
.responseSchema(schema("LoginResponse"))
.build();

MemberEntity member;
Expand Down Expand Up @@ -82,8 +79,10 @@ class Signup {
fieldWithPath("ci").description("회원 식별자 인증 정보")
);
List<FieldDescriptor> responseDescriptors = List.of(
fieldWithPath("memberId").description("회원 id"),
fieldWithPath("nickname").description("닉네임")
fieldWithPath("member.id").description("회원 id"),
fieldWithPath("member.nickname").description("닉네임"),
fieldWithPath("token.accessToken").description("accessToken"),
fieldWithPath("token.refreshToken").description("refreshToken")
);
ResourceSnippetParameters successSnippets = builder()
.summary("회원 가입")
Expand Down Expand Up @@ -144,20 +143,19 @@ void should_throwException_when_givenAlreadyExistMember() {
class Refresh {

List<FieldDescriptor> requestDescriptors = List.of(
fieldWithPath("refreshToken").description("재발급에 필요한 토큰")
fieldWithPath("refreshToken").description("재발급에 필요한 refreshToken")
);
List<HeaderDescriptorWithType> responseHeaderDescriptors = List.of(
headerWithName("Set-Cookie").description("""
access_token=a.b.c; Path=/; HttpOnly \n
refresh_token=a.b.c; Path=/; HttpOnly
""")
List<FieldDescriptor> responseDescriptors = List.of(
fieldWithPath("accessToken").description("재발급한 accessToken"),
fieldWithPath("refreshToken").description("재발급한 refreshToken")
);
ResourceSnippetParameters successSnippets = builder()
.summary("토큰 재발급")
.description("토큰을 재발급합니다.")
.requestFields(requestDescriptors)
.responseHeaders(responseHeaderDescriptors)
.responseFields(responseDescriptors)
.requestSchema(schema("RefreshRequest"))
.requestSchema(schema("RefreshResponse"))
.build();
ResourceSnippetParameters failedSnippets = builder()
.requestFields(requestDescriptors)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,29 +1,35 @@
package com.zzang.chongdae.global.helper;

import com.zzang.chongdae.auth.service.dto.LoginRequest;
import io.restassured.RestAssured;
import com.zzang.chongdae.auth.service.dto.TokenDto;
import io.restassured.http.Cookie;
import io.restassured.http.Cookies;
import org.springframework.http.MediaType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;


@Component
public class CookieProvider {

private final TestTokenProvider tokenProvider;

@Autowired
public CookieProvider(TestTokenProvider tokenProvider) {
this.tokenProvider = tokenProvider;
}

public Cookies createCookies() {
return RestAssured.given().log().all()
.contentType(MediaType.APPLICATION_JSON_VALUE)
.body(new LoginRequest("dora1234"))
.when().post("/auth/login")
.then().log().all()
.extract().detailedCookies();
TokenDto tokenDto = tokenProvider.createTokens();
return createCookiesFromTokenDto(tokenDto);
}

public Cookies createCookiesWithCi(String ci) {
return RestAssured.given().log().all()
.contentType(MediaType.APPLICATION_JSON_VALUE)
.body(new LoginRequest(ci))
.when().post("/auth/login")
.then().log().all()
.extract().detailedCookies();
TokenDto tokenDto = tokenProvider.createTokensWithCi(ci);
return createCookiesFromTokenDto(tokenDto);
}

private Cookies createCookiesFromTokenDto(TokenDto tokenDto) {
Cookie accessToken = new Cookie.Builder("access_token", tokenDto.accessToken()).build();
Cookie refreshToken = new Cookie.Builder("refresh_token", tokenDto.refreshToken()).build();
return new Cookies(accessToken, refreshToken);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.zzang.chongdae.global.helper;

import com.zzang.chongdae.auth.service.dto.LoginRequest;
import com.zzang.chongdae.auth.service.dto.TokenDto;
import io.restassured.RestAssured;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;

@Component
public class TestTokenProvider {

public TokenDto createTokens() { // TODO: 로그인 API 호출 대신 토큰 생성 로직으로 대체하기
return RestAssured.given().log().all()
.contentType(MediaType.APPLICATION_JSON_VALUE)
.body(new LoginRequest("dora1234"))
.when().post("/auth/login")
.then().log().all()
.extract().response().as(TokenDto.class);
}

public TokenDto createTokensWithCi(String ci) {
return RestAssured.given().log().all()
.contentType(MediaType.APPLICATION_JSON_VALUE)
.body(new LoginRequest(ci))
.when().post("/auth/login")
.then().log().all()
.extract().response().as(TokenDto.class);
}
}

0 comments on commit d2c5876

Please sign in to comment.