Skip to content

Commit

Permalink
Feat/LS-30: 회원가입, 로그인 응답 값 수정 & 회원 정보 얻기 API 구현 (#38)
Browse files Browse the repository at this point in the history
* chore: 회원 가입 리턴값에 액세스 토큰 추가

* chore: 로그인 응답 값 수정

* feat: 회원 정보 얻기 API 작성

* docs: AuthApi 수정

* feat: 리프레시 토큰을 로그인, 회원가입 리턴값에 포함

* fix: 토큰 재발급 로직 수정
  • Loading branch information
clean2001 authored Jul 22, 2024
1 parent cb3bd24 commit 6ce1eb1
Show file tree
Hide file tree
Showing 16 changed files with 220 additions and 92 deletions.
15 changes: 0 additions & 15 deletions layer-api/src/main/java/org/layer/config/SecurityConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,8 @@
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.method.HandlerMethod;

import java.util.Arrays;
import java.util.Collections;

@RequiredArgsConstructor
Expand Down Expand Up @@ -64,19 +62,6 @@ private void setHttp(HttpSecurity http) throws Exception {
);
}


// @Bean
// public CorsConfigurationSource corsConfigurationSource() {
// CorsConfiguration configuration = new CorsConfiguration();
// configuration.setAllowedOrigins(Collections.singletonList("*"));
// configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"));
// configuration.setAllowedHeaders(Arrays.asList("*"));
// UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
// source.registerCorsConfiguration("/**", configuration);
// return source;
// }


// swagger
private void permitSwaggerUri(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(authorizeHttpRequests -> authorizeHttpRequests
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,4 @@ public OperationCustomizer customizeOperation() {
return operation;
};
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.layer.common.annotation.MemberId;
import org.layer.domain.auth.controller.dto.*;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestBody;
Expand All @@ -20,22 +21,25 @@ public interface AuthApi {
@ApiResponses({
@ApiResponse(responseCode = "200", description = "로그인 성공",
headers = {
@Header(name = "Authorization", description = "소셜 액세스 토큰(Bearer 없이 토큰만)", schema = @Schema(type = "string", format = "jwt"), required = true)
@Header(name = "X-AUTH-TOKEN", description = "소셜 액세스 토큰(Bearer 없이 토큰만)", schema = @Schema(type = "string", format = "jwt"), required = true)
},
content = @Content(mediaType = "application/json", examples = {
@ExampleObject(name="로그인 성공", value = """
{
"memberId": 1,
"accessToken": "eyJhbGciOiJIUzUxMiJ9.eyJpYXQiOjE3MjA2OTcyMDksImV4cCI6MTcyMDY5OTAwOSwicm9sZSI6WyJVU0VSIl0sIm1lbWJlcklkIjoxfQ.OV-RWbIPZIQlMsPMR0reFHMFq9MNBKQwf7Hw7Uo0QbJPrTEACu0MqSJlv-gMtag1PhBxo7KB5dxEDza6QI06Zw",
"refreshToken": "eyJhbGciOiJIUzUxMiJ9.eyJpYXQiOjE3MjA2OTcyMTAsImV4cCI6MTcyMTkwNjgxMCwicm9sZSI6WyJVU0VSIl0sIm1lbWJlcklkIjoxfQ.fIVauBlL3GHLrVFJ1YwWb89RFwxa84Cql2WqEu4L258ebPJ04TkAGbqrCt7i-oEKI6dbvv0KDRKXkgDQH18kTA",
"memberRole": "USER"
"memberId": 3,
"name": "김회고",
"email": "[email protected]",
"memberRole": "USER",
"socialId": "123456789",
"socialType": "KAKAO",
"accessToken": "[토큰값]"
}
"""
)
})),
@ApiResponse(responseCode = "400", description = "로그인 실패 - 토큰이 유효하지 않음",
headers = {
@Header(name = "Authorization", description = "소셜 액세스 토큰(Bearer 없이 토큰만)", schema = @Schema(type = "string", format = "jwt"), required = true)
@Header(name = "X-AUTH-TOKEN", description = "소셜 액세스 토큰(Bearer 없이 토큰만)", schema = @Schema(type = "string", format = "jwt"), required = true)
},
content = @Content(mediaType = "application/json", examples = {
@ExampleObject(name="토큰이 유효하지 않음", value = """
Expand All @@ -48,7 +52,7 @@ public interface AuthApi {
})),
@ApiResponse(responseCode = "404", description = "로그인 실패 - 회원이 DB에 없음",
headers = {
@Header(name = "Authorization", description = "소셜 액세스 토큰(Bearer 없이 토큰만)", schema = @Schema(type = "string", format = "jwt"), required = true)
@Header(name = "X-AUTH_TOKEN", description = "소셜 액세스 토큰(Bearer 없이 토큰만)", schema = @Schema(type = "string", format = "jwt"), required = true)
},
content = @Content(mediaType = "application/json", examples = {
@ExampleObject(name="회원이 DB에 없음", value = """
Expand All @@ -66,24 +70,25 @@ public interface AuthApi {
@ApiResponses({
@ApiResponse(responseCode = "201", description = "회원가입 성공",
headers = {
@Header(name = "Authorization", description = "소셜 액세스 토큰(Bearer 없이 토큰만)", schema = @Schema(type = "string", format = "jwt"), required = true)
@Header(name = "X-AUTH_TOKEN", description = "소셜 액세스 토큰(Bearer 없이 토큰만)", schema = @Schema(type = "string", format = "jwt"), required = true)
},
content = @Content(mediaType = "application/json", examples = {
@ExampleObject(name="회원 가입 성공. 유저의 정보를 리턴", value = """
{
"memberId": 1,
"memberId": 3,
"name": "김회고",
"email": "layerkim@kakao.com",
"email": "kimlayer@kakao.com",
"memberRole": "USER",
"SocialId": "1234567890",
"socialType": "KAKAO"
"socialId": "123456789",
"socialType": "KAKAO",
"accessToken": "[토큰값]"
}
"""
)
})),
@ApiResponse(responseCode = "400", description = "회원가입 실패",
headers = {
@Header(name = "Authorization", description = "소셜 액세스 토큰", schema = @Schema(type = "string", format = "jwt"), required = true)
@Header(name = "X-AUTH-TOKEN", description = "소셜 액세스 토큰", schema = @Schema(type = "string", format = "jwt"), required = true)
},
content = @Content(mediaType = "application/json", examples = {
@ExampleObject(name="이미 가입된 회원", value = """
Expand All @@ -106,31 +111,47 @@ public interface AuthApi {
@Operation(summary = "로그아웃", description = "member_id를 전달하면 DB에서 리프레시 토큰을 지웁니다.")
public ResponseEntity<?> signOut(SignOutRequest signOutRequest);

@Operation(summary = "[인증 불필요] 토큰 재발급", description = "member_id를 전달하면 데이터베이스에 리프레시 토큰이 남아있지 확인하고 남아있다면 jwt(access + refresh)를 새로 발급합니다.")
@Operation(summary = "[인증 불필요] 토큰 재발급", description = "헤더에 refresh token과 바디에 member_id를 전달하면 데이터베이스에 리프레시 토큰이 남아있는지, 유효한지 확인하고 남아있다면 jwt(access + refresh)를 새로 발급합니다.")
@ApiResponses({
@ApiResponse(responseCode = "201", description = "토큰 재발급 성공",
content = @Content(mediaType = "application/json", examples = {
@ExampleObject(name="토큰 재발급 성공(리프레시 토큰이 DB에 남아있음, 기한 2주)", value = """
{
"memberId": 1,
"accessToken": "eyJhbGciOiJIUzUxMiJ9.eyJpYXQiOjE3MjA2OTMyOTMsImV4cCI6MTcyMDY5NTA5Mywicm9sZSI6WyJVU0VSIl0sIm1lbWJlcklkIjoxfQ.nt4Tj1jTihS-6U7j2wkzv4VbgzTkhSPWnjBC_yXe_GiOKn3eoJ0i9NuA7Dzw6e4w_B-ab_PHzdrhfzyeVoPJOg",
"refreshToken": "eyJhbGciOiJIUzUxMiJ9.eyJpYXQiOjE3MjA2OTMyOTMsImV4cCI6MTcyMTkwMjg5Mywicm9sZSI6WyJVU0VSIl0sIm1lbWJlcklkIjoxfQ.MROa3B266VcnQqGHpvu2Lh3JiwexOM4BTYQt_3Tbc7xMY1AwS5Z51oAyVVZdO7wTLDLiUNe73DwR-7HNejWEdA"
"memberId": 4,
"name": "김세정",
"email": "[email protected]",
"memberRole": "USER",
"socialId": "3612007072",
"socialType": "KAKAO",
"accessToken": "[토큰 값]",
"refreshToken": "[토큰 값]"
}
"""
)
})),
@ApiResponse(responseCode = "401", description = "토큰 재발급 실패",
headers = {
@Header(name = "Refresh", description = "자체 refresh token", schema = @Schema(type = "string", format = "jwt"), required = true)
},
content = @Content(mediaType = "application/json", examples = {
@ExampleObject(name="토큰 재발급 실패(리프레시 토큰이 DB에 없음)", value = """
{
"name": "NOT_FOUND_USER",
"message": "유효한 유저를 찾지 못했습니다."
}
"""
),
@ExampleObject(name="토큰 재발급 실패(잘못된 리프레시 토큰)", value = """
{
"name": "INVALID_REFRESH_TOKEN",
"message": "refresh token이 유효하지 않습니다."
}
"""
)
}))
})
public ResponseEntity<ReissueTokenResponse> reissueToken(@RequestBody ReissueTokenRequest reissueTokenRequest);
public ResponseEntity<ReissueTokenResponse> reissueToken(@RequestHeader(value = "Refresh", required = false) String refreshToken,
@RequestBody ReissueTokenRequest reissueTokenRequest);


@Operation(summary = "회원 탈퇴", description = "header Authorization에 액세스 토큰과 memberId를 전달하여 회원 탈퇴를 할 수 있습니다.")
Expand All @@ -140,6 +161,21 @@ public interface AuthApi {
})
public ResponseEntity<?> withdraw(WithdrawMemberRequest withdrawMemberRequest);

@Operation(summary = "회원 정보 얻기", description = "header Authorization에 Bearer [액세스토큰]을 넣어 인증하면 회원 정보를 얻을 수 있습니다.")
@ApiResponse(responseCode = "200", description = "회원 정보 얻기 성공",
content = @Content(mediaType = "application/json", examples = {
@ExampleObject(name="회원 정보 얻기 성공", value = """
{
"memberId": 3,
"name": "김회고",
"email": "[email protected]",
"memberRole": "USER",
"socialId": "123456789",
"socialType": "KAKAO"
}
""")}))
public MemberInfoResponse getMemberInfo(@MemberId Long memberId);

// TODO: 토큰 확인용 임시 API 추후 삭제
@Operation(summary = "[실제 사용 X] 구글 액세스 토큰 받기", description = "서버 쪽에서 토큰을 확인하기 위한 API입니다! (실제 사용 X, 추후 삭제 예정)")
public String googleTest(@RequestParam("code") String code);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.layer.common.annotation.DisableSwaggerSecurity;
import org.layer.common.annotation.MemberId;
import org.layer.domain.auth.controller.dto.*;
import org.layer.domain.auth.service.AuthService;
import org.layer.domain.auth.service.dto.SignInServiceResponse;
import org.layer.domain.auth.controller.dto.SignInResponse;
import org.layer.domain.auth.service.dto.SignUpServiceResponse;
import org.layer.domain.member.repository.MemberRepository;
import org.layer.oauth.service.GoogleService;
Expand All @@ -30,8 +31,8 @@ public class AuthController implements AuthApi {
@DisableSwaggerSecurity
@PostMapping("/sign-in")
public ResponseEntity<SignInResponse> signIn(@RequestHeader(SOCIAL_TOKEN_NAME) final String socialAccessToken, @RequestBody final SignInRequest signInRequest) {
SignInServiceResponse signInServiceResponse = authService.signIn(socialAccessToken, signInRequest.socialType());
return new ResponseEntity<>(SignInResponse.of(signInServiceResponse), HttpStatus.OK);
SignInResponse signInResponse = authService.signIn(socialAccessToken, signInRequest.socialType());
return new ResponseEntity<>(signInResponse, HttpStatus.OK);
}

// 회원가입 => 소셜로그인 했는데 유효한 유저가 없을 때 이름 입력하고 회원가입하는 과정
Expand Down Expand Up @@ -59,12 +60,19 @@ public ResponseEntity<?> withdraw(WithdrawMemberRequest withdrawMemberRequest) {

// 토큰 재발급
@PostMapping("/reissue-token")
public ResponseEntity<ReissueTokenResponse> reissueToken(ReissueTokenRequest reissueTokenRequest) {
public ResponseEntity<ReissueTokenResponse> reissueToken(@RequestHeader(value = "Refresh", required = false) String refreshToken,
ReissueTokenRequest reissueTokenRequest) {
return new ResponseEntity<>(
ReissueTokenResponse.of(authService.reissueToken(reissueTokenRequest.memberId())),
ReissueTokenResponse.of(authService.reissueToken(refreshToken, reissueTokenRequest.memberId())),
HttpStatus.CREATED);
}

// 액세스 토큰으로 회원 정보 얻기
@GetMapping("/member-info")
public MemberInfoResponse getMemberInfo(@MemberId Long memberId) {
return authService.getMemberInfo(memberId);
}

@DisableSwaggerSecurity
//== google OAuth2 test용 API 액세스 토큰 발급 ==//
@GetMapping("/oauth2/google")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package org.layer.domain.auth.controller.dto;

import lombok.Builder;
import org.layer.domain.member.entity.Member;
import org.layer.domain.member.entity.MemberRole;
import org.layer.domain.member.entity.SocialType;


@Builder
public record MemberInfoResponse(Long memberId,
String name,
String email,
MemberRole memberRole,
String socialId,
SocialType socialType) {
public static MemberInfoResponse of(Member member) {
return new MemberInfoResponse(member.getId(),
member.getName(),
member.getEmail(),
member.getMemberRole(),
member.getSocialId(),
member.getSocialType());
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,33 @@
package org.layer.domain.auth.controller.dto;

import lombok.Builder;
import org.layer.domain.auth.service.dto.ReissueTokenServiceResponse;
import org.layer.domain.jwt.JwtToken;
import org.layer.domain.member.entity.Member;
import org.layer.domain.member.entity.MemberRole;
import org.layer.domain.member.entity.SocialType;

public record ReissueTokenResponse(Long memberId, String accessToken, String refreshToken){
@Builder
public record ReissueTokenResponse(Long memberId,
String name,
String email,
MemberRole memberRole,
String socialId,
SocialType socialType,
String accessToken,
String refreshToken){
public static ReissueTokenResponse of(ReissueTokenServiceResponse rtsr) {
return new ReissueTokenResponse(rtsr.memberId(),
rtsr.jwtToken().getAccessToken(),
rtsr.jwtToken().getRefreshToken());
Member member = rtsr.member();
JwtToken jwtToken = rtsr.jwtToken();
return ReissueTokenResponse.builder()
.memberId(member.getId())
.name(member.getName())
.email(member.getEmail())
.memberRole(member.getMemberRole())
.socialType(member.getSocialType())
.socialId(member.getSocialId())
.accessToken(jwtToken.getAccessToken())
.refreshToken(jwtToken.getRefreshToken())
.build();
}
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,30 @@
package org.layer.domain.auth.controller.dto;

import org.layer.domain.auth.service.dto.SignInServiceResponse;
import lombok.Builder;
import org.layer.domain.jwt.JwtToken;
import org.layer.domain.member.entity.Member;
import org.layer.domain.member.entity.MemberRole;
import org.layer.domain.member.entity.SocialType;


public record SignInResponse(Long memberId, String accessToken, String refreshToken, MemberRole memberRole) {
public static SignInResponse of(SignInServiceResponse signInServiceResponse) {
return new SignInResponse(signInServiceResponse.memberId(),
signInServiceResponse.accessToken(),
signInServiceResponse.refreshToken(),
signInServiceResponse.memberRole()
);
@Builder
public record SignInResponse(Long memberId,
String name,
String email,
MemberRole memberRole,
String socialId,
SocialType socialType,
String accessToken,
String refreshToken) {
public static SignInResponse of(Member member, JwtToken jwtToken) {
return SignInResponse.builder()
.memberId(member.getId())
.name(member.getName())
.email(member.getEmail())
.memberRole(member.getMemberRole())
.socialType(member.getSocialType())
.socialId(member.getSocialId())
.accessToken(jwtToken.getAccessToken())
.refreshToken(jwtToken.getRefreshToken())
.build();
}
}
Loading

0 comments on commit 6ce1eb1

Please sign in to comment.