diff --git a/build.gradle b/build.gradle index 52e13fb5..f49a60fc 100644 --- a/build.gradle +++ b/build.gradle @@ -24,8 +24,6 @@ repositories { } dependencies { - - // Spring Boot implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-mail' @@ -38,6 +36,9 @@ dependencies { runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5' runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5' + // OAuth2 + implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' + // Springdoc OpenAPI implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0") @@ -64,9 +65,8 @@ dependencies { testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'org.springframework.security:spring-security-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' - } tasks.named('test') { useJUnitPlatform() -} +} \ No newline at end of file diff --git a/src/main/java/com/ctrls/auto_enter_view/component/GoogleOAuth2AuthenticationSuccessHandler.java b/src/main/java/com/ctrls/auto_enter_view/component/GoogleOAuth2AuthenticationSuccessHandler.java new file mode 100644 index 00000000..3c5e9f5b --- /dev/null +++ b/src/main/java/com/ctrls/auto_enter_view/component/GoogleOAuth2AuthenticationSuccessHandler.java @@ -0,0 +1,56 @@ +package com.ctrls.auto_enter_view.component; + +import com.ctrls.auto_enter_view.entity.CandidateEntity; +import com.ctrls.auto_enter_view.enums.UserRole; +import com.ctrls.auto_enter_view.repository.CandidateRepository; +import com.ctrls.auto_enter_view.security.JwtTokenProvider; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.security.web.authentication.AuthenticationSuccessHandler; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +@Component +@RequiredArgsConstructor +public class GoogleOAuth2AuthenticationSuccessHandler implements AuthenticationSuccessHandler { + + private final CandidateRepository candidateRepository; + private final KeyGenerator keyGenerator; + private final JwtTokenProvider jwtTokenProvider; + + @Override + @Transactional + public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, + Authentication authentication) throws IOException, ServletException { + + OAuth2User oAuth2User = (OAuth2User) authentication.getPrincipal(); + + CandidateEntity candidateEntity = candidateRepository.findByCandidateKey(oAuth2User.getName()) + .orElseGet(() -> { + CandidateEntity newCandidate = CandidateEntity.builder() + .candidateKey(oAuth2User.getName()) + .name(oAuth2User.getAttribute("name")) + .email(oAuth2User.getAttribute("email")) + .password(keyGenerator.generateKey()) + .phoneNumber("undefinedNumber") + .role(UserRole.ROLE_CANDIDATE) + .build(); + + candidateRepository.save(newCandidate); + + return newCandidate; + }); + + String token = jwtTokenProvider.generateToken(candidateEntity.getEmail(), + candidateEntity.getRole()); + + response.setHeader("Authorization", "Bearer " + token); + response.setContentType("application/json"); + response.sendRedirect("/common/job-postings"); + } +} \ No newline at end of file diff --git a/src/main/java/com/ctrls/auto_enter_view/dto/candidate/GoogleOAuth2User.java b/src/main/java/com/ctrls/auto_enter_view/dto/candidate/GoogleOAuth2User.java new file mode 100644 index 00000000..0885a7ed --- /dev/null +++ b/src/main/java/com/ctrls/auto_enter_view/dto/candidate/GoogleOAuth2User.java @@ -0,0 +1,40 @@ +package com.ctrls.auto_enter_view.dto.candidate; + +import com.ctrls.auto_enter_view.enums.UserRole; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import lombok.AllArgsConstructor; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.oauth2.core.user.OAuth2User; + +@AllArgsConstructor +public class GoogleOAuth2User implements OAuth2User { + + private final Map map; + + @Override + public A getAttribute(String name) { + + return OAuth2User.super.getAttribute(name); + } + + @Override + public Map getAttributes() { + + return map; + } + + @Override + public Collection getAuthorities() { + + return Collections.singletonList(new SimpleGrantedAuthority(UserRole.ROLE_CANDIDATE.name())); + } + + @Override + public String getName() { + + return (String) getAttributes().get("key"); + } +} \ No newline at end of file diff --git a/src/main/java/com/ctrls/auto_enter_view/dto/candidate/GoogleResponse.java b/src/main/java/com/ctrls/auto_enter_view/dto/candidate/GoogleResponse.java new file mode 100644 index 00000000..73787498 --- /dev/null +++ b/src/main/java/com/ctrls/auto_enter_view/dto/candidate/GoogleResponse.java @@ -0,0 +1,34 @@ +package com.ctrls.auto_enter_view.dto.candidate; + +import java.util.Map; +import lombok.AllArgsConstructor; + +@AllArgsConstructor +public class GoogleResponse implements OAuth2Response { + + private final Map attribute; + + @Override + public String getProvider() { + + return "google"; + } + + @Override + public String getProviderId() { + + return attribute.get("sub").toString(); + } + + @Override + public String getEmail() { + + return attribute.get("email").toString(); + } + + @Override + public String getName() { + + return attribute.get("name").toString(); + } +} \ No newline at end of file diff --git a/src/main/java/com/ctrls/auto_enter_view/dto/candidate/OAuth2Response.java b/src/main/java/com/ctrls/auto_enter_view/dto/candidate/OAuth2Response.java new file mode 100644 index 00000000..e3fa7ec8 --- /dev/null +++ b/src/main/java/com/ctrls/auto_enter_view/dto/candidate/OAuth2Response.java @@ -0,0 +1,12 @@ +package com.ctrls.auto_enter_view.dto.candidate; + +public interface OAuth2Response { + + String getProvider(); + + String getProviderId(); + + String getEmail(); + + String getName(); +} \ No newline at end of file diff --git a/src/main/java/com/ctrls/auto_enter_view/security/SecurityConfig.java b/src/main/java/com/ctrls/auto_enter_view/security/SecurityConfig.java index 89c6e5e4..893efd3a 100644 --- a/src/main/java/com/ctrls/auto_enter_view/security/SecurityConfig.java +++ b/src/main/java/com/ctrls/auto_enter_view/security/SecurityConfig.java @@ -1,6 +1,8 @@ package com.ctrls.auto_enter_view.security; +import com.ctrls.auto_enter_view.component.GoogleOAuth2AuthenticationSuccessHandler; import com.ctrls.auto_enter_view.enums.UserRole; +import com.ctrls.auto_enter_view.service.GoogleOAuth2Service; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; @@ -26,6 +28,8 @@ public class SecurityConfig { private final JwtAuthenticationFilter jwtAuthenticationFilter; + private final GoogleOAuth2Service googleOAuth2Service; + private final GoogleOAuth2AuthenticationSuccessHandler googleOAuth2AuthenticationSuccessHandler; @Bean public PasswordEncoder passwordEncoder() { @@ -50,10 +54,16 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .sessionManagement(sessionManagement -> sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS) ) + + //OAuth2 설정 + .oauth2Login(oauth2customizer -> oauth2customizer.userInfoEndpoint( + userInfoEndpointConfig -> userInfoEndpointConfig.userService(googleOAuth2Service)) + .successHandler(googleOAuth2AuthenticationSuccessHandler)) + .authorizeHttpRequests(authHttpRequest -> authHttpRequest // 권한 없이 접근 가능 - .requestMatchers("/api-test/**").permitAll() + .requestMatchers("/oauth2/**", "/api-test/**", "/login/**").permitAll() .requestMatchers("/companies/signup", "/candidates/signup").permitAll() .requestMatchers("/candidates/find-email").permitAll() .requestMatchers("/common/**").permitAll() diff --git a/src/main/java/com/ctrls/auto_enter_view/service/GoogleOAuth2Service.java b/src/main/java/com/ctrls/auto_enter_view/service/GoogleOAuth2Service.java new file mode 100644 index 00000000..569edb5a --- /dev/null +++ b/src/main/java/com/ctrls/auto_enter_view/service/GoogleOAuth2Service.java @@ -0,0 +1,42 @@ +package com.ctrls.auto_enter_view.service; + +import com.ctrls.auto_enter_view.dto.candidate.GoogleOAuth2User; +import com.ctrls.auto_enter_view.dto.candidate.GoogleResponse; +import com.ctrls.auto_enter_view.dto.candidate.OAuth2Response; +import java.util.Map; +import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; +import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; +import org.springframework.security.oauth2.core.OAuth2AuthenticationException; +import org.springframework.security.oauth2.core.OAuth2Error; +import org.springframework.security.oauth2.core.OAuth2ErrorCodes; +import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class GoogleOAuth2Service extends DefaultOAuth2UserService { + + @Override + @Transactional + public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { + + OAuth2User oAuth2User = super.loadUser(userRequest); + String registrationId = userRequest.getClientRegistration().getRegistrationId(); + OAuth2Response oAuth2Response; + + if (registrationId.equals("google")) { + oAuth2Response = new GoogleResponse(oAuth2User.getAttributes()); + } else { + throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_REQUEST)); + } + + String key = oAuth2Response.getProvider() + "-" + oAuth2Response.getProviderId(); + Map immuatableMap = Map.of( + "key", key, + "name", oAuth2Response.getName(), + "email", oAuth2Response.getEmail() + ); + + return new GoogleOAuth2User(immuatableMap); + } +} \ No newline at end of file diff --git a/src/main/resources/logback-spring.xml b/src/main/resources/logback-spring.xml index 9b45a08a..01103fb5 100644 --- a/src/main/resources/logback-spring.xml +++ b/src/main/resources/logback-spring.xml @@ -6,7 +6,9 @@ - %d{yyyy-MM-dd HH:mm:ss.SSS}] %highlight(%-5level) $magenta(%-4relative) --- [ %thread{10} ] %cyan(%logger{20}) : %msg%n + %d{yyyy-MM-dd HH:mm:ss.SSS}] %highlight(%-5level) %magenta(%-4relative) --- [ + %thread{10} ] %cyan(%logger{20}) : %msg%n +