Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ name: Merge Dev to Main
on:
push:
branches: [ main ]
paths-ignore:
- 'README.md'

jobs:
build:
Expand Down
2 changes: 2 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ dependencies {
// Development
developmentOnly 'org.springframework.boot:spring-boot-devtools'

implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'

// Test
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package com.ctrls.auto_enter_view.component;

import static com.ctrls.auto_enter_view.enums.UserRole.ROLE_CANDIDATE;

import com.ctrls.auto_enter_view.entity.CandidateEntity;
import com.ctrls.auto_enter_view.repository.CandidateRepository;
import com.ctrls.auto_enter_view.security.JwtTokenProvider;
import com.ctrls.auto_enter_view.util.RandomGenerator;
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.crypto.password.PasswordEncoder;
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 NaverOAuth2AuthenticationSuccessHandler implements AuthenticationSuccessHandler {

private final CandidateRepository candidateRepository;
private final KeyGenerator keyGenerator;
private final JwtTokenProvider jwtTokenProvider;
private final PasswordEncoder passwordEncoder;

@Override
@Transactional
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
OAuth2User oAuth2User = (OAuth2User) authentication.getPrincipal();

CandidateEntity candidateEntity = candidateRepository.findByEmail(
oAuth2User.getAttribute("email"))
.orElseGet(() -> {
CandidateEntity newCandidate = CandidateEntity.builder()
.candidateKey(keyGenerator.generateKey())
.name(oAuth2User.getAttribute("name"))
.email(oAuth2User.getAttribute("email"))
.password(passwordEncoder.encode(RandomGenerator.generateTemporaryPassword()))
.phoneNumber(oAuth2User.getAttribute("mobile"))
.role(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");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.ctrls.auto_enter_view.component;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
public class PasswordEncoderConfig {

@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
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 NaverOAuth2User implements OAuth2User {

private final Map<String, Object> attributes;

@Override
public <A> A getAttribute(String name) {
return (A) attributes.get(name);
}

@Override
public Map<String, Object> getAttributes() {
return attributes;
}

@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return Collections.singletonList(new SimpleGrantedAuthority(UserRole.ROLE_CANDIDATE.name()));
}

@Override
public String getName() {
return (String) attributes.get("key");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.ctrls.auto_enter_view.dto.candidate;

import java.util.Map;
import lombok.AllArgsConstructor;

@AllArgsConstructor
public class NaverResponse implements OAuth2Response {

private final Map<String, Object> attribute;

@Override
public String getProvider() {
return "naver";
}

@Override
public String getProviderId() {
Map<String, Object> response = (Map<String, Object>) attribute.get("response");
Object providerId = response != null ? response.get("id") : null;
return providerId != null ? providerId.toString() : null;
}

@Override
public String getEmail() {
Map<String, Object> response = (Map<String, Object>) attribute.get("response");
Object email = response != null ? response.get("email") : null;
return email != null ? email.toString() : null;
}

@Override
public String getName() {
Map<String, Object> response = (Map<String, Object>) attribute.get("response");
Object name = response != null ? response.get("name") : null;
return name != null ? name.toString() : null;
}

@Override
public String getNumber() {
Map<String, Object> response = (Map<String, Object>) attribute.get("response");
Object number = response != null ? response.get("mobile") : null;
return number != null ? number.toString() : null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.ctrls.auto_enter_view.dto.candidate;

public interface OAuth2Response {

String getProvider();

String getProviderId();

String getEmail();

String getName();

String getNumber();
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.ctrls.auto_enter_view.security;

import com.ctrls.auto_enter_view.component.NaverOAuth2AuthenticationSuccessHandler;
import com.ctrls.auto_enter_view.enums.UserRole;
import com.ctrls.auto_enter_view.service.NaverOAuth2Service;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
Expand All @@ -11,8 +13,6 @@
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
Expand All @@ -26,12 +26,8 @@
public class SecurityConfig {

private final JwtAuthenticationFilter jwtAuthenticationFilter;

@Bean
public PasswordEncoder passwordEncoder() {

return new BCryptPasswordEncoder();
}
private final NaverOAuth2Service naverOAuth2Service;
private final NaverOAuth2AuthenticationSuccessHandler naverOAuth2AuthenticationSuccessHandler;

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
Expand All @@ -50,10 +46,16 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
.sessionManagement(sessionManagement ->
sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)

.oauth2Login(oauth2customizer -> oauth2customizer
.userInfoEndpoint(userInfoEndpointConfig ->
userInfoEndpointConfig.userService(naverOAuth2Service))
.successHandler(naverOAuth2AuthenticationSuccessHandler))

.authorizeHttpRequests(authHttpRequest -> authHttpRequest

// 권한 없이 접근 가능
.requestMatchers("/api-test/**").permitAll()
.requestMatchers("/login/**", "/oauth2/**", "/api-test/**").permitAll()
.requestMatchers("/companies/signup", "/candidates/signup").permitAll()
.requestMatchers("/candidates/find-email").permitAll()
.requestMatchers("/common/**").permitAll()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.ctrls.auto_enter_view.service;

import com.ctrls.auto_enter_view.dto.candidate.NaverOAuth2User;
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 NaverOAuth2Service extends DefaultOAuth2UserService {

@Override
@Transactional
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
OAuth2User oAuth2User = super.loadUser(userRequest);
String registrationId = userRequest.getClientRegistration().getRegistrationId();
Map<String, Object> attributes;

if (registrationId.equals("naver")) {
attributes = oAuth2User.getAttributes();

Map<String, Object> response = (Map<String, Object>) attributes.get("response");
if (response == null) {
throw new OAuth2AuthenticationException(
new OAuth2Error(OAuth2ErrorCodes.INVALID_REQUEST, "No 'response' field in attributes",
null));
}

String key = "naver-" + response.get("id");

Object name = response.get("name");
Object email = response.get("email");
Object mobile = response.get("mobile");

Map<String, Object> immutableMap = Map.of(
"key", key,
"name", name != null ? name.toString() : null,
"email", email != null ? email.toString() : null,
"mobile", mobile != null ? mobile.toString() : null
);
Comment on lines +40 to +45
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Value 값을 String 으로 넣어주더라도 나중에 사용할 때 다시 Object 로 될텐데 null 확인을 하는 이유가 따로 있으신가요?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

사실 NullPointerException이 떠서 그 부분을 해결하려고 하다보니 null 체크가 여기저기 남아있습니다. 필요 없는 부분들은 후에 삭제하겠습니다!


return new NaverOAuth2User(immutableMap);
} else {
throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_REQUEST));
}
}
}
6 changes: 4 additions & 2 deletions src/main/resources/logback-spring.xml
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<property name="LOG_DIR" value="/app/logs"/>
<property name="LOG_DIR" value="./logs"/>
<property name="LOG_FILE_NAME" value="auto_enter_view"/>

<!-- 콘솔에 남길 로그 -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS}] %highlight(%-5level) $magenta(%-4relative) --- [ %thread{10} ] %cyan(%logger{20}) : %msg%n </pattern>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS}] %highlight(%-5level) %magenta(%-4relative) --- [
%thread{10} ] %cyan(%logger{20}) : %msg%n
</pattern>
</encoder>
</appender>

Expand Down