Skip to content

Commit

Permalink
feat: 네이버 로그인 추가
Browse files Browse the repository at this point in the history
  • Loading branch information
GitJIHO committed Nov 26, 2024
1 parent 7cffbc1 commit 83d5dba
Show file tree
Hide file tree
Showing 8 changed files with 213 additions and 5 deletions.
3 changes: 2 additions & 1 deletion src/main/java/com/software/ott/OttApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@

import com.software.ott.common.properties.CommonProperties;
import com.software.ott.common.properties.KakaoProperties;
import com.software.ott.common.properties.NaverProperties;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;

@SpringBootApplication
@EnableJpaAuditing
@EnableConfigurationProperties({KakaoProperties.class, CommonProperties.class})
@EnableConfigurationProperties({KakaoProperties.class, CommonProperties.class, NaverProperties.class})
public class OttApplication {

public static void main(String[] args) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.software.ott.auth.dto.TokenRefreshRequest;
import com.software.ott.auth.dto.TokenResponse;
import com.software.ott.auth.service.KakaoApiService;
import com.software.ott.auth.service.NaverApiService;
import com.software.ott.auth.service.PhoneNumberAuthService;
import com.software.ott.auth.service.TokenService;
import com.software.ott.member.service.MemberService;
Expand All @@ -28,12 +29,14 @@ public class AuthController {
private final KakaoApiService kakaoApiService;
private final MemberService memberService;
private final PhoneNumberAuthService phoneNumberAuthService;
private final NaverApiService naverApiService;

public AuthController(TokenService tokenService, KakaoApiService kakaoApiService, MemberService memberService, PhoneNumberAuthService phoneNumberAuthService) {
public AuthController(TokenService tokenService, KakaoApiService kakaoApiService, MemberService memberService, PhoneNumberAuthService phoneNumberAuthService, NaverApiService naverApiService) {
this.tokenService = tokenService;
this.kakaoApiService = kakaoApiService;
this.memberService = memberService;
this.phoneNumberAuthService = phoneNumberAuthService;
this.naverApiService = naverApiService;
}

@Operation(summary = "토큰 재발급", description = "RefreshToken으로 AccessToken과 RefreshToken을 재발급 한다.", security = @SecurityRequirement(name = "JWT제외"))
Expand All @@ -59,6 +62,22 @@ public ResponseEntity<TokenResponse> kakaoCallback(@RequestParam("code") String
return ResponseEntity.ok().body(loginResponse);
}

@Operation(summary = "Oauth 네이버 인증페이지 리다이렉트", description = "네이버 로그인 화면으로 이동한다.", security = @SecurityRequirement(name = "JWT제외"))
@GetMapping("/auth/oauth/naver")
public ResponseEntity<Void> redirectToNaverAuth(HttpServletRequest httpServletRequest) {
String url = naverApiService.getNaverLoginUrl(httpServletRequest);
HttpHeaders headers = new HttpHeaders();
headers.setLocation(URI.create(url));
return new ResponseEntity<>(headers, HttpStatus.FOUND);
}

@Operation(summary = "Oauth 네이버 로그인 콜백", description = "네이버 로그인 이후 발생하는 인가코드를 통해 AccessToken을 발급받고 사용자 정보를 조회한다.", security = @SecurityRequirement(name = "JWT제외"))
@GetMapping("/auth/oauth/naver/callback")
public ResponseEntity<TokenResponse> naverCallback(@RequestParam("code") String code, @RequestParam("state") String state, HttpServletRequest httpServletRequest) {
TokenResponse loginResponse = memberService.naverLogin(code, state, httpServletRequest);
return ResponseEntity.ok().body(loginResponse);
}

@Operation(summary = "전화번호 인증용 qr 생성", description = "사용자 전화번호 인증용 qr을 생성합니다.")
@GetMapping("/qr")
public ResponseEntity<byte[]> generateQRCode(@RequestAttribute("memberId") Long memberId, @RequestParam String phoneNumber) {
Expand Down
13 changes: 13 additions & 0 deletions src/main/java/com/software/ott/auth/dto/NaverTokenResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.software.ott.auth.dto;

import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonNaming;

@JsonNaming(value = PropertyNamingStrategies.SnakeCaseStrategy.class)
public record NaverTokenResponse(
String accessToken,
String refreshToken,
String tokenType,
String expiresIn
) {
}
17 changes: 17 additions & 0 deletions src/main/java/com/software/ott/auth/dto/NaverUserResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.software.ott.auth.dto;

import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonNaming;

@JsonNaming(value = PropertyNamingStrategies.SnakeCaseStrategy.class)
public record NaverUserResponse(
String resultCode,
String message,
Response response
) {
public record Response(
String name,
String email
) {
}
}
124 changes: 124 additions & 0 deletions src/main/java/com/software/ott/auth/service/NaverApiService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package com.software.ott.auth.service;

import com.software.ott.auth.dto.NaverTokenResponse;
import com.software.ott.auth.dto.NaverUserResponse;
import com.software.ott.common.exception.BadRequestException;
import com.software.ott.common.properties.NaverProperties;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
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.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;

import java.util.UUID;

@Service
@RequiredArgsConstructor
public class NaverApiService {

private static final String LOCALHOST_URL = "localhost:5173";
private static final String LOCALHOST_URL_IP = "127.0.0.1:5173";
private static final String SUB_SERVER_URL = "http://ott.backapi.site/redirection";
private static final String SUB_SERVER_URL_WITHOUT_HTTP = "ott.backapi.site";

private final RestTemplate restTemplate;
private final NaverProperties naverProperties;

public String getNaverLoginUrl(HttpServletRequest httpServletRequest) {
String originHeader = httpServletRequest.getHeader("Origin");
String refererHeader = httpServletRequest.getHeader("Referer");

String redirectUri = getRedirectUriBasedOnRequest(originHeader, refererHeader);

if (redirectUri == null) {
String hostHeader = httpServletRequest.getHeader("Host");
redirectUri = getRedirectUriBasedOnRequest(hostHeader, null);
}

if (redirectUri == null) {
throw new BadRequestException("해당 도메인에서는 네이버 로그인이 불가합니다.");
}

return UriComponentsBuilder.fromUriString("https://nid.naver.com/oauth2.0/authorize")
.queryParam("response_type", "code")
.queryParam("client_id", naverProperties.clientId())
.queryParam("redirect_uri", redirectUri)
.queryParam("state", UUID.randomUUID().toString())
.build()
.toUriString();
}

private String getRedirectUriBasedOnRequest(String primaryUrl, String secondaryUrl) {
if (isAllowedDomain(primaryUrl) || isAllowedDomain(secondaryUrl)) {
return naverProperties.redirectUri();
} else if (isLocalDomain(primaryUrl) || isLocalDomain(secondaryUrl)) {
return naverProperties.devRedirectUri();
} else if (isSubAllowedDomain(primaryUrl) || isSubAllowedDomain(secondaryUrl)) {
return SUB_SERVER_URL;
}
return null;
}

private boolean isAllowedDomain(String url) {
return url != null && url.contains(naverProperties.frontUriWithoutHttp());
}

private boolean isLocalDomain(String url) {
return url != null && (url.contains(LOCALHOST_URL) || url.contains(LOCALHOST_URL_IP));
}

private boolean isSubAllowedDomain(String url) {
return url != null && url.contains(SUB_SERVER_URL_WITHOUT_HTTP);
}

public NaverTokenResponse getAccessToken(String code, String state, HttpServletRequest httpServletRequest) {
String originHeader = httpServletRequest.getHeader("Origin");
String refererHeader = httpServletRequest.getHeader("Referer");

String redirectUri = getRedirectUriBasedOnRequest(originHeader, refererHeader);

if (redirectUri == null) {
String hostHeader = httpServletRequest.getHeader("Host");
redirectUri = getRedirectUriBasedOnRequest(hostHeader, null);
}

if (redirectUri == null) {
throw new BadRequestException("해당 도메인에서는 네이버 로그인이 불가합니다.");
}

String tokenRequestUrl = UriComponentsBuilder.fromUriString("https://nid.naver.com/oauth2.0/token")
.queryParam("grant_type", "authorization_code")
.queryParam("client_id", naverProperties.clientId())
.queryParam("client_secret", naverProperties.clientSecret())
.queryParam("code", code)
.queryParam("state", state)
.queryParam("redirect_uri", redirectUri)
.build()
.toUriString();

ResponseEntity<NaverTokenResponse> response = restTemplate.getForEntity(tokenRequestUrl, NaverTokenResponse.class);
return response.getBody();
}

public NaverUserResponse getUserInfo(String accessToken) {
String url = "https://openapi.naver.com/v1/nid/me";

HttpHeaders headers = new HttpHeaders();
headers.setBearerAuth(accessToken);

HttpEntity<Void> entity = new HttpEntity<>(headers);

ResponseEntity<NaverUserResponse> response = restTemplate.exchange(
url, HttpMethod.GET, entity, NaverUserResponse.class);

if (response.getBody() != null && response.getBody().response().email() == null) {
throw new IllegalArgumentException("네이버 계정으로부터 이메일을 받아올 수 없습니다.");
}

return response.getBody();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public class PhoneNumberAuthService {
private final MemberRepository memberRepository;
private final Map<String, String> hashStore = new ConcurrentHashMap<>();
private final Map<String, Boolean> checkPhoneNumber = new ConcurrentHashMap<>();

public PhoneNumberAuthService(CommonProperties commonProperties, MemberPhoneNumberRepository memberPhoneNumberRepository, MemberRepository memberRepository) {
this.commonProperties = commonProperties;
this.memberPhoneNumberRepository = memberPhoneNumberRepository;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.software.ott.common.properties;

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "naver")
public record NaverProperties(
String clientId,
String redirectUri,
String clientSecret,
String devRedirectUri,
String frontUriWithoutHttp
) {

}
25 changes: 22 additions & 3 deletions src/main/java/com/software/ott/member/service/MemberService.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
package com.software.ott.member.service;


import com.software.ott.auth.dto.KakaoTokenResponse;
import com.software.ott.auth.dto.KakaoUserResponse;
import com.software.ott.auth.dto.TokenResponse;
import com.software.ott.auth.dto.*;
import com.software.ott.auth.service.KakaoApiService;
import com.software.ott.auth.service.KakaoTokenService;
import com.software.ott.auth.service.NaverApiService;
import com.software.ott.auth.service.TokenService;
import com.software.ott.common.exception.ConflictException;
import com.software.ott.common.exception.NotFoundException;
Expand All @@ -28,6 +27,7 @@ public class MemberService {
private final TokenService tokenService;
private final KakaoApiService kakaoApiService;
private final KakaoTokenService kakaoTokenService;
private final NaverApiService naverApiService;

@Transactional
public TokenResponse kakaoLogin(String authorizationCode, HttpServletRequest httpServletRequest) {
Expand All @@ -50,6 +50,25 @@ public TokenResponse kakaoLogin(String authorizationCode, HttpServletRequest htt
return new TokenResponse(accessToken, refreshToken);
}

@Transactional
public TokenResponse naverLogin(String code, String state, HttpServletRequest httpServletRequest) {
NaverTokenResponse tokenResponse = naverApiService.getAccessToken(code, state, httpServletRequest);
NaverUserResponse naverUserResponse = naverApiService.getUserInfo(tokenResponse.accessToken());

String email = naverUserResponse.response().email();

Optional<Member> optionalMember = memberRepository.findByEmail(email);

if (optionalMember.isEmpty()) {
registerNewMember(naverUserResponse.response().name(), email);
}

String accessToken = tokenService.generateAccessToken(email);
String refreshToken = tokenService.generateRefreshToken(email);

return new TokenResponse(accessToken, refreshToken);
}

public void registerNewMember(String name, String email) {

if (memberRepository.existsByEmail(email)) {
Expand Down

0 comments on commit 83d5dba

Please sign in to comment.