Skip to content

Commit

Permalink
Merge pull request #126 from Me1tingPot/feature/#59
Browse files Browse the repository at this point in the history
[Feature] 회원 탈퇴 마킹 처리 및 카카오 로그인 구현
  • Loading branch information
JangYouJung committed Jul 21, 2024
2 parents daa7ead + 53d27cf commit 220617e
Show file tree
Hide file tree
Showing 25 changed files with 651 additions and 28 deletions.
8 changes: 8 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ dependencies {
// mail
implementation 'org.springframework.boot:spring-boot-starter-mail:3.0.5'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect'

// JWT
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
Expand All @@ -53,10 +54,17 @@ dependencies {
implementation 'com.fasterxml.jackson.core:jackson-core:2.16.1'

implementation 'org.springframework.boot:spring-boot-starter-webflux'

if (isAppleSilicon()) {
runtimeOnly("io.netty:netty-resolver-dns-native-macos:4.1.94.Final:osx-aarch_64")
}

//Apple Login
implementation 'com.nimbusds:nimbus-jose-jwt:3.10'

//Json
implementation 'com.googlecode.json-simple:json-simple:1.1.1'

developmentOnly 'org.springframework.boot:spring-boot-devtools'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.mysql:mysql-connector-j'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import meltingpot.server.auth.controller.dto.*;
import meltingpot.server.auth.service.OAuthService;
import meltingpot.server.auth.service.dto.OAuthSignInResponseDto;
import meltingpot.server.exception.AuthException;
import meltingpot.server.exception.DuplicateException;
import meltingpot.server.exception.IllegalArgumentException;
Expand All @@ -31,9 +33,10 @@
public class AuthController {

private final AuthService authService;
private final OAuthService oAuthService;
private final Logger logger = LoggerFactory.getLogger(this.getClass());

// 회원 가입
// 일반 회원 가입
@PostMapping("signup")
@Operation(summary="회원가입", description="회원가입 API 입니다.\n 회원가입 성공시 자동 로그인되어 AccessToken이 반환됩니다. " )
@ApiResponses(value = {
Expand All @@ -56,6 +59,28 @@ public ResponseEntity<ResponseData<AccountResponseDto>> signup(
}
}

// SNS 회원 가입
@PostMapping("/signup/oauth")
@Operation(summary="SNS 회원가입", description="SNS 회원가입 API 입니다.\n" )
@ApiResponses(value = {
@ApiResponse(responseCode = "CREATED", description = "회원가입 성공"),
@ApiResponse(responseCode = "BAD_REQUEST", description = "회원가입 실패")
})
public ResponseEntity<ResponseData<OAuthSignInResponseDto>> oauthSignup(
@RequestBody @Valid OAuthSignupRequestDto request
){
try{
return ResponseData.toResponseEntity(ResponseCode.OAUTH_SIGNUP_SUCCESS, oAuthService.oauthSignup(request));

}catch ( AuthException e ){
return ResponseData.toResponseEntity( e.getResponseCode(), null);
}catch ( IllegalArgumentException e ){
return ResponseData.toResponseEntity( e.getResponseCode(), null);
}
}



// 프로필 이미지 URL 생성
@GetMapping("/image-url")
@Operation(summary = "회원가입 프로필 이미지 URL 생성", description = "프로필 이미지 업로드를 위한 URL을 생성합니다. 생성된 URL에 PUT으로 이미지를 업로드 한 뒤 key를 회원가입에 첨부해주세요.")
Expand Down Expand Up @@ -86,6 +111,25 @@ public ResponseEntity<ResponseData<AccountResponseDto>> signin(
}
}

// SNS 로그인
@PostMapping("signin/oauth")
@Operation(summary="SNS 로그인", description="SNS 로그인 API 입니다" )
public ResponseEntity<ResponseData<OAuthSignInResponseDto>> SNSLogin(
@RequestBody @Valid OAuthSignInRequestDto request
){
try{
OAuthSignInResponseDto data = oAuthService.SNSLogin(request);
return ResponseData.toResponseEntity(ResponseCode.OAUTH_SIGNIN_SUCCESS, data);

}catch( ResourceNotFoundException e ){
return ResponseData.toResponseEntity(ResponseCode.ACCOUNT_NOT_FOUND, null);
}catch ( InvalidTokenException e ){
return ResponseData.toResponseEntity(ResponseCode.REFRESH_TOKEN_NOT_FOUND, null);
} catch (Exception e) {
throw new RuntimeException(e);
}
}


// 로그아웃
@GetMapping("signout")
Expand All @@ -111,11 +155,4 @@ public ResponseEntity<ResponseData<ReissueTokenResponseDto>> reissueToken(
return ResponseData.toResponseEntity(ResponseCode.INVALID_REFRESH_TOKEN, null);
}
}


// 비밀번호 재설정

// 탈퇴


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package meltingpot.server.auth.controller.dto;

import meltingpot.server.domain.entity.enums.OAuthType;

public record OAuthSignInRequestDto(
OAuthType type,
String code,
String push_token
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package meltingpot.server.auth.controller.dto;

import lombok.Builder;
import meltingpot.server.domain.entity.enums.OAuthType;

import java.time.LocalDate;
import java.util.List;

@Builder
public record OAuthSignupRequestDto(
OAuthType OauthType,
String email,
String name,
String gender,
LocalDate birth,
String nationality,
List<String> languages,
List<ProfileImageRequestDto> profileImages,
String pushToken
) {
}
58 changes: 58 additions & 0 deletions src/main/java/meltingpot/server/auth/oauth/OAuthUserDetails.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package meltingpot.server.auth.oauth;

import meltingpot.server.domain.entity.Account;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Optional;

public class OAuthUserDetails implements UserDetails {

private final Account account;

public OAuthUserDetails(Account account){
this.account = account;
}

@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
grantedAuthorities.add(new SimpleGrantedAuthority("ROLE_USER"));
return grantedAuthorities;
}

@Override
public String getPassword() {
return this.account.getPassword();
}

@Override
public String getUsername() {
return this.account.getName();
}

//계정 만료 여부
@Override
public boolean isAccountNonExpired() {
return true;
}
//계정 잠김 여부
@Override
public boolean isAccountNonLocked() {
return true;
}
//계정 정보 변경 필요 여부
@Override
public boolean isCredentialsNonExpired() {
return true;
}
//계정 활성화 여부
@Override
public boolean isEnabled() {
return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package meltingpot.server.auth.oauth.kakao;

import lombok.Builder;

@Builder
public record KaKaoTokenDto (
String accessToken,
String refreshToken
){
}
13 changes: 13 additions & 0 deletions src/main/java/meltingpot/server/auth/oauth/kakao/KakaoDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package meltingpot.server.auth.oauth.kakao;

import lombok.Builder;
import lombok.Data;
@Builder
@Data
public class KakaoDto {

private long id;
private String email;
private String nickname;

}
110 changes: 110 additions & 0 deletions src/main/java/meltingpot/server/auth/oauth/kakao/KakaoService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package meltingpot.server.auth.oauth.kakao;

import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;

@Service
public class KakaoService {

@Value("${spring.security.oauth2.client.registration.kakao.client-id}")
private String KAKAO_CLIENT_ID;

@Value("${spring.security.oauth2.client.registration.kakao.client-secret}")
private String KAKAO_CLIENT_SECRET;

@Value("${spring.security.oauth2.client.registration.kakao.redirect-uri}")
private String redirect_uri;

private final static String KAKAO_AUTH_URI = "https://kauth.kakao.com";
private final static String KAKAO_API_URI = "https://kapi.kakao.com";

public String getKakaoLogin(String redirect_uri) { // 프론트 구현부
return KAKAO_AUTH_URI + "/oauth/authorize"
+ "?client_id=" + KAKAO_CLIENT_ID
+ "&redirect_uri=" + redirect_uri
+ "&response_type=code";
}

public KaKaoTokenDto getKakaoInfo(String code) throws Exception {
if (code == null) throw new Exception("Failed get authorization code");

String accessToken = "";
String refreshToken = "";

try {
HttpHeaders headers = new HttpHeaders();
headers.add("Content-type", "application/x-www-form-urlencoded");

MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("grant_type" , "authorization_code");
params.add("client_id" , KAKAO_CLIENT_ID);
params.add("client_secret", KAKAO_CLIENT_SECRET);
params.add("code" , code);
params.add("redirect_uri" , redirect_uri);

RestTemplate restTemplate = new RestTemplate();
HttpEntity<MultiValueMap<String, String>> httpEntity = new HttpEntity<>(params, headers);

ResponseEntity<String> response = restTemplate.exchange(
KAKAO_AUTH_URI + "/oauth/token",
HttpMethod.POST,
httpEntity,
String.class
);

JSONParser jsonParser = new JSONParser();
JSONObject jsonObj = (JSONObject) jsonParser.parse(response.getBody());

accessToken = (String) jsonObj.get("access_token");
refreshToken = (String) jsonObj.get("refresh_token");
} catch (Exception e) {
throw new Exception("KAKAO API call failed");
}

return KaKaoTokenDto.builder()
.accessToken(accessToken)
.refreshToken(refreshToken)
.build();
}

public KakaoDto getUserInfoWithToken(String accessToken) throws Exception {
//HttpHeader 생성
HttpHeaders headers = new HttpHeaders();
headers.add("Authorization", "Bearer " + accessToken);
headers.add("Content-type", "application/x-www-form-urlencoded;charset=utf-8");

//HttpHeader 담기
RestTemplate rt = new RestTemplate();
HttpEntity<MultiValueMap<String, String>> httpEntity = new HttpEntity<>(headers);
ResponseEntity<String> response = rt.exchange(
KAKAO_API_URI + "/v2/user/me",
HttpMethod.POST,
httpEntity,
String.class
);

//Response 데이터 파싱
JSONParser jsonParser = new JSONParser();
JSONObject jsonObj = (JSONObject) jsonParser.parse(response.getBody());
JSONObject account = (JSONObject) jsonObj.get("kakao_account");
JSONObject profile = (JSONObject) account.get("profile");

long id = (long) jsonObj.get("id");
String email = String.valueOf(account.get("email"));
String nickname = String.valueOf(profile.get("nickname"));

return KakaoDto.builder()
.id(id)
.email(email)
.nickname(nickname).build();
}
}
Loading

0 comments on commit 220617e

Please sign in to comment.