Skip to content

Commit

Permalink
Merge pull request #79 from TRIP-Side-Project/dev
Browse files Browse the repository at this point in the history
태그 & 소셜 로그인 기능 구현
  • Loading branch information
don9m1n authored Dec 18, 2023
2 parents 3f20407 + f6ac140 commit 118dc3e
Show file tree
Hide file tree
Showing 21 changed files with 361 additions and 131 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import com.api.trip.common.security.jwt.JwtTokenFilter;
import com.api.trip.common.security.jwt.JwtTokenProvider;
import com.api.trip.common.security.oauth.CustomOAuth2UserService;
import com.api.trip.common.security.oauth.OAuthFailureHandler;
import com.api.trip.common.security.oauth.OAuthSuccessHandler;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
Expand All @@ -18,17 +17,15 @@
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter;


@RequiredArgsConstructor
@Configuration
@EnableWebSecurity
@EnableMethodSecurity(securedEnabled = true)
@RequiredArgsConstructor
public class SecurityConfig {

private final JwtTokenProvider jwtTokenProvider;
private final CustomOAuth2UserService customOAuth2UserService;
private final OAuthSuccessHandler OAuthSuccessHandler;
private final OAuthFailureHandler OAuthFailureHandler;

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception{
Expand All @@ -43,11 +40,12 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception{
.addHeaderWriter(new XFrameOptionsHeaderWriter(
XFrameOptionsHeaderWriter.XFrameOptionsMode.SAMEORIGIN))
)
// .oauth2Login(oauth -> oauth
// .userInfoEndpoint(userInfoEndpoint -> userInfoEndpoint.userService(customOAuth2UserService))
// .successHandler(OAuthSuccessHandler)
// .failureHandler(OAuthFailureHandler)
// )
.oauth2Login(oauth -> oauth
.userInfoEndpoint(userInfoEndpoint -> userInfoEndpoint
.userService(customOAuth2UserService)
)
.successHandler(OAuthSuccessHandler)
)
.addFilterBefore(new JwtTokenFilter(jwtTokenProvider), UsernamePasswordAuthenticationFilter.class)
.build();
}
Expand All @@ -56,4 +54,5 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception{
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}

}
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
package com.api.trip.common.security.oauth;

import com.api.trip.domain.member.model.Member;
import com.api.trip.domain.member.repository.MemberRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
Expand All @@ -15,37 +12,23 @@

import java.util.Collections;
import java.util.Map;
import java.util.Optional;

@Slf4j
@Service
@RequiredArgsConstructor
public class CustomOAuth2UserService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {

private final MemberRepository memberRepository;

@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {

OAuth2UserService<OAuth2UserRequest, OAuth2User> oAuth2UserService = new DefaultOAuth2UserService();
OAuth2User oAuth2User = oAuth2UserService.loadUser(userRequest);

String oauthType = userRequest.getClientRegistration().getRegistrationId();
String registrationId = userRequest.getClientRegistration().getRegistrationId();
String userNameAttributeName = userRequest.getClientRegistration().getProviderDetails().getUserInfoEndpoint().getUserNameAttributeName();

OAuth2Attribute oAuth2Attribute = OAuth2Attribute.of(oauthType, userNameAttributeName, oAuth2User.getAttributes());
Map<String, Object> customAttributeMap = oAuth2Attribute.convertToMap();

String email = (String) customAttributeMap.get("email");
Optional<Member> findMember = memberRepository.findByEmail(email);

boolean exist = findMember.isPresent();
String authority = exist ? findMember.get().getRole().getValue() : "ROLE_MEMBER";

log.debug("회원 존재 여부: {}", exist);
log.debug("회원 권한 여부: {}", authority);
OAuth2Attribute oAuth2Attribute = OAuth2Attribute.of(registrationId, userNameAttributeName, oAuth2User.getAttributes());
Map<String, Object> memberAttribute = oAuth2Attribute.convertToMap();

customAttributeMap.put("exist", exist);
return new DefaultOAuth2User(Collections.singleton(new SimpleGrantedAuthority(authority)), customAttributeMap, userNameAttributeName);
return new DefaultOAuth2User(Collections.singleton(new SimpleGrantedAuthority("ROLE_MEMBER")),
memberAttribute, "email");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,66 +15,66 @@
public class OAuth2Attribute {

private Map<String, Object> attributes; // 소셜 로그인 사용자의 속성 정보를 담는 Map
private String userNameAttributeName; // 사용자 속성의 키 값
private String attributeKey; // 사용자 속성의 키 값
private String email; // 이메일
private String nickname; // 이름
private String profileImg; // 프로필 사진
private String oauthType; // 제공자 정보
private String name; // 이름
private String picture; // 프로필 사진

static OAuth2Attribute of(String oauthType, String userNameAttributeName, Map<String, Object> attributes) {
return switch (oauthType) {
case "kakao" -> kakao(oauthType, userNameAttributeName, attributes);
case "google" -> google(oauthType, userNameAttributeName, attributes);
case "naver" -> naver(oauthType, userNameAttributeName, attributes);
static OAuth2Attribute of(String provider, String attributeKey, Map<String, Object> attributes) {
// 각 플랫폼 별로 제공해주는 데이터가 조금씩 다르기 때문에 분기 처리함.
return switch (provider) {
case "google" -> google(attributeKey, attributes);
case "kakao" -> kakao(attributeKey, attributes);
case "naver" -> naver(attributeKey, attributes);
default -> throw new RuntimeException();
};
}

private static OAuth2Attribute google(String attributeKey, Map<String, Object> attributes) {
log.debug("google: {}", attributes);
return OAuth2Attribute.builder()
.email((String) attributes.get("email"))
.name((String) attributes.get("name"))
.picture((String)attributes.get("picture"))
.attributes(attributes)
.attributeKey(attributeKey)
.build();
}

private static OAuth2Attribute kakao(String oauthType, String userNameAttributeName, Map<String, Object> attributes) {
private static OAuth2Attribute kakao(String attributeKey, Map<String, Object> attributes) {
Map<String, Object> kakaoAccount = (Map<String, Object>) attributes.get("kakao_account");
Map<String, Object> kakaoProfile = (Map<String, Object>) kakaoAccount.get("profile");

return OAuth2Attribute.builder()
.userNameAttributeName(userNameAttributeName)
.email("KAKAO_" + (String) kakaoAccount.get("email"))
.name((String) kakaoProfile.get("nickname"))
.picture((String) kakaoProfile.get("profile_image_url"))
.attributes(kakaoAccount)
.email((String) kakaoAccount.get("email"))
.nickname((String) kakaoProfile.get("nickname"))
.profileImg((String) kakaoProfile.get("profile_image_url"))
.oauthType(oauthType)
.build();
}

private static OAuth2Attribute google(String oauthType, String userNameAttributeName, Map<String, Object> attributes) {
return OAuth2Attribute.builder()
.userNameAttributeName(userNameAttributeName)
.attributes(attributes)
.email((String) attributes.get("email"))
.oauthType(oauthType)
.attributeKey(attributeKey)
.build();
}

private static OAuth2Attribute naver(String oauthType, String userNameAttributeName, Map<String, Object> attributes) {
private static OAuth2Attribute naver(String attributeKey, Map<String, Object> attributes) {
Map<String, Object> response = (Map<String, Object>) attributes.get("response");

return OAuth2Attribute.builder()
.email((String) response.get("email"))
.name((String) response.get("nickname"))
.picture((String) response.get("profile_image"))
.attributes(response)
.oauthType(oauthType)
.userNameAttributeName(userNameAttributeName)
.attributeKey(attributeKey)
.build();
}


// OAuth2Attribute -> Map<String, Object>
public Map<String, Object> convertToMap() {
Map<String, Object> map = new HashMap<>();

map.put("id", userNameAttributeName);
map.put("id", attributeKey);
map.put("key", attributeKey);
map.put("email", email);
map.put("nickname", nickname);
map.put("profileImg", profileImg);
map.put("oauthType", oauthType);
map.put("name", name);
map.put("picture", picture);

return map;
}
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,63 +1,82 @@
package com.api.trip.common.security.oauth;

import com.api.trip.common.security.jwt.JwtToken;
import com.api.trip.common.security.jwt.JwtTokenProvider;
import jakarta.servlet.ServletException;
import com.api.trip.domain.member.controller.dto.LoginResponse;
import com.api.trip.domain.member.model.Member;
import com.api.trip.domain.member.model.MemberRole;
import com.api.trip.domain.member.repository.MemberRepository;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseCookie;
import org.springframework.security.core.Authentication;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import org.springframework.web.util.UriComponentsBuilder;

import java.io.IOException;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.Optional;

@Slf4j
@Component
@RequiredArgsConstructor
public class OAuthSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {

private final MemberRepository memberRepository;
private final JwtTokenProvider jwtTokenProvider;
private final ObjectMapper objectMapper;

@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {

OAuth2User oAuth2User = (OAuth2User) authentication.getPrincipal();
log.debug("OAuth2User: {}", oAuth2User);

String email = oAuth2User.getAttribute("email");
Optional<Member> findMember = memberRepository.findByEmail(email);

String role = oAuth2User.getAuthorities().stream()
.findFirst()
.orElseThrow(IllegalAccessError::new)
.getAuthority();

/** 미구현
if (isExist) {
JwtToken jwtToken = jwtTokenProvider.createJwtToken(email, role);
log.debug("JWT TOKEN: {} {}", jwtToken.getAccessToken(), jwtToken.getRefreshToken());
String targetUrl = UriComponentsBuilder.fromUriString("/")
.queryParam("accessToken", jwtToken.getAccessToken())
.queryParam("refreshToken", jwtToken.getRefreshToken())
.build()
.encode(StandardCharsets.UTF_8)
.toUriString();
getRedirectStrategy().sendRedirect(request, response, targetUrl);
} else {
// 회원이 존재하는 않는 경우 회원 가입 후 토큰 발급
String targetUrl = UriComponentsBuilder.fromUriString("/")
.queryParam("email", email)
.queryParam("name", nickname)
.build()
.encode(StandardCharsets.UTF_8)
.toUriString();
getRedirectStrategy().sendRedirect(request, response, targetUrl);
// 회원이 아닌 경우에 회원 가입 진행

Long memberId = 0L;
String role = "";

if (findMember.isEmpty()) {
String name = oAuth2User.getAttribute("name");
String picture = oAuth2User.getAttribute("picture");

Member member = Member.of(email, "", name, picture);
memberRepository.save(member);

memberId = member.getId();
role = member.getRole().getValue();
}
*/

// OAuth2User 객체에서 권한 가져옴
JwtToken jwtToken = jwtTokenProvider.createJwtToken(email, role);

// 쿠키 세팅
response.addHeader(HttpHeaders.SET_COOKIE, createCookie("tokenType", "Bearer"));
response.addHeader(HttpHeaders.SET_COOKIE, createCookie("accessToken", jwtToken.getAccessToken()));
response.addHeader(HttpHeaders.SET_COOKIE, createCookie("refreshToken", jwtToken.getRefreshToken()));
response.addHeader(HttpHeaders.SET_COOKIE, createCookie("memberId", String.valueOf(memberId)));
response.sendRedirect("/home");
}

private static String createCookie(String name, String value) {
return ResponseCookie.from(name, value)
.path("/")
.httpOnly(true)
.sameSite("None")
.secure(true)
.build()
.toString();
}

}
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package com.api.trip.domain.interestitem.controller.dto;

import com.api.trip.domain.interestitem.InterestItem;
import com.api.trip.domain.member.model.Member;
import lombok.Getter;

@Getter
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
package com.api.trip.domain.interestitem.controller.dto;

import com.api.trip.domain.interestitem.InterestItem;
import com.api.trip.domain.interestitem.model.InterestItem;
import com.api.trip.domain.item.controller.dto.GetItemResponse;
import com.api.trip.domain.item.controller.dto.GetItemsResponse;
import com.api.trip.domain.item.model.Item;
import lombok.Builder;
import lombok.Getter;
import org.springframework.data.domain.Page;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.api.trip.domain.interestitem;
package com.api.trip.domain.interestitem.model;

import com.api.trip.common.auditing.entity.BaseTimeEntity;
import com.api.trip.domain.item.model.Item;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.api.trip.domain.interestitem.repository;

import com.api.trip.domain.interestitem.InterestItem;
import com.api.trip.domain.item.model.Item;
import com.api.trip.domain.interestitem.model.InterestItem;
import com.api.trip.domain.member.model.Member;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
package com.api.trip.domain.interestitem.service;

import com.api.trip.domain.interestitem.InterestItem;
import com.api.trip.domain.interestitem.model.InterestItem;
import com.api.trip.domain.interestitem.controller.dto.CreateInterestItemRequest;
import com.api.trip.domain.interestitem.controller.dto.GetInterestItemsResponse;
import com.api.trip.domain.interestitem.repository.InterestItemRepository;
import com.api.trip.domain.item.controller.dto.CreateItemRequest;
import com.api.trip.domain.item.controller.dto.GetItemsResponse;
import com.api.trip.domain.item.model.Item;
import com.api.trip.domain.item.repository.ItemRepository;
Expand All @@ -17,8 +16,6 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.stream.Collectors;

@Service
@Transactional
@RequiredArgsConstructor
Expand Down
Loading

0 comments on commit 118dc3e

Please sign in to comment.