Skip to content

Commit

Permalink
Merge pull request #64 from tukcomCD2024/refactor#62/oauth2-login
Browse files Browse the repository at this point in the history
Refactor#62/oauth2 login
  • Loading branch information
ggamD00 authored Apr 9, 2024
2 parents cdfb40e + c9e73a9 commit cd00346
Show file tree
Hide file tree
Showing 31 changed files with 349 additions and 444 deletions.
1 change: 0 additions & 1 deletion backend/memetory/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ dependencies {

implementation 'com.auth0:java-jwt:4.2.1'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'

// Swagger 적용
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.example.memetory.domain.auth.controller;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestBody;

import com.example.memetory.domain.auth.dto.LoginRequest;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
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 jakarta.servlet.http.HttpServletResponse;

@Tag(name = "Auth")
public interface AuthApi {

@Operation(
summary = "로그인",
description = "앱에서 받아온 인증서버의 access_token을 통해서 우리 서버의 JWT를 받아가는 과정"
+ "처음 로그인 할 경우 DB에 사용자 등록이 진행된다."
)
@ApiResponses(
@ApiResponse(
responseCode = "200",
description = "로그인 성공, header의 Authorization과 Authorization-refresh를 확인"
)
)
ResponseEntity<HttpStatus> login(LoginRequest request,
@Parameter(hidden = true) HttpServletResponse response);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.example.memetory.domain.auth.controller;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import com.example.memetory.domain.auth.dto.LoginRequest;
import com.example.memetory.domain.auth.service.AuthService;

import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;

@RestController
@RequiredArgsConstructor
public class AuthController implements AuthApi {
private final AuthService authService;

@PostMapping("/login")
public ResponseEntity<HttpStatus> login(@RequestBody LoginRequest request, HttpServletResponse response) {
authService.authenticateOrRegisterUser(request, response);
return new ResponseEntity<>(HttpStatus.OK);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.example.memetory.domain.auth.dto;

import com.example.memetory.domain.member.entity.SocialType;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@Schema(description = "로그인 포맷")
public class LoginRequest {
@Schema(description = "인증서버에서 받아온 access token을 입력")
private String token;
@Schema(description = "인증서버타입, 현재는 google만 가능")
private SocialType socialType;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.example.memetory.domain.auth.service;

import java.util.UUID;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import com.example.memetory.domain.auth.dto.LoginRequest;
import com.example.memetory.domain.auth.userInfo.OAuth2UserInfo;
import com.example.memetory.domain.member.entity.Member;
import com.example.memetory.domain.member.entity.Role;
import com.example.memetory.domain.member.entity.SocialType;
import com.example.memetory.domain.member.repository.MemberRepository;
import com.example.memetory.global.security.jwt.service.JwtService;

import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;

@Service
@RequiredArgsConstructor
public class AuthService {
private static final Logger log = LoggerFactory.getLogger(AuthService.class);
private final MemberRepository memberRepository;
private final OAuth2ProviderService oAuth2ProviderService;
private final JwtService jwtService;

public void authenticateOrRegisterUser(LoginRequest loginRequest, HttpServletResponse response) {
OAuth2UserInfo userInfo = oAuth2ProviderService.getUserInfo(loginRequest);
Member member = findOrElseRegisterMember(userInfo, loginRequest.getSocialType());
jwtService.sendAccessAndRefreshToken(response, member.getEmail());
}

private Member findOrElseRegisterMember(OAuth2UserInfo userInfo, SocialType socialType) {
return memberRepository.findBySocialTypeAndSocialId(socialType, userInfo.getId())
.orElse(registerMember(socialType, userInfo));
}

private Member registerMember(SocialType socialType, OAuth2UserInfo userInfo) {
Member member = Member.builder()
.socialType(socialType)
.socialId(userInfo.getId())
.email(UUID.randomUUID() + "@socialUser.com")
.name(userInfo.getName())
.nickname(String.valueOf(UUID.randomUUID()))
.imageUrl(userInfo.getImageUrl())
.role(Role.USER)
.build();

return memberRepository.save(member);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.example.memetory.domain.auth.service;

import static com.example.memetory.domain.member.entity.SocialType.*;

import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;

import com.example.memetory.domain.auth.dto.LoginRequest;
import com.example.memetory.domain.auth.userInfo.GoogleOAuth2UserInfo;
import com.example.memetory.domain.auth.userInfo.OAuth2UserInfo;

import lombok.RequiredArgsConstructor;

@Service
@RequiredArgsConstructor
public class OAuth2ProviderService {

private static final Logger log = LoggerFactory.getLogger(OAuth2ProviderService.class);

public OAuth2UserInfo getUserInfo(LoginRequest request) {
return switch (request.getSocialType()) {
case GOOGLE -> getGoogleUserInfo(request);
};
}

private OAuth2UserInfo getGoogleUserInfo(LoginRequest request) {
Map attributes = WebClient.create(GOOGLE.getProviderUrl())
.get()
.headers(httpHeaders -> {
httpHeaders.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
httpHeaders.setBearerAuth(request.getToken());
})
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(Map.class)
.log()
.block();

return new GoogleOAuth2UserInfo(attributes);
}
}

Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package com.example.memetory.global.security.oauth.userInfo;
package com.example.memetory.domain.auth.userInfo;

import java.util.Map;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class GoogleOAuth2UserInfo extends OAuth2UserInfo {

public GoogleOAuth2UserInfo(Map<String, Object> attributes) {
Expand All @@ -10,11 +13,11 @@ public GoogleOAuth2UserInfo(Map<String, Object> attributes) {

@Override
public String getId() {
return (String)attributes.get("sub");
return (String)attributes.get("id");
}

@Override
public String getNickname() {
public String getName() {
return (String)attributes.get("name");
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.example.memetory.global.security.oauth.userInfo;
package com.example.memetory.domain.auth.userInfo;

import java.util.Map;

Expand All @@ -12,7 +12,7 @@ public OAuth2UserInfo(Map<String, Object> attributes) {

public abstract String getId(); //소셜 식별 값 : 구글 - "sub", 카카오 - "id", 네이버 - "id"

public abstract String getNickname();
public abstract String getName();

public abstract String getImageUrl();
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,32 @@
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;

import com.example.memetory.domain.member.dto.MemberSignUpRequest;
import com.example.memetory.domain.member.dto.MemberUpdateDto;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletResponse;

@Tag(name = "Member")
public interface MemberApi {

@Operation(
summary = "회원가입",
description = "첫 소셜로그인 후 추가 정보 기입",
summary = "멤버 업데이트",
description = "멤버의 이미지 url 및 닉네임 업데이트"
+ "업데이트를 하지 않을 필드의 경우에는, 기존 값을 넣어 주면 된다.",
security = {@SecurityRequirement(name = "access_token")}
)
@ApiResponses(value = {
@ApiResponse(
responseCode = "200",
description = "회원가입 성공!"
)
})
ResponseEntity<HttpStatus> register(
HttpServletResponse response,
MemberSignUpRequest memberSignUpRequest,
@Parameter(hidden = true) String email
);
description = "이미지 생성 성공"
),
@ApiResponse(
responseCode = "409",
description = "닉네임 중복"
)}
)
ResponseEntity<HttpStatus> updateMember(@Parameter(hidden = true) String email, MemberUpdateDto memberUpdateDto);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,37 +4,31 @@
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.example.memetory.domain.member.dto.MemberServiceDto;
import com.example.memetory.domain.member.dto.MemberSignUpRequest;
import com.example.memetory.domain.member.dto.MemberUpdateDto;
import com.example.memetory.domain.member.service.MemberService;
import com.example.memetory.global.annotation.LoginMemberEmail;
import com.example.memetory.global.security.jwt.refresh.service.RefreshTokenService;
import com.example.memetory.global.security.jwt.service.JwtService;

import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;

@RestController
@RequiredArgsConstructor
@RequestMapping("/member")
public class MemberController implements MemberApi {
private final MemberService memberService;
private final JwtService jwtService;
private final RefreshTokenService refreshTokenService;

@PostMapping("/sign-up")
@Override
public ResponseEntity<HttpStatus> register(
HttpServletResponse response, @RequestBody MemberSignUpRequest memberSignUpRequest,
@LoginMemberEmail String email) {
MemberServiceDto memberServiceDto = memberSignUpRequest.toServiceDto(email);
@PostMapping
public ResponseEntity<HttpStatus> updateMember(@LoginMemberEmail String email,
@RequestBody MemberUpdateDto memberUpdateDto) {
MemberServiceDto memberServiceDto = memberUpdateDto.toServiceDto(email);

memberService.register(memberServiceDto);

String refreshToken = jwtService.createRefreshToken();
jwtService.setRefreshTokenHeader(response, refreshToken);
refreshTokenService.updateToken(email, refreshToken);
if (memberService.isDuplicatedNickname(memberServiceDto)) {
return ResponseEntity.status(HttpStatus.CONFLICT).build();
}
memberService.update(memberServiceDto);

return ResponseEntity.status(HttpStatus.OK).build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,21 @@
import lombok.Getter;
import lombok.NoArgsConstructor;

@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@Getter
@Schema(description = "회원 가입 포맷")
public class MemberSignUpRequest {

@Schema(description = "닉네임")
private String nickName;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@AllArgsConstructor
@Schema(description = "멤버 업데이트 포맷")
public class MemberUpdateDto {
@Schema(description = "변경할 닉네임")
private String nickname;
@Schema(description = "변경할 이미지 S3 Url")
private String imageUrl;

public MemberServiceDto toServiceDto(String email) {
return MemberServiceDto.builder()
.email(email)
.nickname(nickName)
.nickname(this.nickname)
.imageUrl(this.imageUrl)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.example.memetory.domain.member.entity;

import com.example.memetory.domain.member.dto.MemberServiceDto;
import com.example.memetory.domain.member.dto.MemberSignUpRequest;
import com.example.memetory.global.entity.BaseEntity;

import jakarta.persistence.Column;
Expand Down Expand Up @@ -30,6 +29,7 @@ public class Member extends BaseEntity {

private String email;
private String nickname;
private String name;
private String imageUrl;

@Enumerated(EnumType.STRING)
Expand All @@ -41,17 +41,20 @@ public class Member extends BaseEntity {
private String socialId;

@Builder
public Member(String email, String nickname, String imageUrl, Role role, SocialType socialType, String socialId) {
public Member(String email, String nickname, String name, String imageUrl, Role role, SocialType socialType,
String socialId) {
this.email = email;
this.nickname = nickname;
this.name = name;
this.imageUrl = imageUrl;
this.role = role;
this.socialType = socialType;
this.socialId = socialId;
}

public void register(MemberServiceDto memberServiceDto) {
// Todo 닉네임이랑, 이미지 변경할 수 있게 하기
public void update(MemberServiceDto memberServiceDto) {
this.nickname = memberServiceDto.getNickname();
this.role = Role.USER;
this.imageUrl = memberServiceDto.getImageUrl();
}
}
Loading

0 comments on commit cd00346

Please sign in to comment.