diff --git a/src/main/java/com/myadd/myadd/post/calendar/controller/CalController.java b/src/main/java/com/myadd/myadd/post/calendar/controller/CalController.java index 1587d36..bc69e3e 100644 --- a/src/main/java/com/myadd/myadd/post/calendar/controller/CalController.java +++ b/src/main/java/com/myadd/myadd/post/calendar/controller/CalController.java @@ -4,7 +4,6 @@ import com.myadd.myadd.post.crud.dto.PostBackDto; import com.myadd.myadd.response.BaseResponse; import com.myadd.myadd.response.BaseResponseStatus; -import com.myadd.myadd.user.security.service.PrincipalDetails; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.security.core.Authentication; diff --git a/src/main/java/com/myadd/myadd/post/crud/controller/PostCrudController.java b/src/main/java/com/myadd/myadd/post/crud/controller/PostCrudController.java index 8d5c9b5..1e46ff9 100644 --- a/src/main/java/com/myadd/myadd/post/crud/controller/PostCrudController.java +++ b/src/main/java/com/myadd/myadd/post/crud/controller/PostCrudController.java @@ -6,18 +6,16 @@ import com.myadd.myadd.post.domain.PostEntity; import com.myadd.myadd.response.BaseResponse; import com.myadd.myadd.response.BaseResponseStatus; -import com.myadd.myadd.user.security.service.PrincipalDetails; +import com.myadd.myadd.user.security.service.CustomUserDetails; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; -import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; -import javax.validation.Valid; import java.io.IOException; @RestController @@ -37,7 +35,7 @@ public BaseResponse create(@RequestPart PostBackDto post, @ModelAtt if(authentication == null) return new BaseResponse<>(BaseResponseStatus.FAILED_NOT_AUTHENTICATION); - Long id = ((PrincipalDetails) authentication.getPrincipal()).getId(); + Long id = ((CustomUserDetails) authentication.getPrincipal()).getId(); log.info(post.toString()); String S3FileName = fileUploadService.upload(image); @@ -68,7 +66,7 @@ public BaseResponse update(@RequestParam("postId") Long postId,@Req if(authentication == null) return new BaseResponse<>(BaseResponseStatus.FAILED_NOT_AUTHENTICATION); - Long id = ((PrincipalDetails) authentication.getPrincipal()).getId(); + Long id = ((CustomUserDetails) authentication.getPrincipal()).getId(); PostEntity postEntity = postCrudService.modifyPost(postId, post, image, id); if(postEntity == null) { diff --git a/src/main/java/com/myadd/myadd/post/search/controller/PostSearchController.java b/src/main/java/com/myadd/myadd/post/search/controller/PostSearchController.java index 2abf6a0..b93913f 100644 --- a/src/main/java/com/myadd/myadd/post/search/controller/PostSearchController.java +++ b/src/main/java/com/myadd/myadd/post/search/controller/PostSearchController.java @@ -1,13 +1,12 @@ package com.myadd.myadd.post.search.controller; -import com.fasterxml.jackson.databind.ser.Serializers; import com.myadd.myadd.post.crud.dto.PostBackDto; import com.myadd.myadd.post.search.dto.PostSearchBackDto; import com.myadd.myadd.post.search.dto.PostSearchFrontDto; import com.myadd.myadd.post.search.service.PostSearchService; import com.myadd.myadd.response.BaseResponse; import com.myadd.myadd.response.BaseResponseStatus; -import com.myadd.myadd.user.security.service.PrincipalDetails; +import com.myadd.myadd.user.security.service.CustomUserDetails; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Controller; @@ -28,7 +27,7 @@ public PostSearchController(PostSearchService postSearchService){ public Long getAuthentication(){ Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if(authentication == null) return null; - Long userId = ((PrincipalDetails) authentication.getPrincipal()).getId(); // UserDetailsImpl은 사용자의 상세 정보를 구현한 클래스 + Long userId = ((CustomUserDetails) authentication.getPrincipal()).getId(); // UserDetailsImpl은 사용자의 상세 정보를 구현한 클래스 return userId; } diff --git a/src/main/java/com/myadd/myadd/user/controller/ChangeProfileController.java b/src/main/java/com/myadd/myadd/user/controller/ChangeProfileController.java index 9af0246..32fa942 100644 --- a/src/main/java/com/myadd/myadd/user/controller/ChangeProfileController.java +++ b/src/main/java/com/myadd/myadd/user/controller/ChangeProfileController.java @@ -1,14 +1,10 @@ package com.myadd.myadd.user.controller; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.ObjectNode; import com.myadd.myadd.fileUpload.service.FileUploadService; import com.myadd.myadd.response.BaseResponse; import com.myadd.myadd.response.BaseResponseStatus; -import com.myadd.myadd.user.domain.dto.ProfileChangeRequestDto; import com.myadd.myadd.user.domain.dto.UserProfileDto; -import com.myadd.myadd.user.domain.entity.UserEntity; -import com.myadd.myadd.user.security.service.PrincipalDetails; +import com.myadd.myadd.user.security.service.CustomUserDetails; import com.myadd.myadd.user.service.ChangeProfileService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -47,8 +43,8 @@ public BaseResponse changeProfile( if(authentication == null) return new BaseResponse<>(BaseResponseStatus.FAILED_NOT_AUTHENTICATION); - String email = ((PrincipalDetails)authentication.getPrincipal()).getEmail(); // 이메일 또는 사용자명 - Long id = ((PrincipalDetails) authentication.getPrincipal()).getId(); // UserDetailsImpl은 사용자의 상세 정보를 구현한 클래스 + String email = ((CustomUserDetails)authentication.getPrincipal()).getEmail(); // 이메일 또는 사용자명 + Long id = ((CustomUserDetails) authentication.getPrincipal()).getId(); // UserDetailsImpl은 사용자의 상세 정보를 구현한 클래스 userProfileDto = changeProfileService.findUser(id, email); @@ -80,8 +76,8 @@ public BaseResponse getMyProfile(){ if(authentication == null) return new BaseResponse<>(BaseResponseStatus.FAILED_NOT_AUTHENTICATION); - String email = ((PrincipalDetails)authentication.getPrincipal()).getEmail(); // 이메일 또는 사용자명 - Long id = ((PrincipalDetails) authentication.getPrincipal()).getId(); + String email = ((CustomUserDetails)authentication.getPrincipal()).getEmail(); // 이메일 또는 사용자명 + Long id = ((CustomUserDetails) authentication.getPrincipal()).getId(); userProfileDto = changeProfileService.findUser(id, email); diff --git a/src/main/java/com/myadd/myadd/user/controller/UserController.java b/src/main/java/com/myadd/myadd/user/controller/UserController.java index e143295..fc58235 100644 --- a/src/main/java/com/myadd/myadd/user/controller/UserController.java +++ b/src/main/java/com/myadd/myadd/user/controller/UserController.java @@ -4,16 +4,15 @@ import com.myadd.myadd.response.BaseResponseStatus; import com.myadd.myadd.user.domain.dto.EmailRequestDto; import com.myadd.myadd.user.domain.entity.UserEntity; -import com.myadd.myadd.user.domain.usertype.UserTypeEnum; +import com.myadd.myadd.user.security.usertype.UserTypeEnum; import com.myadd.myadd.user.domain.dto.UserDto; -import com.myadd.myadd.user.security.service.PrincipalDetails; +import com.myadd.myadd.user.security.service.CustomUserDetails; import com.myadd.myadd.user.service.UserService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; -import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*; // 로그인은 spring security에서 처리해주므로 별도로 controller를 구현할 필요 없음 @@ -69,8 +68,8 @@ public BaseResponse deleteUser() { if(authentication == null) return new BaseResponse<>(BaseResponseStatus.FAILED_NOT_AUTHENTICATION); - String email = ((PrincipalDetails)authentication.getPrincipal()).getEmail(); // 이메일 또는 사용자명 - Long id = ((PrincipalDetails) authentication.getPrincipal()).getId(); // UserDetailsImpl은 사용자의 상세 정보를 구현한 클래스 + String email = ((CustomUserDetails)authentication.getPrincipal()).getEmail(); // 이메일 또는 사용자명 + Long id = ((CustomUserDetails) authentication.getPrincipal()).getId(); // UserDetailsImpl은 사용자의 상세 정보를 구현한 클래스 if(userService.findByEmail(email) == null || !userService.deleteUser(id, email)) return new BaseResponse<>(BaseResponseStatus.FAILED_ALREADY_DELETE_USER); diff --git a/src/main/java/com/myadd/myadd/user/domain/dto/UserDto.java b/src/main/java/com/myadd/myadd/user/domain/dto/UserDto.java index 05619c4..7610c15 100644 --- a/src/main/java/com/myadd/myadd/user/domain/dto/UserDto.java +++ b/src/main/java/com/myadd/myadd/user/domain/dto/UserDto.java @@ -1,6 +1,6 @@ package com.myadd.myadd.user.domain.dto; -import com.myadd.myadd.user.domain.usertype.UserTypeEnum; +import com.myadd.myadd.user.security.usertype.UserTypeEnum; import com.myadd.myadd.user.domain.entity.UserEntity; import lombok.*; diff --git a/src/main/java/com/myadd/myadd/user/domain/entity/UserEntity.java b/src/main/java/com/myadd/myadd/user/domain/entity/UserEntity.java index 6cc453b..fde0f7d 100644 --- a/src/main/java/com/myadd/myadd/user/domain/entity/UserEntity.java +++ b/src/main/java/com/myadd/myadd/user/domain/entity/UserEntity.java @@ -3,7 +3,7 @@ import com.myadd.myadd.post.domain.PostEntity; import com.myadd.myadd.user.domain.dto.UserDto; -import com.myadd.myadd.user.domain.usertype.UserTypeEnum; +import com.myadd.myadd.user.security.usertype.UserTypeEnum; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; diff --git a/src/main/java/com/myadd/myadd/user/non_security/google/service/GoogleLoginService.java b/src/main/java/com/myadd/myadd/user/non_security/google/service/GoogleLoginService.java index 41e4324..8b5bb54 100644 --- a/src/main/java/com/myadd/myadd/user/non_security/google/service/GoogleLoginService.java +++ b/src/main/java/com/myadd/myadd/user/non_security/google/service/GoogleLoginService.java @@ -3,7 +3,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.myadd.myadd.user.domain.entity.UserEntity; -import com.myadd.myadd.user.domain.usertype.UserTypeEnum; +import com.myadd.myadd.user.security.usertype.UserTypeEnum; import com.myadd.myadd.user.repository.UserRepository; import lombok.RequiredArgsConstructor; import org.springframework.core.env.Environment; diff --git a/src/main/java/com/myadd/myadd/user/non_security/kakao/controller/KakaoLoginController.java b/src/main/java/com/myadd/myadd/user/non_security/kakao/controller/KakaoLoginController.java index c11c3ef..71679e2 100644 --- a/src/main/java/com/myadd/myadd/user/non_security/kakao/controller/KakaoLoginController.java +++ b/src/main/java/com/myadd/myadd/user/non_security/kakao/controller/KakaoLoginController.java @@ -3,7 +3,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.myadd.myadd.user.domain.entity.UserEntity; -import com.myadd.myadd.user.security.service.PrincipalDetails; +import com.myadd.myadd.user.security.service.CustomUserDetails; import com.myadd.myadd.user.non_security.kakao.service.KakaoLoginService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -62,8 +62,8 @@ public String kakaoCallback(@RequestParam String code, HttpServletRequest reques @PostMapping("/users/my-info/delete/kakao-user") public @ResponseBody String kakaoWithdrawal() { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - String email = ((PrincipalDetails)authentication.getPrincipal()).getEmail(); // 이메일 또는 사용자명 - Long id = ((PrincipalDetails) authentication.getPrincipal()).getId(); // UserDetailsImpl은 사용자의 상세 정보를 구현한 클래스 + String email = ((CustomUserDetails)authentication.getPrincipal()).getEmail(); // 이메일 또는 사용자명 + Long id = ((CustomUserDetails) authentication.getPrincipal()).getId(); // UserDetailsImpl은 사용자의 상세 정보를 구현한 클래스 log.info("email = {}", email); log.info("id = {}", id); return kakaoLoginService.kakaoWithdrawal(id, email); diff --git a/src/main/java/com/myadd/myadd/user/non_security/kakao/service/KakaoLoginService.java b/src/main/java/com/myadd/myadd/user/non_security/kakao/service/KakaoLoginService.java index 715ea1f..fc0a2c2 100644 --- a/src/main/java/com/myadd/myadd/user/non_security/kakao/service/KakaoLoginService.java +++ b/src/main/java/com/myadd/myadd/user/non_security/kakao/service/KakaoLoginService.java @@ -4,7 +4,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.myadd.myadd.user.domain.entity.UserEntity; -import com.myadd.myadd.user.domain.usertype.UserTypeEnum; +import com.myadd.myadd.user.security.usertype.UserTypeEnum; import com.myadd.myadd.user.repository.UserRepository; import com.myadd.myadd.user.non_security.kakao.OAuthToken; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/com/myadd/myadd/user/security/config/PasswordEncoder.java b/src/main/java/com/myadd/myadd/user/security/config/PasswordEncoder.java index ef98f42..f9a9e04 100644 --- a/src/main/java/com/myadd/myadd/user/security/config/PasswordEncoder.java +++ b/src/main/java/com/myadd/myadd/user/security/config/PasswordEncoder.java @@ -8,6 +8,9 @@ @Configuration public class PasswordEncoder { // BCrypt 알고리즘을 사용하는 PasswordEncoder 빈 생성 + // 단순히 Bean이 싱글톤으로 관리되기 때문이라고 할 수 있겠다. + // BCryptPasswordEncoder의 용도 특성상 Bean으로 등록하지 않으면 객체가 무분별하게 생성될 수 있음. + // 이런 부담을 줄이고자 Bean으로 등록해서 사용. @Bean public BCryptPasswordEncoder encodePwd() { return new BCryptPasswordEncoder(); diff --git a/src/main/java/com/myadd/myadd/user/security/config/SecurityFilterChainConfig.java b/src/main/java/com/myadd/myadd/user/security/config/SecurityFilterChainConfig.java index d74bc54..ceee426 100644 --- a/src/main/java/com/myadd/myadd/user/security/config/SecurityFilterChainConfig.java +++ b/src/main/java/com/myadd/myadd/user/security/config/SecurityFilterChainConfig.java @@ -4,13 +4,15 @@ import com.myadd.myadd.response.BaseResponse; import com.myadd.myadd.response.BaseResponseStatus; import com.myadd.myadd.user.security.handler.CustomLogoutSuccessHandler; -import com.myadd.myadd.user.security.service.PrincipalOauth2UserService; +import com.myadd.myadd.user.security.service.AuthenticationProvider; import com.myadd.myadd.user.security.handler.CustomAuthenticationFailureHandler; import com.myadd.myadd.user.security.handler.CustomAuthenticationSuccessHandler; +import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.core.Authentication; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; @@ -20,42 +22,50 @@ import javax.servlet.http.HttpServletResponse; import java.io.IOException; +// 어떤 요청에 대한 엔드 포인트에 도달하기 전에 요청을 가로채서 어떤 작업을 수행하는 컴포넌트를 “서블릿 필터(Servlet Filter)”라고 함. (보호나 인증 등에 대한 처리를 전처리하기 위해 사용) +// 이러한 필터들이 Chain, 즉 사슬처럼 엮인 것을 Filter Chain이라고 부름. // Spring Security: SecurityFilterChain @Configuration +@RequiredArgsConstructor +@EnableWebSecurity // // 스프링 시큐리티 필터가 스프링 필터체인에 등록이 됨 public class SecurityFilterChainConfig { - @Autowired - private PrincipalOauth2UserService principalOauth2UserService; + private AuthenticationProvider authenticationProvider; + // 관련 있는 설정들은 람다를 사용해서 그룹화 @Bean public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception { - httpSecurity.httpBasic(); // HTTP Basic 인증 활성화 - httpSecurity.csrf().disable(); // CSRF 보호 비활성화 (주의: 프로덕션 환경에서는 권장되지 않음) - httpSecurity.authorizeHttpRequests() - .antMatchers("/posts/**").authenticated() // /posts/** 경로는 인증된 사용자만 접근 가능 - .antMatchers("/users/my-info/**").authenticated() // /users/my-info/** 경로는 인증된 사용자만 접근 가능 - .antMatchers("/users/change-password/**").authenticated() // /users/change-password/** 경로는 인증된 사용자만 접근 가능 - .anyRequest().permitAll() // 그 외 모든 요청은 누구나 접근 가능 + httpSecurity + .httpBasic() // HTTP Basic 인증 활성화 .and() - .formLogin() // 폼 로그인 설정 - // 시큐리티가 /users/login/email 경로로 로직을 만들어서 시큐리티 로그인을 처리함(로그인 처리 URL 설정) - // 결과적으로 로그인을 위해 UserController에 따로 "/users/login/email"을 구현하지 않아도 괜찮다. - // 매우 편리하지만, 이 과정에서 UserDetails가 필요하기에 따로 이를 구현한 클래스를 만들어줘야한다. - // 이 로그인 과정에서 필요한 것이 있기 때문에 auth 패키지를 파서 PrincipalDetails 을 만들어줘야한다. - .loginProcessingUrl("/users/login/email") - .successHandler(new CustomAuthenticationSuccessHandler()) // 로그인 성공 핸들러 설정 - .failureHandler(new CustomAuthenticationFailureHandler()) // 로그인 실패 핸들러 설정 - .usernameParameter("email") // 사용자 이름 파라미터를 'email'로 설정 - .and() - .logout() // 로그아웃 설정 - .logoutUrl("/users/my-info/logout") - .logoutSuccessHandler(new CustomLogoutSuccessHandler()) - .and() - .oauth2Login() // OAuth2 로그인 설정 - .userInfoEndpoint() - // OAuth2 사용자 서비스 설정 - // 로그인한 사용자에게 받은 정보가 principalOauth2UserService의 매개변수인 userRequest로 return - .userService(principalOauth2UserService); + .csrf().disable() // CSRF 보호 비활성화 (주의: 프로덕션 환경에서는 권장되지 않음) + .authorizeHttpRequests(auth -> auth // 인증 요청 설정 + // /posts/**, /users/my-info/**, /users/change-password/** 경로는 인증된 사용자만 접근 가능 + .antMatchers("/posts/**", "/users/my-info/**", "/users/change-password/**").authenticated() + // 그 외 모든 요청은 누구나 접근 가능 + .anyRequest().permitAll() + ) + .formLogin(form -> form // 폼 로그인 설정 + // 시큐리티가 /users/login/email 경로로 로직을 만들어서 시큐리티 로그인을 처리함(이메일 방식 로그인 처리 URL 설정) + // 로그인을 위해 UserController에 따로 "/users/login/email"을 구현하지 않아도 됨. + // 이 과정에서 UserDetails가 필요하기에 따로 이를 구현한 클래스를 만들어줘야한다. + // 이 로그인 과정에서 필요한 것이 있기 때문에 auth 패키지를 파서 PrincipalDetails 을 만들어줘야한다. + .loginProcessingUrl("/users/login/email") + .successHandler(new CustomAuthenticationSuccessHandler()) // 로그인 성공 핸들러 설정 + .failureHandler(new CustomAuthenticationFailureHandler()) // 로그인 실패 핸들러 설정 + .usernameParameter("email") // 사용자 이름 파라미터를 'email'로 설정 + ) + .logout(logout -> logout // 로그아웃 설정 + .logoutUrl("/users/my-info/logout") + .logoutSuccessHandler(new CustomLogoutSuccessHandler()) + ) + .oauth2Login(oauth2 -> oauth2 // OAuth2 로그인 설정 + .userInfoEndpoint() + // OAuth2로 로그인한 사용자에 대해서는 authenticationProvider에 구현한대로 처리함. + // 로그인한 사용자에게 받은 정보가 AuthenticationProvider의 매개변수인 userRequest로 return + .userService(authenticationProvider) + ) ; + return httpSecurity.build(); } } diff --git a/src/main/java/com/myadd/myadd/user/security/handler/CustomAuthenticationSuccessHandler.java b/src/main/java/com/myadd/myadd/user/security/handler/CustomAuthenticationSuccessHandler.java index 4bad54c..cc3dbed 100644 --- a/src/main/java/com/myadd/myadd/user/security/handler/CustomAuthenticationSuccessHandler.java +++ b/src/main/java/com/myadd/myadd/user/security/handler/CustomAuthenticationSuccessHandler.java @@ -12,6 +12,7 @@ import javax.servlet.http.HttpServletResponse; import java.io.IOException; +// Spring Security: Authentication public class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandler { @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication) throws IOException, ServletException { @@ -33,4 +34,22 @@ public void emailLoginSuccess(HttpServletResponse response) throws IOException { response.setCharacterEncoding("UTF-8"); response.getWriter().write(jsonResponse); } + + public String oauth2LoginSuccess(HttpServletResponse response, String provider) throws IOException { + BaseResponse successResponse = null; + + if(provider.equals("google")) + successResponse = new BaseResponse<>(BaseResponseStatus.SUCCESS_GOOGLE_LOGIN); + else if(provider.equals("kakao")) + successResponse = new BaseResponse<>(BaseResponseStatus.SUCCESS_KAKAO_LOGIN); + + String jsonResponse = new ObjectMapper().writeValueAsString(successResponse); + + response.setStatus(HttpServletResponse.SC_OK); + response.setContentType("application/json"); + response.setCharacterEncoding("UTF-8"); + response.getWriter().write(jsonResponse); + + return jsonResponse; + } } diff --git a/src/main/java/com/myadd/myadd/user/security/service/PrincipalOauth2UserService.java b/src/main/java/com/myadd/myadd/user/security/service/AuthenticationProvider.java similarity index 51% rename from src/main/java/com/myadd/myadd/user/security/service/PrincipalOauth2UserService.java rename to src/main/java/com/myadd/myadd/user/security/service/AuthenticationProvider.java index 09c55d7..6e4d67f 100644 --- a/src/main/java/com/myadd/myadd/user/security/service/PrincipalOauth2UserService.java +++ b/src/main/java/com/myadd/myadd/user/security/service/AuthenticationProvider.java @@ -1,16 +1,14 @@ package com.myadd.myadd.user.security.service; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.myadd.myadd.response.BaseResponse; -import com.myadd.myadd.response.BaseResponseStatus; import com.myadd.myadd.user.domain.entity.UserEntity; -import com.myadd.myadd.user.domain.usertype.UserTypeEnum; +import com.myadd.myadd.user.security.handler.CustomAuthenticationSuccessHandler; +import com.myadd.myadd.user.security.usertype.UserTypeEnum; import com.myadd.myadd.user.repository.UserRepository; -import com.myadd.myadd.user.domain.usertype.GoogleUserInfo; -import com.myadd.myadd.user.domain.usertype.KakaoUserInfo; -import com.myadd.myadd.user.domain.usertype.OAuth2UserInfo; +import com.myadd.myadd.user.security.usertype.GoogleUserInfo; +import com.myadd.myadd.user.security.usertype.KakaoUserInfo; +import com.myadd.myadd.user.security.usertype.OAuth2UserInfo; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; @@ -26,27 +24,29 @@ import java.util.Optional; import java.util.UUID; +// OAuth2 로그인 처리를 위한 서비스 +// Spring Security: AuthenticationProvider(사용자 정보를 로드하는 핵심 인터페이스) @Slf4j +@RequiredArgsConstructor @Service -public class PrincipalOauth2UserService extends DefaultOAuth2UserService { // Oauth2 로그인 +public class AuthenticationProvider extends DefaultOAuth2UserService { // Oauth2 로그인 - @Autowired - private BCryptPasswordEncoder bCryptPasswordEncoder; - - @Autowired + private BCryptPasswordEncoder bCryptPasswordEncoder; // 비밀번호 암호화를 위한 인코더 + private CustomAuthenticationSuccessHandler customAuthenticationSuccessHandler; private UserRepository userRepository; - @Override // 구글로부터 받은 userRequest 데이터에 대한 후처리를 하는 메서드 + // OAuth2 제공자인 구글 혹은 카카오로부터 받은 사용자 정보인 userRequest에 대한 후처리를 하는 메서드 + @Override public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { - log.info("userRequest = {}", userRequest); - log.info("userRequest.getClientRegistration = {}", userRequest.getClientRegistration()); // registrationId = google을 통해 어떤 Oauth로 로그인 했는지 확인가능 - log.info("userRequest.getAccessToken = {}", userRequest.getAccessToken().getTokenValue()); // getToenValue()를 통해 실제 액세스 토큰 확인 가능 + log.info("userRequest.getClientRegistration = {}", userRequest.getClientRegistration()); // 어떤 Oauth로 로그인 했는지 확인 + log.info("userRequest.getAccessToken = {}", userRequest.getAccessToken().getTokenValue()); // 실제 액세스 토큰 확인 + // userRequest 정보를 통해 OAuth2User 객체(회원프로필) 얻기 OAuth2User oAuth2User = super.loadUser(userRequest); - // userRequest 정보를 통해서 loadUser() 함수를 호출하여 회원프로필을 구글로부터 받음 // {sub=104717591461978030161, name=강병준, given_name=병준, family_name=강, picture=https://lh3.googleusercontent.com/a/AAcHTtczGvv086yOdzmf0UuQxF0cdYVIRVDooGQ3qWOIeLUv3Q=s96-c, email=bjkang402@gmail.com, email_verified=true, locale=ko} log.info("getAttributes = {}", oAuth2User.getAttributes()); + // OAuth2 제공자(Google, Kakao 등)에 따라 사용자 정보 처리 방식 분기 OAuth2UserInfo oAuth2UserInfo = null; if(userRequest.getClientRegistration().getRegistrationId().equals("google")){ log.info("구글 유저"); @@ -56,27 +56,21 @@ else if(userRequest.getClientRegistration().getRegistrationId().equals("kakao")) log.info("카카오 유저"); oAuth2UserInfo = new KakaoUserInfo((Map)oAuth2User.getAttributes().get("kakao_account")); } - -// String provider = userRequest.getClientRegistration().getRegistrationId(); // google -// String nickName = oAuth2User.getAttribute("name"); // 강병준 -// String profile = oAuth2User.getAttribute("picture"); // https://lh3.googleusercontent.com/a/AAcHTtczGvv086yOdzmf0UuQxF0cdYVIRVDooGQ3qWOIeLUv3Q=s96-c -// UserTypeEnum userTypeEnum = UserTypeEnum.GOOGLE; // 2 -// String email = oAuth2User.getAttribute("email"); // bjkang402@gamil.com -// String password = bCryptPasswordEncoder.encode(UUID.randomUUID().toString()); - - String provider = userRequest.getClientRegistration().getRegistrationId(); - String nickName = oAuth2UserInfo.getNickname(); - String profile = oAuth2UserInfo.getProfile(); - UserTypeEnum userTypeEnum = oAuth2UserInfo.getUsertype(); - String email = provider.toLowerCase() + "_" + oAuth2UserInfo.getEmail(); + + // 주석에 적어둔 것은 구글로 로그인 한 경우 + String provider = userRequest.getClientRegistration().getRegistrationId(); // google + String nickName = oAuth2UserInfo.getNickname(); // 강병준 + String profile = oAuth2UserInfo.getProfile(); // https://lh3.googleusercontent.com/a/AAcHTtczGvv086yOdzmf0UuQxF0cdYVIRVDooGQ3qWOIeLUv3Q=s96-c + UserTypeEnum userTypeEnum = oAuth2UserInfo.getUsertype(); // 2 + String email = oAuth2UserInfo.getEmail(); // bjkang402@gamil.com String password = bCryptPasswordEncoder.encode(UUID.randomUUID().toString()); - log.info("userInfo = {}", provider + " " + nickName + " " + profile + " " +userTypeEnum + " " + email + " "+password); + // 소셜 로그인을 한 이메일에 User 객체 찾기 + Optional userEntityOptional = userRepository.findByEmail(email); + UserEntity userEntity = null; - Optional userEntityOptional= userRepository.findByEmail(email); - UserEntity userEntity; - - if(!userEntityOptional.isPresent()){ + // 소셜 로그인을 한 이메일이 없다면 회원가입 + if(userEntityOptional.isEmpty()){ userEntity = UserEntity.builder() .nickname(nickName) .profile(profile) @@ -86,22 +80,23 @@ else if(userRequest.getClientRegistration().getRegistrationId().equals("kakao")) .build(); userRepository.save(userEntity); } - else{ + else{ // 이미 존재하는 사용자라면 조회된 정보 사용 userEntity = userEntityOptional.get(); log.info("이미 존재하는 OAuth2 회원입니다."); } - // response 출력을 위한 응답 스트림 처리 + // 현재 요청의 HttpServletResponse 객체 얻기 HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse(); - // 로그인 성공 메시지 생성 + // OAuth2 로그인 성공 처리 String successMessage = null; try { - successMessage = oauth2LoginSuccess(response, provider); + successMessage = customAuthenticationSuccessHandler.oauth2LoginSuccess(response, provider); } catch (IOException e) { throw new RuntimeException(e); } + // 응답 설정 및 메시지 작성 response.setContentType("application/json;charset=UTF-8"); try { @@ -115,24 +110,9 @@ else if(userRequest.getClientRegistration().getRegistrationId().equals("kakao")) throw new RuntimeException(e); } - return new PrincipalDetails(userEntity, oAuth2User.getAttributes()); // Authentication 객체 안에 들어감 + // Spring Security의 인증 객체에 들어갈 UserDetails 구현체 반환(Authentication 객체 안에 들어감) + return new CustomUserDetails(userEntity, oAuth2User.getAttributes()); } - public String oauth2LoginSuccess(HttpServletResponse response, String provider) throws IOException { - BaseResponse successResponse = null; - - if(provider.equals("google")) - successResponse = new BaseResponse<>(BaseResponseStatus.SUCCESS_GOOGLE_LOGIN); - else if(provider.equals("kakao")) - successResponse = new BaseResponse<>(BaseResponseStatus.SUCCESS_KAKAO_LOGIN); - String jsonResponse = new ObjectMapper().writeValueAsString(successResponse); - - // response.setStatus(HttpServletResponse.SC_OK); - // response.setContentType("application/json"); - // response.setCharacterEncoding("UTF-8"); - // response.getWriter().write(jsonResponse); - - return jsonResponse; - } } diff --git a/src/main/java/com/myadd/myadd/user/security/service/PrincipalDetails.java b/src/main/java/com/myadd/myadd/user/security/service/CustomUserDetails.java similarity index 85% rename from src/main/java/com/myadd/myadd/user/security/service/PrincipalDetails.java rename to src/main/java/com/myadd/myadd/user/security/service/CustomUserDetails.java index 299380d..14bbaa0 100644 --- a/src/main/java/com/myadd/myadd/user/security/service/PrincipalDetails.java +++ b/src/main/java/com/myadd/myadd/user/security/service/CustomUserDetails.java @@ -10,6 +10,7 @@ import java.util.Collection; import java.util.Map; +// Spring Security: UserDetails(사용자 정보를 담는 인터페이스) // 시큐리티가 /login 주소 요청이 오면 낚아채서 로그인 진행 // 로그인 진행 완료 시 시큐리티 session을 생성 (Security ContextHolder) // 오브젝트 타입 => Authentication 타입 객체. Authentication 안에 User 정보가 있어야 함. @@ -17,18 +18,18 @@ // Authentication 안에 들어갈 수 있는 객체는 UserDeatails 객체. 이를 통해 User object에 접근 가능 // Security Session => Authentication => UserDetails(PrincipalDetails) @Data -public class PrincipalDetails implements UserDetails, OAuth2User { // PrincipalDetails가 UserDetails 타입이 됨. +public class CustomUserDetails implements UserDetails, OAuth2User { // PrincipalDetails가 UserDetails 타입이 됨. - private UserEntity user; // 콤포지션 - private Map attributes; + private UserEntity user; // 사용자 엔티티 + private Map attributes; // OAuth2 속성 // 이메일 회원이 로그인할 때의 생성자 - public PrincipalDetails(UserEntity user){ + public CustomUserDetails(UserEntity user){ this.user = user; } // Oauth2 회원이 로그인할 때의 생성자 - public PrincipalDetails(UserEntity user, Map attributes){ + public CustomUserDetails(UserEntity user, Map attributes){ this.user = user; this.attributes = attributes; } diff --git a/src/main/java/com/myadd/myadd/user/security/service/PrincipalEmailUserService.java b/src/main/java/com/myadd/myadd/user/security/service/CustomUserDetailsService.java similarity index 88% rename from src/main/java/com/myadd/myadd/user/security/service/PrincipalEmailUserService.java rename to src/main/java/com/myadd/myadd/user/security/service/CustomUserDetailsService.java index a1231b5..7fff19a 100644 --- a/src/main/java/com/myadd/myadd/user/security/service/PrincipalEmailUserService.java +++ b/src/main/java/com/myadd/myadd/user/security/service/CustomUserDetailsService.java @@ -8,10 +8,11 @@ import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; +// Spring Security: UserDetailsService(사용자 정보를 로드하는 핵심 인터페이스) // 시큐리티 설정(SecurityConfig)에서 loginProcessUrl("/login"); // /login 요청이 오면 자동으로 UserDetailsService 타입으로 IoC 되어 있는 loadUserByUsername 함수가 실행 @Service -public class PrincipalEmailUserService implements UserDetailsService { // 일반적인 로그인 +public class CustomUserDetailsService implements UserDetailsService { // 일반적인 로그인 @Autowired private UserRepository userRepository; @@ -28,10 +29,7 @@ public UserDetails loadUserByUsername(String email) throws UsernameNotFoundExcep // -> 찾아지면 loadUserByUsername() 호출. UserEntity userEntity = userRepository.findByEmail(email).get(); - if(userEntity != null){ - return new PrincipalDetails(userEntity); - } + return new CustomUserDetails(userEntity); - return null; } } diff --git a/src/main/java/com/myadd/myadd/user/domain/usertype/GoogleUserInfo.java b/src/main/java/com/myadd/myadd/user/security/usertype/GoogleUserInfo.java similarity index 93% rename from src/main/java/com/myadd/myadd/user/domain/usertype/GoogleUserInfo.java rename to src/main/java/com/myadd/myadd/user/security/usertype/GoogleUserInfo.java index b080928..4e974c1 100644 --- a/src/main/java/com/myadd/myadd/user/domain/usertype/GoogleUserInfo.java +++ b/src/main/java/com/myadd/myadd/user/security/usertype/GoogleUserInfo.java @@ -1,4 +1,4 @@ -package com.myadd.myadd.user.domain.usertype; +package com.myadd.myadd.user.security.usertype; import java.util.Map; diff --git a/src/main/java/com/myadd/myadd/user/domain/usertype/KakaoUserInfo.java b/src/main/java/com/myadd/myadd/user/security/usertype/KakaoUserInfo.java similarity index 94% rename from src/main/java/com/myadd/myadd/user/domain/usertype/KakaoUserInfo.java rename to src/main/java/com/myadd/myadd/user/security/usertype/KakaoUserInfo.java index af27ac0..8f16bbc 100644 --- a/src/main/java/com/myadd/myadd/user/domain/usertype/KakaoUserInfo.java +++ b/src/main/java/com/myadd/myadd/user/security/usertype/KakaoUserInfo.java @@ -1,4 +1,4 @@ -package com.myadd.myadd.user.domain.usertype; +package com.myadd.myadd.user.security.usertype; import java.util.Map; diff --git a/src/main/java/com/myadd/myadd/user/domain/usertype/OAuth2UserInfo.java b/src/main/java/com/myadd/myadd/user/security/usertype/OAuth2UserInfo.java similarity index 74% rename from src/main/java/com/myadd/myadd/user/domain/usertype/OAuth2UserInfo.java rename to src/main/java/com/myadd/myadd/user/security/usertype/OAuth2UserInfo.java index 091d538..d015321 100644 --- a/src/main/java/com/myadd/myadd/user/domain/usertype/OAuth2UserInfo.java +++ b/src/main/java/com/myadd/myadd/user/security/usertype/OAuth2UserInfo.java @@ -1,4 +1,4 @@ -package com.myadd.myadd.user.domain.usertype; +package com.myadd.myadd.user.security.usertype; public interface OAuth2UserInfo { String getEmail(); diff --git a/src/main/java/com/myadd/myadd/user/domain/usertype/UserTypeEnum.java b/src/main/java/com/myadd/myadd/user/security/usertype/UserTypeEnum.java similarity index 93% rename from src/main/java/com/myadd/myadd/user/domain/usertype/UserTypeEnum.java rename to src/main/java/com/myadd/myadd/user/security/usertype/UserTypeEnum.java index cf153d7..4fdd5cc 100644 --- a/src/main/java/com/myadd/myadd/user/domain/usertype/UserTypeEnum.java +++ b/src/main/java/com/myadd/myadd/user/security/usertype/UserTypeEnum.java @@ -1,4 +1,4 @@ -package com.myadd.myadd.user.domain.usertype; +package com.myadd.myadd.user.security.usertype; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonKey; diff --git a/src/main/java/com/myadd/myadd/user/service/ChangePasswordService.java b/src/main/java/com/myadd/myadd/user/service/ChangePasswordService.java index 872e505..5d39cbe 100644 --- a/src/main/java/com/myadd/myadd/user/service/ChangePasswordService.java +++ b/src/main/java/com/myadd/myadd/user/service/ChangePasswordService.java @@ -1,7 +1,7 @@ package com.myadd.myadd.user.service; import com.myadd.myadd.user.domain.entity.UserEntity; -import com.myadd.myadd.user.domain.usertype.UserTypeEnum; +import com.myadd.myadd.user.security.usertype.UserTypeEnum; import com.myadd.myadd.user.repository.EmailSignupRepository; import com.myadd.myadd.user.repository.UserRepository; import com.myadd.myadd.user.domain.entity.EmailAuthEntity;