Skip to content

Commit

Permalink
Merge branch 'main' into feat/#10
Browse files Browse the repository at this point in the history
  • Loading branch information
sycuuui committed May 29, 2024
2 parents b1d0bf2 + 13eb7ff commit 8c0754c
Show file tree
Hide file tree
Showing 10 changed files with 196 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,9 @@ public void login(HttpServletResponse response) throws IOException {

@Operation(summary = "로그인 API")
@PostMapping("/sign-in")
public ApiResponse<LoginRes> signIn(@RequestHeader("Authorization") String socialAccessToken, @RequestBody String type) {
return ApiResponse.success(Success.LOGIN_SUCCESS,authService.signIn(socialAccessToken,type));
public ApiResponse<LoginRes> signIn(@RequestHeader("Authorization") String token,
@RequestBody String type) {
return ApiResponse.success(Success.LOGIN_SUCCESS,authService.signIn(token,type));
}

@Operation(summary = "로그아웃 API", description = "로그아웃된 JWT 블랙리스트 등록")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
public class Member extends BaseTimeEntity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "member_id", nullable = false, columnDefinition = "bigint")
@Column(name = "member_id", columnDefinition = "bigint")
private Long memberId;

// 이메일은 최대 255자 + 1자(@) + 69자해서 최대 320글자이므로, varchar(320) 사용
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,18 @@
import Journey.Together.global.security.kakao.dto.KakaoProfile;
import Journey.Together.global.security.jwt.TokenProvider;
import Journey.Together.global.security.jwt.dto.TokenDto;
import Journey.Together.global.security.kakao.dto.KakaoToken;
import Journey.Together.global.security.naver.dto.NaverProperties;
import Journey.Together.global.security.naver.dto.NaverUserResponse;
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.transaction.annotation.Transactional;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;

@Service
@Transactional(readOnly = true)
Expand All @@ -23,39 +32,78 @@ public class AuthService {
private final TokenProvider tokenProvider;
private final MemberRepository memberRepository;

private final RestTemplate restTemplate = new RestTemplate();

@Transactional
public LoginRes signIn(String kakaoAccessToken, String type) {
//Business Logic
// 카카오톡에 있는 사용자 정보 반환
KakaoProfile kakaoProfile = kakaoClient.getMemberInfo(kakaoAccessToken);
// 반환된 정보의 이메일 기반으로 사용자 테이블에서 계정 정보 조회 진행
Member member = memberRepository.findMemberByEmailAndDeletedAtIsNull(kakaoProfile.kakao_account().email()).orElse(null);
// 이메일 존재 시 로그인 , 존재하지 않을 경우 회원가입 진행
if(member == null) {
Member newMember = Member.builder()
.email(kakaoProfile.kakao_account().email())
.name(kakaoProfile.kakao_account().profile().nickname())
.profileUrl(kakaoProfile.kakao_account().profile().profile_image_url())
.memberType(MemberType.valueOf("GENERAL"))
.loginType(LoginType.valueOf("KAKAO"))
.build();
member = memberRepository.save(newMember);
}
TokenDto tokenDto = tokenProvider.createToken(member);
member.setRefreshToken(tokenDto.refreshToken());
public LoginRes signIn(String token, String type) {
Member member = null;
TokenDto tokenDto = null;

// Response
return LoginRes.of(member, tokenDto);
if(type.equals("KAKAO")) {
//Business Logic
// 카카오톡에 있는 사용자 정보 반환
KakaoProfile kakaoProfile = kakaoClient.getMemberInfo(token);
// 반환된 정보의 이메일 기반으로 사용자 테이블에서 계정 정보 조회 진행
member = memberRepository.findMemberByEmailAndDeletedAtIsNull(kakaoProfile.kakao_account().email()).orElse(null);
// 이메일 존재 시 로그인 , 존재하지 않을 경우 회원가입 진행
if(member == null) {
Member newMember = Member.builder()
.email(kakaoProfile.kakao_account().email())
.name(kakaoProfile.kakao_account().profile().nickname())
.profileUrl(kakaoProfile.kakao_account().profile().profile_image_url())
.memberType(MemberType.valueOf("GENERAL"))
.loginType(LoginType.valueOf("KAKAO"))
.build();
member = memberRepository.save(newMember);
}
tokenDto = tokenProvider.createToken(member);
member.setRefreshToken(tokenDto.refreshToken());

// Response
return LoginRes.of(member, tokenDto);

} else if (type.equals("NAVER")) {
NaverUserResponse.NaverUserDetail naverProfile = toRequestProfile(token.substring(7));
member = memberRepository.findMemberByEmailAndDeletedAtIsNull(naverProfile.getEmail()).orElse(null);

if (member == null) {
Member newMember = Member.builder()
.email(naverProfile.getEmail() != null ? naverProfile.getEmail() : "Unknown")
.profileUrl(naverProfile.getProfile_image() != null ? naverProfile.getProfile_image() : "Unknown")
.name(naverProfile.getName() != null ? naverProfile.getName() : "Unknown")
.memberType(MemberType.GENERAL)
.loginType(LoginType.NAVER)
.build();

member = memberRepository.save(newMember);
}

tokenDto = tokenProvider.createToken(member);
member.setRefreshToken(tokenDto.refreshToken());

}
return LoginRes.of(member, tokenDto);
}
private NaverUserResponse.NaverUserDetail toRequestProfile(String accessToken) {
HttpHeaders headers = new HttpHeaders();
headers.setBearerAuth(accessToken);
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(headers);

ResponseEntity<NaverUserResponse> response =
restTemplate.exchange("https://openapi.naver.com/v1/nid/me", HttpMethod.GET, request, NaverUserResponse.class);

return response.getBody().getNaverUserDetail();
}

public void signOut(String token, Member member) {
// Validation
String accessToken = token.substring(7);
tokenProvider.validateToken(accessToken);

// Business Logic - Refresh Token 삭제 및 Access Token 블랙리스트 등록
String key = member.getEmail();
// redisClient.deleteValue(key);
// redisClient.setValue(accessToken, "logout", tokenProvider.getExpiration(accessToken));
member.setRefreshToken(null);

// Response
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ public class MemberService {

private final MemberRepository memberRepository;


}
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
authorize.requestMatchers("/api-docs/**", "/swagger-ui/**", "/swagger-ui.html").permitAll()
// 로그인 로직 접속 허용
.requestMatchers("/v1/auth/**", "/oauth2/**", "/login.html").permitAll()
.requestMatchers("/actuator/**").permitAll()
.requestMatchers("/v1/member/**").authenticated()
// 메인 페이지, 공고 페이지 등에 한해 인증 정보 없이 접근 가능 (추후 추가)
// 이외의 모든 요청은 인증 정보 필요
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,8 @@ public class JwtFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String requestURI = request.getRequestURI();

// 회원가입일때는 jwt 유효성 검사를 하지않음
if ("/v1/auth/sign-in".equals(requestURI)) {
//jwt 유효성 검사를 하지않음
if ("/v1/auth/sign-in".equals(requestURI) || "/actuator/health".equals(requestURI)) {
filterChain.doFilter(request, response);
return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,53 @@ public class KakaoClient {
private String kakaoUserInfoUri;

public KakaoProfile getMemberInfo(String access_token) {
/**
* 카카오 서버에 인가코드 기반으로 사용자의 토큰 정보를 조회하는 메소드
* @param code - 카카오에서 발급해준 인가 코드
* @return - 카카오에서 반환한 응답 토큰 객체
*/

public KakaoToken getKakaoAccessToken(String code) {
// 요청 보낼 객체 기본 생성
WebClient webClient = WebClient.create(kakaoTokenUri);

//요청 본문
MultiValueMap<String , String> params = new LinkedMultiValueMap<>();
params.add("grant_type", kakaoGrantType);
params.add("client_id", kakaoClientId);
params.add("redirect_uri", kakaoRedirectUri);
params.add("code", code);
params.add("client_secret", kakaoClientSecret);

// 요청 보내기 및 응답 수신
String response = webClient.post()
.uri(kakaoTokenUri)
.header("Content-type", "application/x-www-form-urlencoded")
.body(BodyInserters.fromFormData(params))
.retrieve() // 데이터 받는 방식, 스프링에서는 exchange는 메모리 누수 가능성 때문에 retrieve 권장
.bodyToMono(String.class) // (Mono는 단일 데이터, Flux는 복수 데이터)
.block();// 비동기 방식의 데이터 수신

// 수신된 응답 Mapping
ObjectMapper objectMapper = new ObjectMapper();
KakaoToken kakaoToken;
try {
kakaoToken = objectMapper.readValue(response, KakaoToken.class);
} catch (Exception e) {
throw new RuntimeException(e);
}

return kakaoToken;
}

public KakaoProfile getMemberInfo(String accesToken) {
// 요청 기본 객체 생성
WebClient webClient = WebClient.create(kakaoUserInfoUri);
// 요청 보내서 응답 받기
String response = webClient.post()
.uri(kakaoUserInfoUri)
.header("Content-Type", "application/x-www-form-urlencoded;charset=utf-8")
.header("Authorization", access_token)
.header("Authorization", accesToken)
.retrieve()
.bodyToMono(String.class)
.block();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package Journey.Together.global.security.naver.dto;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.util.UriComponentsBuilder;

@Data
@Configuration
@ConfigurationProperties(prefix = "naver")
public class NaverProperties {
private String requestTokenUri;
private String clientId;
private String clientSecret;

public String getRequestURL(String code) {
return UriComponentsBuilder.fromHttpUrl(requestTokenUri)
.queryParam("grant_type", "authorization_code")
.queryParam("client_id", clientId)
.queryParam("client_secret", clientSecret)
.queryParam("code", code)
.toUriString();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package Journey.Together.global.security.naver.dto;

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
@AllArgsConstructor
public class NaverTokenResponse {
@JsonProperty("access_token")
private String accessToken;
@JsonProperty("refresh_token")
private String refreshToken;
@JsonProperty("token_type")
private String tokenType;
@JsonProperty("expires_in")
private String expiresIn;
@JsonProperty("error")
private String error;
@JsonProperty("error_description")
private String errorDescription;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package Journey.Together.global.security.naver.dto;

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
@AllArgsConstructor
public class NaverUserResponse {
@JsonProperty("resultcode")
private String resultCode;
@JsonProperty("message")
private String message;
@JsonProperty("response")
private NaverUserDetail naverUserDetail;

@Getter
@NoArgsConstructor
@AllArgsConstructor
public static class NaverUserDetail {
private String id;
private String name;
private String email;
private String nickname;
private String profile_image;
}
}

0 comments on commit 8c0754c

Please sign in to comment.