-
Notifications
You must be signed in to change notification settings - Fork 1
OAuth2.0-version #30
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: feat/27-user,tree,treeITem+jwt
Are you sure you want to change the base?
OAuth2.0-version #30
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,89 @@ | ||
| package com.chukapoka.server.common.authority.oauth2.dto; | ||
|
|
||
| import com.chukapoka.server.common.enums.EmailType; | ||
| import lombok.*; | ||
|
|
||
| import java.util.HashMap; | ||
| import java.util.Map; | ||
|
|
||
| @ToString | ||
| @Builder(access = AccessLevel.PRIVATE) // Builder 메서드를 외부에서 사용하지 않으므로, Private 제어자로 지정 | ||
| @Getter | ||
| public class OAuth2Attribute { | ||
| private Map<String, Object> attributes; // 사용자 속성 정보를 담는 Map | ||
| private String attributeId; // 사용자 속성의 키 값 | ||
| private String emailType; //GOOGLE , NAVER | ||
| private String email; // 이메일 정보 | ||
| private String name; //사용자 정보 | ||
|
|
||
|
|
||
|
|
||
| public static OAuth2Attribute of(String emailType, String userNameAttributeName, Map<String, Object> attributes) { | ||
| switch (emailType) { | ||
| case "google": | ||
| return ofGoogle(userNameAttributeName, attributes); | ||
| case "kakao": | ||
| return ofKakao( userNameAttributeName, attributes); | ||
| case "naver": | ||
| return ofNaver(emailType, userNameAttributeName, attributes); | ||
| default: | ||
| throw new RuntimeException(); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Google 로그인일 경우 사용하는 메서드, 사용자 정보가 따로 Wrapping 되지 않고 제공되어, | ||
| * 바로 get() 메서드로 접근이 가능하다. | ||
| * */ | ||
| private static OAuth2Attribute ofGoogle(String userNameAttributeName, | ||
| Map<String, Object> attributes ) { | ||
| return OAuth2Attribute.builder() | ||
| .email(attributes.get("email").toString()) | ||
| .emailType(EmailType.GOOGLE.name()) | ||
| .attributes(attributes) | ||
| .attributeId(attributes.get(userNameAttributeName).toString()) | ||
| .name( attributes.get("name").toString()) | ||
| .build(); | ||
| } | ||
| /** | ||
| * Kakao 로그인일 경우 사용하는 메서드, 필요한 사용자 정보가 kakaoAccount -> kakaoProfile 두번 감싸져 있어서, | ||
| * 두번 get() 메서드를 이용해 사용자 정보를 담고있는 Map을 꺼내야한다. | ||
| * */ | ||
| private static OAuth2Attribute ofKakao( String userNameAttributeName,Map<String, Object> attributes) { | ||
|
|
||
| Map<String, Object> kakaoAccount = (Map<String, Object>) attributes.get("kakao_account"); | ||
| Map<String, Object> profile = (Map<String, Object>) kakaoAccount.get("profile"); | ||
| return OAuth2Attribute.builder() | ||
| .email(kakaoAccount.get("email").toString()) | ||
| .emailType(EmailType.KAKAO.name()) | ||
| .attributes(kakaoAccount) | ||
| .attributeId(attributes.get(userNameAttributeName).toString()) | ||
| .name(profile.get("nickname").toString()) | ||
| .build(); | ||
| } | ||
| /* | ||
| * Naver 로그인일 경우 사용하는 메서드, 필요한 사용자 정보가 response Map에 감싸져 있어서, | ||
| * 한번 get() 메서드를 이용해 사용자 정보를 담고있는 Map을 꺼내야한다. | ||
| * */ | ||
| private static OAuth2Attribute ofNaver(String provider, String userNameAttributeName, Map<String, Object> attributes) { | ||
| Map<String, Object> response = (Map<String, Object>) attributes.get(userNameAttributeName); | ||
|
|
||
| return OAuth2Attribute.builder() | ||
| .email( response.get("email").toString()) | ||
| .emailType(provider.toUpperCase()) | ||
| .attributes(response) | ||
| .attributeId( response.get("id").toString()) | ||
| .name(response.get("name").toString()) | ||
| .build(); | ||
| } | ||
|
|
||
| /** OAuth2User 객체에 넣어주기 위해서 Map으로 값들을 반환 */ | ||
| public Map<String, Object> convertToMap() { | ||
| Map<String, Object> map = new HashMap<>(); | ||
| map.put("id", attributeId); | ||
| map.put("emailType", emailType); | ||
| map.put("email", email); | ||
| map.put("name", name); | ||
| return map; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| package com.chukapoka.server.common.authority.oauth2.handler; | ||
|
|
||
| import jakarta.servlet.ServletException; | ||
| import jakarta.servlet.http.HttpServletRequest; | ||
| import jakarta.servlet.http.HttpServletResponse; | ||
| import org.springframework.security.core.AuthenticationException; | ||
| import org.springframework.security.web.authentication.AuthenticationFailureHandler; | ||
| import org.springframework.stereotype.Component; | ||
|
|
||
| import java.io.IOException; | ||
|
|
||
| @Component | ||
| public class CustomAuthenticationFailHandler implements AuthenticationFailureHandler { | ||
| @Override | ||
| public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { | ||
| /** 인증 실패시 메인 url로 이동 */ | ||
| response.sendRedirect("http://localhost:8080/"); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| package com.chukapoka.server.common.authority.oauth2.handler; | ||
|
|
||
| import com.chukapoka.server.common.authority.jwt.JwtTokenProvider; | ||
| import com.chukapoka.server.common.dto.BaseResponse; | ||
| import com.chukapoka.server.common.dto.CustomUserDetails; | ||
| import com.chukapoka.server.common.dto.TokenDto; | ||
| import com.chukapoka.server.common.dto.TokenResponseDto; | ||
| import com.chukapoka.server.common.entity.Token; | ||
| import com.chukapoka.server.common.enums.ResultType; | ||
| import com.chukapoka.server.common.repository.TokenRepository; | ||
| import com.chukapoka.server.user.dto.UserResponseDto; | ||
| import com.fasterxml.jackson.databind.ObjectMapper; | ||
| import jakarta.servlet.ServletException; | ||
| import jakarta.servlet.http.HttpServletRequest; | ||
| import jakarta.servlet.http.HttpServletResponse; | ||
| import lombok.RequiredArgsConstructor; | ||
| import lombok.extern.slf4j.Slf4j; | ||
| import org.springframework.security.core.Authentication; | ||
| import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; | ||
| import org.springframework.stereotype.Component; | ||
|
|
||
| import java.io.IOException; | ||
|
|
||
|
|
||
| /** OAuth2 인증이 성공했을 경우 */ | ||
| @Slf4j | ||
| @Component | ||
| @RequiredArgsConstructor | ||
| public class CustomAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler { | ||
| private final JwtTokenProvider jwtTokenProvider; | ||
| private final TokenRepository tokenRepository; | ||
| @Override | ||
| public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { | ||
|
|
||
| CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal(); | ||
| // 1. authentication 유저 정보에 따른 토큰 생성 | ||
| TokenResponseDto token = saveToken(authentication, userDetails.getUser().getId().toString()); | ||
| // 2. baseResponse 객체 생성 | ||
| UserResponseDto userResponseDto = new UserResponseDto(ResultType.SUCCESS, userDetails.getEmail(), userDetails.getUserId(), token); | ||
| BaseResponse<UserResponseDto> baseResponse = new BaseResponse<>(ResultType.SUCCESS, userResponseDto); | ||
| // 3. JSON 형식으로 변환 | ||
| ObjectMapper objectMapper = new ObjectMapper(); | ||
| String jsonResponse = objectMapper.writeValueAsString(baseResponse); | ||
| // 4. 응답 헤더 설정 | ||
| response.setContentType("application/json"); | ||
| response.setCharacterEncoding("UTF-8"); | ||
| // 5. 클라이언트에게 응답 전송 | ||
| response.getWriter().write(jsonResponse); | ||
| } | ||
|
|
||
| /** 토큰 생성 */ | ||
| private TokenResponseDto saveToken(Authentication authentication, String id){ | ||
| System.out.println("OAuth2 토큰 생성중"); | ||
| // JWT 토큰 생성 | ||
| TokenDto jwtToken = jwtTokenProvider.createToken(authentication); | ||
| Token token = Token.builder() | ||
| .key(id) | ||
| .atValue(jwtToken.getAccessToken()) | ||
| .rtValue(jwtToken.getRefreshToken()) | ||
| .atExpiration(jwtToken.getAtExpiration()) | ||
| .rtExpiration(jwtToken.getRtExpiration()) | ||
| .build(); | ||
| return tokenRepository.save(token).toResponseDto(); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,79 @@ | ||
| package com.chukapoka.server.common.authority.oauth2.service; | ||
|
|
||
| import com.chukapoka.server.common.authority.oauth2.dto.OAuth2Attribute; | ||
| import com.chukapoka.server.common.dto.CustomUserDetails; | ||
| import com.chukapoka.server.common.enums.Authority; | ||
| import com.chukapoka.server.user.entity.User; | ||
| import com.chukapoka.server.user.repository.UserRepository; | ||
| import jakarta.transaction.Transactional; | ||
| import lombok.RequiredArgsConstructor; | ||
| import lombok.extern.slf4j.Slf4j; | ||
| import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; | ||
| import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; | ||
| import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; | ||
| import org.springframework.security.oauth2.client.userinfo.OAuth2UserService; | ||
| import org.springframework.security.oauth2.core.OAuth2AuthenticationException; | ||
| import org.springframework.security.oauth2.core.user.OAuth2User; | ||
| import org.springframework.stereotype.Service; | ||
|
|
||
| import java.util.Map; | ||
| import java.util.Optional; | ||
|
|
||
| @Slf4j | ||
| @Service | ||
| @Transactional | ||
| @RequiredArgsConstructor | ||
| public class CustomOAuth2UserService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> { | ||
| private final UserRepository userRepository; | ||
| private final BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder(); | ||
| @Override | ||
| public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { | ||
| log.debug("Loading user from OAuth2: {}", userRequest); | ||
|
|
||
| // 1. 기본 OAuth2UserService 객체 생성 | ||
| OAuth2UserService<OAuth2UserRequest, OAuth2User> oAuth2UserService = new DefaultOAuth2UserService(); | ||
| System.out.println("oAuth2UserService = " + oAuth2UserService); | ||
| // 2. OAuth2UserService를 사용하여 OAuth2User 정보를 가져온다. | ||
| OAuth2User oAuth2User = oAuth2UserService.loadUser(userRequest); | ||
| System.out.println("oAuth2User = " + oAuth2User); | ||
| // 3. 클라이언트 등록 ID(google, naver, kakao)와 사용자 이름 속성을 가져온다. | ||
| String registrationId = userRequest.getClientRegistration().getRegistrationId(); | ||
| String userNameAttributeName = userRequest.getClientRegistration() | ||
| .getProviderDetails().getUserInfoEndpoint().getUserNameAttributeName(); | ||
|
Comment on lines
+40
to
+42
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
|
||
| // 4. OAuth2UserService를 사용하여 가져온 OAuth2User 정보로 OAuth2Attribute 객체를 만든다. | ||
| OAuth2Attribute oAuth2Attribute = | ||
| OAuth2Attribute.of(registrationId, userNameAttributeName, oAuth2User.getAttributes()); | ||
|
|
||
| // 5. OAuth2Attribute의 속성값들을 Map으로 반환 받는다. | ||
| Map<String, Object> memberAttribute = oAuth2Attribute.convertToMap(); | ||
|
Comment on lines
+46
to
+49
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| System.out.println("memberAttribute = " + memberAttribute); | ||
| // 6. 사용자 email(또는 id) 정보를 가져온다. | ||
| String email = (String) memberAttribute.get("email"); | ||
| log.debug("Email retrieved from OAuth2 attributes: {}", email); | ||
|
Comment on lines
+52
to
+53
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
|
||
| // 7. 이메일로 가입된 회원인지 조회한다. | ||
| Optional<User> findMember = userRepository.findByEmail(email); | ||
|
|
||
|
|
||
|
Comment on lines
+56
to
+58
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| User user; | ||
| /** 회원이 존재하지 않을 경우 */ | ||
| if (findMember.isEmpty()) { | ||
| // user의 패스워드가 null이기 때문에 OAuth 유저는 일반적인 로그인을 할 수 없음. | ||
| user = User.builder() | ||
| .email(email) | ||
| .emailType((String) memberAttribute.get("emailType")) | ||
| .authorities("ROLE_"+Authority.USER.getAuthority()) | ||
| .password(bCryptPasswordEncoder.encode((String) memberAttribute.get("id"))) | ||
| .build(); | ||
|
Comment on lines
+61
to
+68
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| userRepository.save(user); | ||
| } | ||
|
|
||
| /** 회원이 존재할 경우 */ | ||
| else { | ||
| user = findMember.get(); | ||
| user.setEmail(email); // Email이 변경 될 경우 업데이트 | ||
| } | ||
|
Comment on lines
+73
to
+76
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| return new CustomUserDetails(user, memberAttribute); | ||
| } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CustomOAuth2UserService클래스에서loadUser메서드가 구현되어 있습니다. 해당 메서드는 OAuth2 사용자 정보를 가져오고 회원 가입 여부를 확인하여 처리하는 중요한 역할을 합니다.