Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: 회원 생성 및 로그인 응답 기능 구현 #47

Merged
merged 51 commits into from
Nov 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
2cb3959
feat: 회원 엔티티 생성 및 테스트코드 추가
parksey Oct 29, 2023
3c119f8
feat: 카카오 OAuth 환경변수 추가 및 클래스 바인딩
parksey Oct 29, 2023
729d7d2
feat: authorization code를 받기 위한 queryString generator 추가
parksey Oct 29, 2023
9cbfc2e
feat: Authorization code의 parameter 만드는 로직 분리 및 테스트 코드 추가
parksey Oct 30, 2023
e056905
feat: 회원 가입/로그인 요청 api 및 소셜 로그인 페이지 반환
parksey Oct 30, 2023
0dc50e4
refactor: member관련 클래스 네이밍과 폴더 위치 변경
parksey Oct 30, 2023
233661a
refactor: 로그인 페이지 요청 방식 Resttemplate -> response (redirect)하도록 변경
parksey Oct 30, 2023
18f3496
style: 코드 포맷 재적용 및 사용하지 않는 클래스 삭제
parksey Oct 30, 2023
a1e7533
chore: config 파일 업데이트
parksey Oct 30, 2023
b5163eb
refactor: 테스트 코드 추가 및 코드 포맷 재적용
parksey Oct 30, 2023
9c10d45
refactor: 사용하지 않는 코드 제거
parksey Oct 30, 2023
92cb531
refactor: CRLF -> LF로 변경
parksey Oct 30, 2023
a7291a8
fix: config 커밋, config 최근 커밋으로 변경
parksey Oct 30, 2023
47ef3ea
feat: 테스트 코드 추가 및 패키지 구조 변경
parksey Oct 30, 2023
5a76a50
refactor: revert merge
parksey Oct 30, 2023
ab0063d
Merge branch 'develop' into feature/#5
parksey Oct 30, 2023
ab0b0ab
fix: merge confilt해결 및 예외처리 추가
parksey Oct 30, 2023
dff5e2e
test: oauth properties가 없을 때의 테스트코드 추가
parksey Oct 30, 2023
31407ab
feat: 코드리뷰에 따른 기능 분리 및 테스트 코드 변경
parksey Oct 31, 2023
448011e
fix: 테스트코드 관련 code smell 제거
parksey Oct 31, 2023
c421394
feat: Authorization grant 받기 예외 코드 및 테스트 코드 추가
parksey Nov 1, 2023
40cac6f
refactor: develop브랜치 merge
parksey Nov 1, 2023
36a64b1
feat: Authorization Token 요청 및 반환 코드, 에러 반환 테스트 코드 추가
parksey Nov 1, 2023
1eb8eb4
refactor: AuthenticationService에서 서버에 요청보내는 로직 OAuth2AuthorizationSer…
parksey Nov 1, 2023
5ef8308
test: 로그인 요청 테스트 코드 추가
parksey Nov 1, 2023
82925fa
feat: 토큰 발급 요청 기능 테스트 코드 추가 및 RestTemplate 필드변수로 변경
parksey Nov 1, 2023
95bea5d
refactor: develop 브랜치 merge
parksey Nov 1, 2023
7e53464
test: restTemplate 및 서비스 테스트 추가
parksey Nov 2, 2023
41b234b
refactor: 에러 메세지 이름 변경
parksey Nov 2, 2023
dffed00
refacotr: 변수명 및 entity default 명 변경
parksey Nov 2, 2023
5c32df9
feat: 토큰 정보 조회 기능 및 테스트 추가
parksey Nov 2, 2023
436dbdc
feat: 사용자 토큰 정보 조회 및 테스트 코드 & Resttemplate 테크트 코드 변경
parksey Nov 3, 2023
5b94df4
refactor: develop 브랜치 merge
parksey Nov 3, 2023
9995d28
Merge branch 'develop' of https://github.com/team-moabam/moabam-BE in…
parksey Nov 3, 2023
f58059b
fix: encoding, formatting, tab 문제로 인한 파일 삭제 후 다시 작성
parksey Nov 3, 2023
d1db0e4
feat: JWT 토큰 제공 서비스 및 테스트 코드 추가
parksey Nov 4, 2023
7862353
feat: 토큰 인증 코드 및 테스트 코드 작성
parksey Nov 4, 2023
dbf9488
feat: 로그인 및 회원가입 기능 추가
parksey Nov 5, 2023
5aa6cc0
feat: 회원 로그인 테스트 코드 추가
parksey Nov 6, 2023
cf030fc
chore: 코드 포메팅 재 설정
parksey Nov 6, 2023
f706306
Merge branch 'develop' of https://github.com/team-moabam/moabam-BE in…
parksey Nov 6, 2023
64d1609
refactor: develop 변경사항 merge
parksey Nov 6, 2023
672840e
feat: config 파일 업데이트
parksey Nov 6, 2023
0759ebb
refactor: develop 기능 merge
parksey Nov 6, 2023
9080613
fix: MemberServie confilt 해결
parksey Nov 6, 2023
00b1fe0
feat: Window용 포트 redis 포트 변경 추가
parksey Nov 7, 2023
42ddbd6
Merge branch 'develop' of https://github.com/team-moabam/moabam-BE in…
parksey Nov 7, 2023
4b00b42
refacotr: develop 업데이트 사항 merge
parksey Nov 7, 2023
b9b90f2
refactor: develop 업데이트 부분 merge
parksey Nov 7, 2023
3294470
fix: TimeConfig 삭제 및 코드 스멜 변경
parksey Nov 7, 2023
dafef37
refactor: 코르리뷰 반영
parksey Nov 7, 2023
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
8 changes: 8 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,14 @@ dependencies {
// Firebase Admin
implementation 'com.google.firebase:firebase-admin:9.2.0'

// JWT
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5'

// JSON parser
implementation 'org.json:json:20230618'

// Asciidoctor
asciidoctorExtensions 'org.springframework.restdocs:spring-restdocs-asciidoctor'

Expand Down
54 changes: 36 additions & 18 deletions src/main/java/com/moabam/api/application/AuthenticationService.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
package com.moabam.api.application;

import static com.moabam.global.common.util.OAuthParameterNames.*;

import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.util.UriComponentsBuilder;
Expand All @@ -13,9 +12,10 @@
import com.moabam.api.dto.AuthorizationTokenInfoResponse;
import com.moabam.api.dto.AuthorizationTokenRequest;
import com.moabam.api.dto.AuthorizationTokenResponse;
import com.moabam.api.dto.LoginResponse;
import com.moabam.api.dto.OAuthMapper;
import com.moabam.global.common.constant.GlobalConstant;
import com.moabam.global.common.util.TokenConstant;
import com.moabam.global.common.util.CookieUtils;
import com.moabam.global.config.OAuthConfig;
import com.moabam.global.error.exception.BadRequestException;
import com.moabam.global.error.model.ErrorMessage;
Expand All @@ -29,6 +29,8 @@ public class AuthenticationService {

private final OAuthConfig oAuthConfig;
private final OAuth2AuthorizationServerRequestService oauth2AuthorizationServerRequestService;
private final MemberService memberService;
private final JwtProviderService jwtProviderService;

public void redirectToLoginPage(HttpServletResponse httpServletResponse) {
String authorizationCodeUri = getAuthorizationCodeUri();
Expand All @@ -37,36 +39,46 @@ public void redirectToLoginPage(HttpServletResponse httpServletResponse) {

public AuthorizationTokenResponse requestToken(AuthorizationCodeResponse authorizationCodeResponse) {
validAuthorizationGrant(authorizationCodeResponse.code());

return issueTokenToAuthorizationServer(authorizationCodeResponse.code());
}

public AuthorizationTokenInfoResponse requestTokenInfo(AuthorizationTokenResponse authorizationTokenResponse) {
String tokenValue = generateTokenValue(authorizationTokenResponse.accessToken());
ResponseEntity<AuthorizationTokenInfoResponse> authorizationTokenInfoResponse
= oauth2AuthorizationServerRequestService.tokenInfoRequest(oAuthConfig.provider().tokenInfo(), tokenValue);
ResponseEntity<AuthorizationTokenInfoResponse> authorizationTokenInfoResponse =
oauth2AuthorizationServerRequestService.tokenInfoRequest(oAuthConfig.provider().tokenInfo(), tokenValue);

return authorizationTokenInfoResponse.getBody();
}

@Transactional
public LoginResponse signUpOrLogin(HttpServletResponse httpServletResponse,
AuthorizationTokenInfoResponse authorizationTokenInfoResponse) {
LoginResponse loginResponse = memberService.login(authorizationTokenInfoResponse);
issueServiceToken(httpServletResponse, loginResponse.id());

return loginResponse;
}

private String getAuthorizationCodeUri() {
AuthorizationCodeRequest authorizationCodeRequest = OAuthMapper.toAuthorizationCodeRequest(oAuthConfig);
return generateQueryParamsWith(authorizationCodeRequest);
}

private String generateTokenValue(String token) {
return TokenConstant.TOKEN_TYPE + GlobalConstant.SPACE + token;
return "Bearer" + GlobalConstant.SPACE + token;
}

private String generateQueryParamsWith(AuthorizationCodeRequest authorizationCodeRequest) {
UriComponentsBuilder authorizationCodeUri = UriComponentsBuilder
.fromUriString(oAuthConfig.provider().authorizationUri())
.queryParam(RESPONSE_TYPE, CODE)
.queryParam(CLIENT_ID, authorizationCodeRequest.clientId())
.queryParam(REDIRECT_URI, authorizationCodeRequest.redirectUri());
UriComponentsBuilder authorizationCodeUri = UriComponentsBuilder.fromUriString(
oAuthConfig.provider().authorizationUri())
.queryParam("response_type", "code")
.queryParam("client_id", authorizationCodeRequest.clientId())
.queryParam("redirect_uri", authorizationCodeRequest.redirectUri());

if (!authorizationCodeRequest.scope().isEmpty()) {
String scopes = String.join(GlobalConstant.COMMA, authorizationCodeRequest.scope());
authorizationCodeUri.queryParam(SCOPE, scopes);
String scopes = String.join(",", authorizationCodeRequest.scope());
authorizationCodeUri.queryParam("scope", scopes);
}

return authorizationCodeUri.toUriString();
Expand All @@ -91,15 +103,21 @@ private AuthorizationTokenResponse issueTokenToAuthorizationServer(String code)

private MultiValueMap<String, String> generateTokenRequest(AuthorizationTokenRequest authorizationTokenRequest) {
MultiValueMap<String, String> contents = new LinkedMultiValueMap<>();
contents.add(GRANT_TYPE, authorizationTokenRequest.grantType());
contents.add(CLIENT_ID, authorizationTokenRequest.clientId());
contents.add(REDIRECT_URI, authorizationTokenRequest.redirectUri());
contents.add(CODE, authorizationTokenRequest.code());
contents.add("grant_type", authorizationTokenRequest.grantType());
contents.add("client_id", authorizationTokenRequest.clientId());
contents.add("redirect_uri", authorizationTokenRequest.redirectUri());
contents.add("code", authorizationTokenRequest.code());

if (authorizationTokenRequest.clientSecret() != null) {
contents.add(CLIENT_SECRET, authorizationTokenRequest.clientSecret());
contents.add("client_secret", authorizationTokenRequest.clientSecret());
}

return contents;
}

private void issueServiceToken(HttpServletResponse response, Long id) {
response.addHeader("token_type", "Bearer");
response.addCookie(CookieUtils.tokenCookie("access_token", jwtProviderService.provideAccessToken(id)));
response.addCookie(CookieUtils.tokenCookie("refresh_token", jwtProviderService.provideRefreshToken(id)));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.moabam.api.application;

import java.util.Base64;

import org.json.JSONObject;
import org.springframework.stereotype.Service;

import com.moabam.global.config.TokenConfig;
import com.moabam.global.error.exception.UnauthorizedException;
import com.moabam.global.error.model.ErrorMessage;

import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts;
import lombok.RequiredArgsConstructor;

@Service
@RequiredArgsConstructor
public class JwtAuthenticationService {

private final TokenConfig tokenConfig;

public boolean isTokenValid(String token) {
try {
Jwts.parserBuilder()
.setSigningKey(tokenConfig.getKey())
.build()
.parseClaimsJwt(token);
return true;
} catch (ExpiredJwtException expiredJwtException) {
return false;
} catch (Exception exception) {
throw new UnauthorizedException(ErrorMessage.AUTHENTICATIE_FAIL);
}
}

public String parseEmail(String token) {
String claims = token.split("\\.")[1];
String decodeClaims = new String(Base64.getDecoder().decode(claims));
JSONObject jsonObject = new JSONObject(decodeClaims);

return (String)jsonObject.get("id");
}
}
41 changes: 41 additions & 0 deletions src/main/java/com/moabam/api/application/JwtProviderService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.moabam.api.application;

import java.util.Date;

import org.springframework.stereotype.Service;

import com.moabam.global.config.TokenConfig;

import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.RequiredArgsConstructor;

@Service
@RequiredArgsConstructor
public class JwtProviderService {

private final TokenConfig tokenConfig;

public String provideAccessToken(long id) {
return generateToken(id, tokenConfig.getAccessExpire());
}

public String provideRefreshToken(long id) {
return generateToken(id, tokenConfig.getRefreshExpire());
}

private String generateToken(long id, long expireTime) {
Date issueDate = new Date();
Date expireDate = new Date(issueDate.getTime() + expireTime);

return Jwts.builder()
.setHeaderParam("alg", "HS256")
.setHeaderParam("typ", "JWT")
.setIssuer(tokenConfig.getIss())
.setIssuedAt(issueDate)
.setExpiration(expireDate)
.claim("id", id)
.signWith(tokenConfig.getKey(), SignatureAlgorithm.HS256)
.compact();
}
}
28 changes: 28 additions & 0 deletions src/main/java/com/moabam/api/application/MemberService.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,21 @@

import static com.moabam.global.error.model.ErrorMessage.*;

import java.security.SecureRandom;
import java.util.List;
import java.util.Optional;

import org.apache.commons.lang3.RandomStringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.moabam.api.domain.entity.Member;
import com.moabam.api.domain.repository.MemberRepository;
import com.moabam.api.domain.repository.MemberSearchRepository;
import com.moabam.api.domain.repository.NotificationRepository;
import com.moabam.api.dto.AuthorizationTokenInfoResponse;
import com.moabam.api.dto.LoginResponse;
import com.moabam.api.dto.MemberMapper;
import com.moabam.global.error.exception.NotFoundException;

import lombok.RequiredArgsConstructor;
Expand All @@ -21,12 +28,33 @@ public class MemberService {

private final MemberRepository memberRepository;
private final MemberSearchRepository memberSearchRepository;
private final NotificationRepository notificationRepository;

public Member getById(Long memberId) {
return memberRepository.findById(memberId)
.orElseThrow(() -> new NotFoundException(MEMBER_NOT_FOUND));
}

@Transactional
public LoginResponse login(AuthorizationTokenInfoResponse authorizationTokenInfoResponse) {
Optional<Member> member = memberRepository.findBySocialId(authorizationTokenInfoResponse.id());
Member loginMember = member.orElse(signUp(authorizationTokenInfoResponse.id()));

return MemberMapper.toLoginResponse(loginMember.getId(), member.isEmpty());
}

private Member signUp(Long socialId) {
String randomNickName = createRandomNickName();
Member member = MemberMapper.toMember(socialId, randomNickName);

return memberRepository.save(member);
}

private String createRandomNickName() {
return RandomStringUtils.random(6, 0, 0, true, true, null,
new SecureRandom());
}

public Member getManager(Long roomId) {
return memberSearchRepository.findManager(roomId)
.orElseThrow(() -> new NotFoundException(MEMBER_NOT_FOUND));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
import com.moabam.api.dto.AuthorizationTokenInfoResponse;
import com.moabam.api.dto.AuthorizationTokenResponse;
import com.moabam.global.common.constant.GlobalConstant;
import com.moabam.global.common.util.TokenConstant;
import com.moabam.global.error.exception.BadRequestException;
import com.moabam.global.error.handler.RestTemplateResponseHandler;
import com.moabam.global.error.model.ErrorMessage;
Expand Down Expand Up @@ -54,7 +53,7 @@ public ResponseEntity<AuthorizationTokenResponse> requestAuthorizationServer(Str

public ResponseEntity<AuthorizationTokenInfoResponse> tokenInfoRequest(String tokenInfoUri, String tokenValue) {
HttpHeaders headers = new HttpHeaders();
headers.add(TokenConstant.AUTHORIZATION, tokenValue);
headers.add("Authorization", tokenValue);
HttpEntity<Void> httpEntity = new HttpEntity<>(headers);

return restTemplate.exchange(tokenInfoUri, HttpMethod.GET, httpEntity, AuthorizationTokenInfoResponse.class);
Expand Down
8 changes: 4 additions & 4 deletions src/main/java/com/moabam/api/domain/entity/Member.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public class Member extends BaseTimeEntity {
private Long id;

@Column(name = "social_id", nullable = false, unique = true)
private String socialId;
private Long socialId;

@Column(name = "nickname", nullable = false, unique = true)
private String nickname;
Expand Down Expand Up @@ -79,11 +79,11 @@ public class Member extends BaseTimeEntity {
private LocalDateTime deletedAt;

@Builder
private Member(Long id, String socialId, String nickname, String profileImage, Bug bug) {
private Member(Long id, Long socialId, String nickname, Bug bug) {
this.id = id;
this.socialId = requireNonNull(socialId);
this.socialId = socialId;
this.nickname = requireNonNull(nickname);
this.profileImage = requireNonNullElse(profileImage, BaseImageUrl.PROFILE_URL);
this.profileImage = BaseImageUrl.PROFILE_URL;
this.bug = requireNonNull(bug);
this.role = Role.USER;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package com.moabam.api.domain.repository;

import java.util.Optional;

import org.springframework.data.jpa.repository.JpaRepository;

import com.moabam.api.domain.entity.Member;

public interface MemberRepository extends JpaRepository<Member, Long> {

Optional<Member> findBySocialId(Long id);
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public class NotificationRepository {

private final StringRedisRepository stringRedisRepository;

// TODO : 세연님 로그인 시, 해당 메서드 사용해서 해당 유저의 FCM TOKEN 저장하면 됩니다.
// TODO : 세연님 로그인 시, 해당 메서드 사용해서 해당 유저의 FCM TOKEN 저장하면 됩니다. Front와 상의 후 삭제예정
public void saveFcmToken(Long key, String value) {
stringRedisRepository.save(
String.valueOf(requireNonNull(key)),
Expand Down
11 changes: 11 additions & 0 deletions src/main/java/com/moabam/api/dto/LoginResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.moabam.api.dto;

import lombok.Builder;

@Builder
public record LoginResponse(
Long id,
boolean isSignUp
) {

}
32 changes: 32 additions & 0 deletions src/main/java/com/moabam/api/dto/MemberMapper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.moabam.api.dto;

import com.moabam.api.domain.entity.Bug;
import com.moabam.api.domain.entity.Member;

import lombok.AccessLevel;
import lombok.NoArgsConstructor;

@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class MemberMapper {

public static Member toMember(Long socialId, String nickName) {
return Member.builder()
.socialId(socialId)
.nickname(nickName)
.bug(Bug.builder().build())
.build();
}

public static LoginResponse toLoginResponse(Long memberId) {
return LoginResponse.builder()
.id(memberId)
.build();
}

public static LoginResponse toLoginResponse(Long memberId, boolean isSignUp) {
return LoginResponse.builder()
.id(memberId)
.isSignUp(isSignUp)
.build();
}
}
Loading