Skip to content

Commit

Permalink
[FEAT] kakao auth api
Browse files Browse the repository at this point in the history
[FEAT] 카카오 auth
  • Loading branch information
devyubin authored Aug 13, 2023
2 parents b168a33 + c509ef7 commit 72c360d
Show file tree
Hide file tree
Showing 38 changed files with 1,519 additions and 13 deletions.
17 changes: 16 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ repositories {
dependencies {
implementation 'org.springframework.boot:spring-boot-starter'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
implementation 'org.springframework.boot:spring-boot-starter-web'

compileOnly 'org.projectlombok:lombok'
Expand All @@ -32,6 +31,22 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-jdbc'

implementation 'mysql:mysql-connector-java:8.0.33'

// jwt config
implementation 'javax.xml.bind:jaxb-api'
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5'
implementation 'javax.xml.bind:jaxb-api'

// redis config
implementation 'org.springframework.boot:spring-boot-starter-data-redis'

// security
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5'
testImplementation 'org.springframework.security:spring-security-test'

}

jar {
Expand Down
126 changes: 126 additions & 0 deletions src/main/java/com/haemil/backend/auth/controller/AuthController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package com.haemil.backend.auth.controller;

import com.haemil.backend.auth.dto.LoginDto;
import com.haemil.backend.auth.dto.RespLoginDto;
import com.haemil.backend.auth.dto.SuccessDto;
import com.haemil.backend.auth.dto.kakao.KakaoLoginParams;
import com.haemil.backend.auth.service.AuthService;
import com.haemil.backend.global.config.BaseException;
import com.haemil.backend.global.config.BaseResponse;
import com.haemil.backend.global.security.jwt.AuthTokens;
import com.haemil.backend.user.entity.UserRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseCookie;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("/auth")
public class AuthController {

private final AuthService authService;
private final UserRepository userRepository;

@Value("${jwt.cookie-period}")
private long CookiePeriod;

@PostMapping("/kakao")
public ResponseEntity<BaseResponse> loginKakao(@RequestParam Boolean isGuardian, @RequestBody KakaoLoginParams params) {
try {
log.debug("isGuardian = {}", isGuardian);
RespLoginDto respLoginDto = authService.login(params, isGuardian);

HttpHeaders headers = respLoginDto.getHeaders();
LoginDto loginDto = respLoginDto.getLoginDto();

return ResponseEntity.ok().headers(headers).body(new BaseResponse<>(loginDto));
} catch (BaseException e){
return new BaseResponse<>(e.getStatus()).convert();
}
}

// 토큰 재발급
@PostMapping("/reissue")
public ResponseEntity<?> reissue(@CookieValue(name = "refresh-token") String requestRefreshToken,
@RequestHeader("Authorization") String requestAccessToken) {

AuthTokens.TokenDto reissuedTokenDto = authService.reissue(requestAccessToken, requestRefreshToken);

SuccessDto successDto;
if (reissuedTokenDto != null) { // 토큰 재발급 성공
// RT 저장
ResponseCookie responseCookie = ResponseCookie.from("refresh-token", reissuedTokenDto.getRefreshToken())
.maxAge(CookiePeriod)
// .domain(".photohere.co.kr")
.path("/")
.sameSite("None")
.httpOnly(true)
.secure(true)
.build();
// success true
successDto = SuccessDto.builder().success(true).build();
return ResponseEntity
.status(HttpStatus.OK)
.header(HttpHeaders.SET_COOKIE, responseCookie.toString())
// AT 저장
.header(HttpHeaders.AUTHORIZATION, "Bearer " + reissuedTokenDto.getAccessToken())
.body(successDto);
// .build();

} else { // Refresh Token 탈취 가능성
// Cookie 삭제 후 재로그인 유도
ResponseCookie responseCookie = ResponseCookie.from("refresh-token", "")
.maxAge(0)
.path("/")
.build();
// success false
successDto = SuccessDto.builder().success(false).build();

return ResponseEntity
.status(HttpStatus.UNAUTHORIZED)
.header(HttpHeaders.SET_COOKIE, responseCookie.toString())
.body(successDto);
// .build();
}
}

// temp mapping - find cookie

@RequestMapping("/getCookie1")
public ResponseEntity<BaseResponse<String>> getCookie1(@CookieValue String useremail, @CookieValue("useremail") String umail) {
System.out.println(umail);
return ResponseEntity.ok().body(new BaseResponse<>("test."));
}

// --

// 로그아웃
@PostMapping("/logout")
public ResponseEntity<?> logout(@RequestHeader("Authorization") String requestAccessToken) {
try {
// RT가 null이 아니면서 empty가 아닌 경우 로그아웃 진행.
if (requestAccessToken != null && !requestAccessToken.isEmpty())
authService.logout(requestAccessToken);
ResponseCookie responseCookie = ResponseCookie.from("refresh-token", "")
.maxAge(0)
.path("/")
.build();

HttpHeaders headers = new HttpHeaders();
headers.add(HttpHeaders.SET_COOKIE, responseCookie.toString());

return ResponseEntity.ok().headers(headers).body(new BaseResponse<>("로그아웃 되었습니다."));
} catch (BaseException e){
return new BaseResponse<>(e.getStatus()).convert();
}


}

}
23 changes: 23 additions & 0 deletions src/main/java/com/haemil/backend/auth/dto/LoginDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.haemil.backend.auth.dto;

import lombok.Builder;
import lombok.Getter;

@Getter
@Builder
public class LoginDto {

private String nickname;
private String profileImageUrl;
private Long userId;
// 추가: AT
private String accessToken;

public LoginDto(String nickname, String profileImageUrl, Long userId, String accessToken) {
this.nickname = nickname;
this.profileImageUrl = profileImageUrl;
this.userId = userId;
this.accessToken = accessToken;
}
}

21 changes: 21 additions & 0 deletions src/main/java/com/haemil/backend/auth/dto/RespLoginDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.haemil.backend.auth.dto;

import org.springframework.http.HttpHeaders;

public class RespLoginDto {
private HttpHeaders headers;
private LoginDto loginDto;

public RespLoginDto(HttpHeaders headers, LoginDto loginDto) {
this.headers = headers;
this.loginDto = loginDto;
}

public HttpHeaders getHeaders() {
return headers;
}

public LoginDto getLoginDto() {
return loginDto;
}
}
10 changes: 10 additions & 0 deletions src/main/java/com/haemil/backend/auth/dto/SuccessDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.haemil.backend.auth.dto;

import lombok.Builder;
import lombok.Getter;

@Getter
@Builder
public class SuccessDto {
private Boolean success;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package com.haemil.backend.auth.dto.kakao;

import com.haemil.backend.global.security.oauth.OAuthApiClient;
import com.haemil.backend.global.security.oauth.OAuthInfoResponse;
import com.haemil.backend.global.security.oauth.OAuthLoginParams;
import com.haemil.backend.global.security.oauth.OAuthProvider;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;

@Component
@RequiredArgsConstructor
@Slf4j
public class KakaoApiClient implements OAuthApiClient {

private static final String GRANT_TYPE = "authorization_code";

@Value("${oauth.kakao.url.auth}")
private String authUrl;

@Value("${oauth.kakao.url.api}")
private String apiUrl;

@Value("${oauth.kakao.client-id}")
private String clientId;

@Value("${oauth.kakao.client-secret}")
private String clientSecret;

private final RestTemplate restTemplate;
// restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory());


@Override
public OAuthProvider oAuthProvider() {
return OAuthProvider.KAKAO;
}

@Override
public String requestAccessToken(OAuthLoginParams params) {
String url = authUrl + "/oauth/token";

HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED);

MultiValueMap<String, String> body = params.makeBody();
body.add("grant_type", GRANT_TYPE);
body.add("client_id", clientId);

HttpEntity<?> request = new HttpEntity<>(body, httpHeaders);

KakaoTokens response = restTemplate.postForObject(url, request, KakaoTokens.class);

assert response != null;
return response.getAccessToken();
}

@Override
public OAuthInfoResponse requestOauthInfo(String accessToken) {
String url = apiUrl + "/v2/user/me";

HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
httpHeaders.set("Authorization", "Bearer " + accessToken);

MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
body.add("property_keys", "[\"kakao_account.email\", \"kakao_account.profile\"]");
log.info("call request user/me");
HttpEntity<?> request = new HttpEntity<>(body, httpHeaders);

return restTemplate.postForObject(url, request, KakaoInfoResponse.class);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package com.haemil.backend.auth.dto.kakao;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.haemil.backend.global.security.oauth.OAuthInfoResponse;
import com.haemil.backend.global.security.oauth.OAuthProvider;
import lombok.Getter;

@Getter
@JsonIgnoreProperties(ignoreUnknown = true)
public class KakaoInfoResponse implements OAuthInfoResponse {

@JsonProperty("kakao_account")
private KakaoAccount kakaoAccount;

@Getter
@JsonIgnoreProperties(ignoreUnknown = true)
static class KakaoAccount {
private KakaoProfile profile;
private String email;
}

@Getter
@JsonIgnoreProperties(ignoreUnknown = true)
static class KakaoProfile {
private String nickname;
private String profileImageUrl;
}

@Override
public String getEmail() {
return kakaoAccount.email;
}

@Override
public String getNickname() {
return kakaoAccount.profile.nickname;
}

@Override
public String getProfileImageUrl() {
return kakaoAccount.profile.profileImageUrl;
}

@Override
public OAuthProvider getOAuthProvider() {
return OAuthProvider.KAKAO;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.haemil.backend.auth.dto.kakao;

import com.haemil.backend.global.security.oauth.OAuthLoginParams;
import com.haemil.backend.global.security.oauth.OAuthProvider;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;

@Slf4j
@Getter
@NoArgsConstructor
public class KakaoLoginParams implements OAuthLoginParams {
private String authorizationCode;

@Override
public OAuthProvider oAuthProvider() {
return OAuthProvider.KAKAO;
}

@Override
public MultiValueMap<String, String> makeBody() {
MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
body.add("code", authorizationCode);
return body;
}
}
Loading

0 comments on commit 72c360d

Please sign in to comment.