Skip to content

Commit

Permalink
[BE] 카카오 로그인 기능을 추가 (#123)
Browse files Browse the repository at this point in the history
* feat: 카카오 OAuth2 기능 추가

* feat: 로그인 기능 추가

* feat: 테스트 전용 PlatformUserProvider 추가

* test: 로그인 관련 테스트시 쿠키를 얻을 수 있는 공용 메서드 추가

* refactor: 패키지명 변경

* feat: 카카오 로그인시, 사이트에 등록된 회원이면 등록된 정보를 반환하고, 아니라면 등록한 후 정보를 반환하는 기능 추가

* refactor: 팀 컨벤션에 맞게 리팩터링

* test: platformId를 통해 유저를 반환하는 테스트 추가

* refactor: 멤버 생성자 수정으로 인한 테스트 코드 변경

* feat: 가입된 유저라면 201 OK 반환, 가입되지 않은 유저라면 302 FOUND 반환

* refactor: 팀원 피드백 반영

* feat: 로그아웃 기능 추가

* feat: 카카오 로그인 환경 변수 설정
  • Loading branch information
70825 authored Jul 28, 2023
1 parent 461ac4f commit b2ddd7e
Show file tree
Hide file tree
Showing 32 changed files with 894 additions and 51 deletions.
31 changes: 31 additions & 0 deletions backend/src/main/java/com/funeat/auth/application/AuthService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.funeat.auth.application;

import com.funeat.auth.dto.SignUserDto;
import com.funeat.auth.dto.UserInfoDto;
import com.funeat.auth.util.PlatformUserProvider;
import com.funeat.member.application.MemberService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@Transactional(readOnly = true)
public class AuthService {

private final MemberService memberService;
private final PlatformUserProvider platformUserProvider;

public AuthService(final MemberService memberService, final PlatformUserProvider platformUserProvider) {
this.memberService = memberService;
this.platformUserProvider = platformUserProvider;
}

public SignUserDto loginWithKakao(final String code) {
final UserInfoDto userInfoDto = platformUserProvider.getPlatformUser(code);
final SignUserDto signUserDto = memberService.findOrCreateMember(userInfoDto);
return signUserDto;
}

public String getLoginRedirectUri() {
return platformUserProvider.getRedirectURI();
}
}
53 changes: 53 additions & 0 deletions backend/src/main/java/com/funeat/auth/dto/KakaoTokenDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package com.funeat.auth.dto;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;

public class KakaoTokenDto {

private final String accessToken;
private final String tokenType;
private final String refreshToken;
private final String expiresIn;
private final String scope;
private final String refreshTokenExpiresIn;

@JsonCreator
public KakaoTokenDto(@JsonProperty("access_token") final String accessToken,
@JsonProperty("token_type") final String tokenType,
@JsonProperty("refresh_token") final String refreshToken,
@JsonProperty("expires_in") final String expiresIn,
@JsonProperty("scope") final String scope,
@JsonProperty("refresh_token_expires_in") final String refreshTokenExpiresIn) {
this.accessToken = accessToken;
this.tokenType = tokenType;
this.refreshToken = refreshToken;
this.expiresIn = expiresIn;
this.scope = scope;
this.refreshTokenExpiresIn = refreshTokenExpiresIn;
}

public String getAccessToken() {
return accessToken;
}

public String getTokenType() {
return tokenType;
}

public String getRefreshToken() {
return refreshToken;
}

public String getExpiresIn() {
return expiresIn;
}

public String getScope() {
return scope;
}

public String getRefreshTokenExpiresIn() {
return refreshTokenExpiresIn;
}
}
61 changes: 61 additions & 0 deletions backend/src/main/java/com/funeat/auth/dto/KakaoUserInfoDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package com.funeat.auth.dto;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;

public class KakaoUserInfoDto {

private final Long id;
private final KakaoAccount kakaoAccount;

@JsonCreator
public KakaoUserInfoDto(@JsonProperty("id") final Long id,
@JsonProperty("kakao_account") final KakaoAccount kakaoAccount) {
this.id = id;
this.kakaoAccount = kakaoAccount;
}

public Long getId() {
return id;
}

public KakaoAccount getKakaoAccount() {
return kakaoAccount;
}

public static class KakaoAccount {

private final KakaoProfile profile;

@JsonCreator
public KakaoAccount(@JsonProperty("profile") final KakaoProfile profile) {
this.profile = profile;
}

public KakaoProfile getProfile() {
return profile;
}
}

public static class KakaoProfile {

private final String nickname;
private final String profileImageUrl;

@JsonCreator
public KakaoProfile(
@JsonProperty("nickname") final String nickname,
@JsonProperty("profile_image_url") final String profileImageUrl) {
this.nickname = nickname;
this.profileImageUrl = profileImageUrl;
}

public String getNickname() {
return nickname;
}

public String getProfileImageUrl() {
return profileImageUrl;
}
}
}
14 changes: 14 additions & 0 deletions backend/src/main/java/com/funeat/auth/dto/LoginRequest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.funeat.auth.dto;

public class LoginRequest {

private final Long id;

public LoginRequest(final Long id) {
this.id = id;
}

public Long getId() {
return id;
}
}
26 changes: 26 additions & 0 deletions backend/src/main/java/com/funeat/auth/dto/SignUserDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.funeat.auth.dto;

import com.funeat.member.domain.Member;

public class SignUserDto {

private final boolean isSignIn;
private final Member member;

public SignUserDto(final boolean isSignIn, final Member member) {
this.isSignIn = isSignIn;
this.member = member;
}

public static SignUserDto of(final boolean isSignIn, final Member member) {
return new SignUserDto(isSignIn, member);
}

public boolean isSignIn() {
return isSignIn;
}

public Member getMember() {
return member;
}
}
14 changes: 14 additions & 0 deletions backend/src/main/java/com/funeat/auth/dto/TokenResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.funeat.auth.dto;

public class TokenResponse {

private String accessToken;

public TokenResponse(final String accessToken) {
this.accessToken = accessToken;
}

public String getAccessToken() {
return accessToken;
}
}
40 changes: 40 additions & 0 deletions backend/src/main/java/com/funeat/auth/dto/UserInfoDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.funeat.auth.dto;

import com.funeat.member.domain.Member;

public class UserInfoDto {

private final Long id;
private final String nickname;
private final String profileImageUrl;

public UserInfoDto(final Long id, final String nickname, final String profileImageUrl) {
this.id = id;
this.nickname = nickname;
this.profileImageUrl = profileImageUrl;
}

public static UserInfoDto from(final KakaoUserInfoDto kakaoUserInfoDto) {
return new UserInfoDto(
kakaoUserInfoDto.getId(),
kakaoUserInfoDto.getKakaoAccount().getProfile().getNickname(),
kakaoUserInfoDto.getKakaoAccount().getProfile().getProfileImageUrl()
);
}

public Member toMember() {
return new Member(this.nickname, this.profileImageUrl, this.id.toString());
}

public Long getId() {
return id;
}

public String getNickname() {
return nickname;
}

public String getProfileImageUrl() {
return profileImageUrl;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package com.funeat.auth.presentation;

import com.funeat.auth.application.AuthService;
import com.funeat.auth.dto.LoginRequest;
import com.funeat.auth.dto.SignUserDto;
import com.funeat.auth.util.AuthenticationPrincipal;
import java.net.URI;
import javax.servlet.http.HttpServletRequest;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class AuthController {

private final AuthService authService;

public AuthController(final AuthService authService) {
this.authService = authService;
}

@GetMapping("/api/auth/kakao")
public ResponseEntity<Void> kakaoLogin() {
return ResponseEntity.status(HttpStatus.FOUND)
.location(URI.create(authService.getLoginRedirectUri()))
.build();
}

@GetMapping("/login/oauth2/code/kakao")
public ResponseEntity<Void> loginAuthorizeUser(@RequestParam("code") final String code,
final HttpServletRequest request) {
final SignUserDto signUserDto = authService.loginWithKakao(code);
final Long memberId = signUserDto.getMember().getId();
request.getSession().setAttribute("member", memberId);

if (signUserDto.isSignIn()) {
return ResponseEntity.status(HttpStatus.FOUND)
.location(URI.create("/"))
.build();
}
return ResponseEntity.status(HttpStatus.FOUND)
.location(URI.create("/profile"))
.build();
}

@GetMapping("/api/logout")
public ResponseEntity<Void> logout(@AuthenticationPrincipal final LoginRequest loginRequest,
final HttpServletRequest request) {
request.getSession().removeAttribute("member");

return ResponseEntity.ok().build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.funeat.auth.util;

import com.funeat.auth.dto.LoginRequest;
import java.util.Objects;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import org.springframework.core.MethodParameter;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;

@Component
public class AuthArgumentResolver implements HandlerMethodArgumentResolver {

@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(AuthenticationPrincipal.class);
}

@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {
final HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
final HttpSession session = Objects.requireNonNull(request).getSession();
final String id = String.valueOf(session.getAttribute("member"));

return new LoginRequest(Long.valueOf(id));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.funeat.auth.util;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

@Component
public class AuthHandlerInterceptor implements HandlerInterceptor {

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
final HttpSession session = request.getSession();
if (session.getAttribute("member") == null) {
throw new IllegalArgumentException("login error");
}
return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.funeat.auth.util;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface AuthenticationPrincipal {
}
Loading

0 comments on commit b2ddd7e

Please sign in to comment.