Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 7 additions & 18 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,11 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'

//redis
// Redis / WebSocket / Mongo
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
implementation 'org.springframework.boot:spring-boot-starter-websocket'
implementation 'org.springframework.boot:spring-boot-starter-data-mongodb'


/* Swagger / OpenAPI */
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0'

Expand All @@ -81,24 +80,14 @@ dependencies {
annotationProcessor 'org.projectlombok:lombok'

/* === 테스트 전용 === */
testImplementation 'org.springframework.boot:spring-boot-starter-test' // JUnit5 + Mockito(core) + AssertJ 포함
testImplementation 'org.springframework.security:spring-security-test' // SecurityMockMvcRequestPostProcessors 등
testImplementation 'org.mockito:mockito-junit-jupiter' // MockitoExtension
testImplementation 'org.assertj:assertj-core' // 최신 AssertJ

testRuntimeOnly 'org.junit.platform:junit-platform-launcher'

// security
implementation 'org.springframework.boot:spring-boot-starter-security'

// OAuth2
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'

// jwt
implementation 'io.jsonwebtoken:jjwt:0.9.1'
implementation 'javax.xml.bind:jaxb-api:2.3.1'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
testImplementation 'org.mockito:mockito-junit-jupiter'
testImplementation 'org.assertj:assertj-core'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}


tasks.named('test') {
useJUnitPlatform()

Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
package com.arom.with_travel.domain.member.controller;

import com.arom.with_travel.domain.member.dto.MemberSignupTokenResponse;
import com.arom.with_travel.domain.member.dto.MemberSignupRequestDto;
import com.arom.with_travel.domain.member.dto.MemberSignupResponseDto;
import com.arom.with_travel.domain.member.service.MemberService;
import com.arom.with_travel.domain.member.service.MemberSignupService;
import com.arom.with_travel.global.jwt.dto.response.AuthTokenResponse;
import com.arom.with_travel.global.jwt.service.TokenService;
import com.arom.with_travel.global.oauth2.dto.CustomOAuth2User;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
Expand All @@ -19,6 +24,7 @@ public class MemberSignupController {

private final MemberService memberService;
private final MemberSignupService memberSignupService;
private final TokenService tokenService;

// 추가 정보 등록
@PostMapping("/signup/register")
Expand All @@ -32,12 +38,16 @@ public ResponseEntity<MemberSignupResponseDto> fillExtraInfo(
}

// 현재 로그인 사용자 정보 조회
@GetMapping("/signup/register")
public ResponseEntity<MemberSignupResponseDto> getMyInfo(
@AuthenticationPrincipal CustomOAuth2User user) {
@GetMapping(value = "/signup/register",
produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<MemberSignupTokenResponse> getMyInfo(
@AuthenticationPrincipal CustomOAuth2User user,
HttpServletResponse response) {

MemberSignupResponseDto dto = memberSignupService
.getSignupInfo(user.getEmail());
return ResponseEntity.ok(dto);
AuthTokenResponse tokenDto = tokenService.issueTokenPair(user.getEmail(), response);
MemberSignupTokenResponse signupDto = new MemberSignupTokenResponse(dto, tokenDto);
return ResponseEntity.ok(signupDto);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.arom.with_travel.domain.member.dto;

import com.arom.with_travel.global.jwt.dto.response.AuthTokenResponse;
import com.nimbusds.oauth2.sdk.TokenResponse;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
@AllArgsConstructor
public class MemberSignupTokenResponse {
private MemberSignupResponseDto memberSignupDto;
private AuthTokenResponse tokenDto;
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,7 @@
public class JwtProperties {
private String issuer;
private String secretKey;
private int accessTokenExpireHours;
private int refreshTokenExpireDays;
private String refreshCookieName;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.arom.with_travel.global.jwt.dto.response;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
@AllArgsConstructor
public class AuthTokenResponse {
private String accessToken;
private String refreshToken;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,18 @@

import com.arom.with_travel.domain.member.Member;
import com.arom.with_travel.domain.member.service.MemberService;
import com.arom.with_travel.domain.member.service.MemberSignupService;
import com.arom.with_travel.global.exception.BaseException;
import com.arom.with_travel.global.exception.error.ErrorCode;
import com.arom.with_travel.global.jwt.config.JwtProperties;
import com.arom.with_travel.global.jwt.domain.RefreshToken;
import com.arom.with_travel.global.jwt.dto.response.AuthTokenResponse;
import com.arom.with_travel.global.jwt.repository.RefreshTokenRepository;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseCookie;
import org.springframework.stereotype.Service;

import java.time.Duration;
Expand All @@ -24,7 +28,7 @@ public class TokenService {
private final RefreshTokenService refreshTokenService;
private final MemberService memberService;
private final RefreshTokenRepository refreshTokenRepository;
private final MemberSignupService memberSignupService;
private final JwtProperties jwtProperties;

// 새로운 액세스 토큰 생성
public String createNewAccessToken(String refreshToken) {
Expand All @@ -51,4 +55,30 @@ public void validateRefreshTokenOrElseThrow(String refreshToken) {
throw BaseException.from(ErrorCode.INVALID_TOKEN);
}
}

public AuthTokenResponse issueTokenPair(String loginEmail, HttpServletResponse response) {

Member member = memberService.getUserByLoginEmailOrElseThrow(loginEmail);

String accessToken = tokenProvider.generateToken(
member,
Duration.ofHours(jwtProperties.getAccessTokenExpireHours())
);

String refreshToken = tokenProvider.generateToken(
member,
Duration.ofDays(jwtProperties.getRefreshTokenExpireDays())
);

// HttpOnly 쿠키 설정
ResponseCookie cookie = ResponseCookie.from(jwtProperties.getRefreshCookieName(), refreshToken)
.httpOnly(true)
.secure(true)
.path("/")
.maxAge(Duration.ofDays(jwtProperties.getRefreshTokenExpireDays()))
.build();
response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString());
Comment on lines +74 to +80
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

쿠키 설정 없이 문자열만 있으면 될 것 같습니다.


return new AuthTokenResponse(accessToken, refreshToken);
Copy link

Copilot AI Aug 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The refresh token is being returned in the response body, which contradicts the security goal stated in the PR description. Since the refresh token is already set as an HttpOnly cookie, it should not be included in the response body to maintain security.

Suggested change
return new AuthTokenResponse(accessToken, refreshToken);
return new AuthTokenResponse(accessToken);

Copilot uses AI. Check for mistakes.
}
}