Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ public class CorsMvcConfig implements WebMvcConfigurer {

@Override
public void addCorsMappings(CorsRegistry corsRegistry) {

corsRegistry.addMapping("/**")
corsRegistry.addMapping("/public/**") // Spring Security์—์„œ ์ œ์™ธ๋œ ๊ฒฝ๋กœ๋งŒ ๊ด€๋ฆฌ
.allowedOrigins("http://localhost:3000")
.allowedMethods("GET", "POST", "OPTIONS")
.allowedHeaders("*")
.exposedHeaders("Set-Cookie")
.allowedOrigins("http://localhost:3000");
.allowCredentials(true);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.web.cors.CorsConfiguration;

import java.util.Collections;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

@Configuration
@EnableWebSecurity
Expand All @@ -40,18 +40,32 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {

.authorizeHttpRequests(auth -> auth
.requestMatchers("/login", "/oauth2/**", "/auth/**", "/reissue").permitAll()
// "/" ๊ฒฝ๋กœ ์ œ์™ธ
.anyRequest().authenticated())

.oauth2Login(oauth2 -> oauth2
.userInfoEndpoint(userInfo ->
userInfo.userService(customOAuth2UserService))
.successHandler(customSuccessHandler))
.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class) // JWT ํ•„ํ„ฐ ์ถ”๊ฐ€
.addFilterBefore(customLogoutFilter, LogoutFilter.class);

.addFilterBefore(customLogoutFilter, LogoutFilter.class)
// CORS ์„ค์ •์„ ๋žŒ๋‹ค ํ˜•์‹์œผ๋กœ ์ถ”๊ฐ€
.cors(cors -> {
CorsConfigurationSource source = corsConfigurationSource();
cors.configurationSource(source);
});

return http.build();
}
}

@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.addAllowedOrigin("http://localhost:3000"); // ํ—ˆ์šฉํ•  ํด๋ผ์ด์–ธํŠธ ์ฃผ์†Œ
configuration.addAllowedMethod("*"); // ๋ชจ๋“  HTTP ๋ฉ”์„œ๋“œ ํ—ˆ์šฉ
configuration.addAllowedHeader("*"); // ๋ชจ๋“  ํ—ค๋” ํ—ˆ์šฉ
configuration.setAllowCredentials(true); // ์ฟ ํ‚ค ํ—ˆ์šฉ
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration); // ๋ชจ๋“  ๊ฒฝ๋กœ์— ๋Œ€ํ•ด ์„ค์ • ์ ์šฉ
return source;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ public void addInterceptors(InterceptorRegistry registry) {
* // (...)
* .anyRequest().authenticated()) // ๊ทธ ์™ธ์˜ ์š”์ฒญ์€ ์ธ์ฆ ํ•„์š”
*/
registry.addInterceptor(authorizationInterceptor)
.addPathPatterns("/**") // ๋ชจ๋“  ๊ฒฝ๋กœ์— ์ ์šฉ, '/' ์ œ์™ธ ํ•„์š” ์—†์Œ
.excludePathPatterns("/error", "/logout", "/api/login"); // ์—๋Ÿฌ ํŽ˜์ด์ง€, ๋กœ๊ทธ์•„์›ƒ, ๋กœ๊ทธ์ธ API๋Š” ์ œ์™ธ
registry.addInterceptor(authorizationInterceptor)
.addPathPatterns("/**") // ๋ชจ๋“  ๊ฒฝ๋กœ์— ์ ์šฉ, '/' ์ œ์™ธ ํ•„์š” ์—†์Œ
.excludePathPatterns("/error", "/logout", "/api/login"); // ์—๋Ÿฌ ํŽ˜์ด์ง€, ๋กœ๊ทธ์•„์›ƒ, ๋กœ๊ทธ์ธ API๋Š” ์ œ์™ธ
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ public ResponseEntity<?> reissue(HttpServletRequest request, HttpServletResponse
String role = jwtUtil.getRole(refresh);

//make new JWT
String newAccess = jwtUtil.createJwt("access", username, role, 600000L);
String newAccess = jwtUtil.createJwt("access", username, role, 60000000L);
String newRefresh = jwtUtil.createJwt("refresh", username, role, 86400000L);

//Refresh ํ† ํฐ ์ €์žฅ DB์— ๊ธฐ์กด์˜ Refresh ํ† ํฐ ์‚ญ์ œ ํ›„ ์ƒˆ Refresh ํ† ํฐ ์ €์žฅ
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,44 +5,78 @@
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.core.user.OAuth2User;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

@Getter
public class CustomOAuth2User implements OAuth2User {

private final UserDTO userDTO;
private final Member member; // Member ์ถ”๊ฐ€
private final UserDTO userDTO; // ์†Œ์…œ ๋กœ๊ทธ์ธ ์ •๋ณด ์ €์žฅ
private final Member member; // Member ๊ฐ์ฒด์™€ ์—ฐ๋™

public CustomOAuth2User(UserDTO userDTO, Member member) { // ์ƒ์„ฑ์ž ์ˆ˜์ •
public CustomOAuth2User(UserDTO userDTO, Member member) {
this.userDTO = userDTO;
this.member = member;
}

/**
* OAuth2 ์‚ฌ์šฉ์ž ์†์„ฑ ๋ฐ˜ํ™˜
* - ํ˜„์žฌ๋Š” attributes๊ฐ€ null๋กœ ๋ฐ˜ํ™˜๋˜์–ด ์žˆ์œผ๋ฏ€๋กœ, ํ•„์š”ํ•œ ๊ฒฝ์šฐ ์ด๋ฅผ ์ˆ˜์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
*/
@Override
public Map<String, Object> getAttributes() {
return null;
// userDTO๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์†์„ฑ์„ ๋ฐ˜ํ™˜ํ•˜๊ฑฐ๋‚˜ ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ์ฑ„์šธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
Map<String, Object> attributes = new HashMap<>();
attributes.put("email", userDTO.getEmail());
attributes.put("nickname", userDTO.getNickname());
attributes.put("provider", userDTO.getProvider());
attributes.put("providerId", userDTO.getProviderId());
attributes.put("profileImage", userDTO.getProfileImage());
return attributes;
}

/**
* ์‚ฌ์šฉ์ž ๊ถŒํ•œ ๋ฐ˜ํ™˜
* - ํ˜„์žฌ๋Š” "ROLE_USER" ๊ถŒํ•œ๋งŒ ๋ถ€์—ฌํ•˜๋„๋ก ์„ค์ •
*/
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> collection = new ArrayList<>();
collection.add(() -> "ROLE_USER"); // ๋žŒ๋‹ค์‹์œผ๋กœ ๋‹จ์ˆœํ™”
return collection;
return Collections.singleton(() -> "ROLE_USER"); // ๋‹จ์ผ ๊ถŒํ•œ ROLE_USER
}

/**
* OAuth2 ์‚ฌ์šฉ์ž์˜ ์ด๋ฆ„ ๋ฐ˜ํ™˜
* - ๋‹‰๋„ค์ž„์ด ์กด์žฌํ•˜๋ฉด ๋‹‰๋„ค์ž„ ๋ฐ˜ํ™˜, ์—†์œผ๋ฉด "Unknown User" ๋ฐ˜ํ™˜
*/
@Override
public String getName() {
return userDTO.getNickname() != null ? userDTO.getNickname() : "Unknown User";
}

/**
* OAuth2 ์‚ฌ์šฉ์ž์˜ ๊ณ ์œ  username ์ƒ์„ฑ
* - "provider_providerId" ํ˜•์‹์œผ๋กœ ๋ฐ˜ํ™˜
*/
public String getUsername() {
return userDTO.getProvider() + "_" + userDTO.getProviderId();
}

// Member ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ๋ฉ”์†Œ๋“œ ์ถ”๊ฐ€
/**
* Member ์—”ํ‹ฐํ‹ฐ ๋ฐ˜ํ™˜
* - ์†Œ์…œ ๋กœ๊ทธ์ธ ์ดํ›„ Member์™€ ์—ฐ๋™๋œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ˜ํ™˜
*/
public Member getMember() {
return member;
}
}
public String getProvider() {
return userDTO.getProvider();
}

public String getProviderId() {
return userDTO.getProviderId();
}


}
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package com.mycom.backenddaengplace.auth.handler;

import com.mycom.backenddaengplace.auth.dto.CustomOAuth2User;
import com.mycom.backenddaengplace.auth.domain.RefreshEntity;
import com.mycom.backenddaengplace.auth.dto.CustomOAuth2User;
import com.mycom.backenddaengplace.auth.jwt.JWTUtil;
import com.mycom.backenddaengplace.auth.repository.RefreshRepository;
import com.mycom.backenddaengplace.member.domain.Member;
import com.mycom.backenddaengplace.member.repository.MemberRepository;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
Expand All @@ -28,60 +30,74 @@
@Slf4j
@Component
@RequiredArgsConstructor
// public class CustomSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
public class CustomSuccessHandler implements AuthenticationSuccessHandler {

private final JWTUtil jwtUtil;
private final RefreshRepository refreshRepository;
private final RequestCache requestCache = new HttpSessionRequestCache(); // RequestCache ์ถ”๊ฐ€
private final MemberRepository memberRepository; // DB ์กฐํšŒ๋ฅผ ์œ„ํ•œ MemberRepository ์ถ”๊ฐ€

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

// OAuth2User ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ด
// **OAuth2User ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ด**
CustomOAuth2User customUserDetails = (CustomOAuth2User) authentication.getPrincipal();
String username = customUserDetails.getUsername();
String provider = customUserDetails.getProvider(); // ์†Œ์…œ ๋กœ๊ทธ์ธ ์ œ๊ณต์ž (e.g., google, kakao)
String providerId = customUserDetails.getProviderId(); // ์ œ๊ณต์ž์˜ ๊ณ ์œ  ID

Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
String role = authorities.iterator().next().getAuthority();

// Access์™€ Refresh ํ† ํฐ ์ƒ์„ฑ
String accessToken = jwtUtil.createJwt("access", username, role, 600000L); // 10๋ถ„
String refreshToken = jwtUtil.createJwt("refresh", username, role, 2592000000L); // 30์ผ

// Refresh ํ† ํฐ ์ €์žฅ
addRefreshEntity(username, refreshToken, 2592000000L);
log.info("accessToken: {}", accessToken);
log.info("refreshToken: {}", refreshToken);

// ์‘๋‹ต ์„ค์ •: Access๋Š” ํ—ค๋”์—, Refresh๋Š” ์ฟ ํ‚ค์— ์ €์žฅ
// FIXME: ๋ฌธ์ž์—ด ํ•˜๋“œ์ฝ”๋”ฉ๋ณด๋‹ค๋Š” ์ƒ์ˆ˜๋กœ ๊ด€๋ฆฌํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Œ
// response.setHeader("Authorization", accessToken);
response.setHeader(HttpHeaders.AUTHORIZATION, accessToken);
response.addCookie(createCookie("refresh", refreshToken));
response.setStatus(HttpStatus.OK.value());

/*
* TODO:
* ์˜ˆ๋ฅผ ๋“ค์–ด ์ธ๋ฑ์Šค ํŽ˜์ด์ง€ ์ ‘๊ทผ (๋ฏธ์ธ์ฆ ์ƒํƒœ) -> ๋กœ๊ทธ์ธ ์‹œ๋„ -> ๋กœ๊ทธ์ธ ์„ฑ๊ณต -> ์ธ๋ฑ์Šค ํŽ˜์ด์ง€๋กœ ์ด๋™
* ์‡ผํ•‘๋ชฐ์—์„œ ๋งˆ์ดํŽ˜์ด์ง€ URL ์„ ์•Œ๊ณ  ์žˆ์„ ๋•Œ ๋ฐ”๋กœ ์ ‘๊ทผ (๋ฏธ์ธ์ฆ ์ƒํƒœ) -> ์ธ์ฆ ์„ฑ๊ณต -> ์š”์ฒญํ–ˆ๋˜ ๋งˆ์ดํŽ˜์ด์ง€ URL ๋กœ ์ด๋™
*/
RequestCache requestCache = new HttpSessionRequestCache();
String redirectUrl = Optional.ofNullable(requestCache.getRequest(request, response))
.map(SavedRequest::getRedirectUrl)
.orElse("/");

/*
* FIXME #1: access Token ์„ ๋กœ๊ทธ์ธ ์ธ์ฆ ์„ฑ๊ณตํ•˜๋ฉด FE ์— ์ „๋‹ฌํ•˜๋Š” ๋ฐฉ๋ฒ• -> ์ดํ›„ ์š”์ฒญ ์‹œ ํ—ค๋”์— access Token ์„ ๋„ฃ์–ด์„œ ์š”์ฒญ
* Next.js FE -> /places/1 API ๋ฅผ ํ˜ธ์ถœํ•  ๋•Œ, Authorization ํ—ค๋”์— ์ธ์ฆ ํ† ํฐ์„ ์„ธํŒ…ํ•ด์„œ ์š”์ฒญํ•ด์ฃผ์„ธ์š”.
* FE -> ์ธ์ฆ์ด ํ•„์š”ํ•œ API -> ํ—ค๋”์— ์ธ์ฆ ์ •๋ณด๋ฅผ ์„ธํŒ…ํ•ด์•ผ ํ•œ๋‹ค.
*
* FIXME #2: ํ”„๋ก ํŠธ๊ฐ€ /places/1 API, ์ฟ ํ‚ค์— ์ €์žฅ๋˜์–ด ์žˆ๋Š” refresh Token ์„ API ์ „๋‹ฌ
* ํ—ค๋”์— refreshToken ์„ ๊บผ๋‚ด์„œ ๋งŒ๋ฃŒ ์•ˆ๋๋‹ค -> ๋งŒ๋ฃŒ ๊ธฐ๊ฐ„ ๊ฐฑ์‹ ๋„ ์ƒˆ๋กœ ํ•ด์ฃผ๊ณ , ์ƒˆ๋กœ์šด access Token ๋ฐœ๊ธ‰ํ•ด์ค„ ์ˆ˜ ์žˆ์Œ (์ •ํ•˜๊ธฐ ๋‚˜๋ฆ„)
* API ์—์„œ๋Š” refresh ํ† ํฐ์„ DB ๋กœ ์กฐํšŒํ•ด์„œ ๋งŒ๋ฃŒ ์—ฌ๋ถ€ ํ™•์ธ
*/

response.sendRedirect(redirectUrl);
// **DB์—์„œ ํšŒ์› ์—ฌ๋ถ€ ํ™•์ธ**
Optional<Member> member = memberRepository.findByProviderAndProviderId(provider, providerId);

if (member.isPresent()) {
// **ํšŒ์›์ด ์กด์žฌํ•˜๋Š” ๊ฒฝ์šฐ: JWT ์ƒ์„ฑ ๋ฐ ์›๋ž˜ ์š”์ฒญ์œผ๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ**
log.info("Existing member found: {}", member.get().getEmail());

// Access์™€ Refresh ํ† ํฐ ์ƒ์„ฑ
String accessToken = jwtUtil.createJwt("access", username, role, 60000000L); // 1000๋ถ„
String refreshToken = jwtUtil.createJwt("refresh", username, role, 2592000000L); // 30์ผ

// Refresh ํ† ํฐ ์ €์žฅ
addRefreshEntity(username, refreshToken, 2592000000L);
log.info("accessToken: {}", accessToken);
log.info("refreshToken: {}", refreshToken);

// ์‘๋‹ต ์„ค์ •: Access๋Š” ํ—ค๋”์—, Refresh๋Š” ์ฟ ํ‚ค์— ์ €์žฅ
response.setHeader(HttpHeaders.AUTHORIZATION, accessToken);
response.addCookie(createCookie("refresh", refreshToken));
response.setStatus(HttpStatus.OK.value());
/*
* TODO:
* ์˜ˆ๋ฅผ ๋“ค์–ด ์ธ๋ฑ์Šค ํŽ˜์ด์ง€ ์ ‘๊ทผ (๋ฏธ์ธ์ฆ ์ƒํƒœ) -> ๋กœ๊ทธ์ธ ์‹œ๋„ -> ๋กœ๊ทธ์ธ ์„ฑ๊ณต -> ์ธ๋ฑ์Šค ํŽ˜์ด์ง€๋กœ ์ด๋™
* ์‡ผํ•‘๋ชฐ์—์„œ ๋งˆ์ดํŽ˜์ด์ง€ URL ์„ ์•Œ๊ณ  ์žˆ์„ ๋•Œ ๋ฐ”๋กœ ์ ‘๊ทผ (๋ฏธ์ธ์ฆ ์ƒํƒœ) -> ์ธ์ฆ ์„ฑ๊ณต -> ์š”์ฒญํ–ˆ๋˜ ๋งˆ์ดํŽ˜์ด์ง€ URL ๋กœ ์ด๋™
*/
// **RequestCache์—์„œ ๋ฆฌ๋‹ค์ด๋ ‰์…˜ URL ๊ฐ€์ ธ์˜ค๊ธฐ**
SavedRequest savedRequest = requestCache.getRequest(request, response);
String redirectUrl = "/home"; // ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ /home ์„ค์ •
if (savedRequest != null) {
redirectUrl = savedRequest.getRedirectUrl();
}

log.info("Login successful. Redirecting to: {}", redirectUrl);
response.sendRedirect(redirectUrl); // ์›๋ž˜ ์š”์ฒญ์œผ๋กœ ๋ฆฌ๋‹ค์ด๋ ‰์…˜
} else {
// **ํšŒ์›์ด ์กด์žฌํ•˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ: /members๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ**
log.info("No member found. Redirecting to /members for registration.");
response.sendRedirect("/members");
}
}
/*
* FIXME #1: access Token ์„ ๋กœ๊ทธ์ธ ์ธ์ฆ ์„ฑ๊ณตํ•˜๋ฉด FE ์— ์ „๋‹ฌํ•˜๋Š” ๋ฐฉ๋ฒ• -> ์ดํ›„ ์š”์ฒญ ์‹œ ํ—ค๋”์— access Token ์„ ๋„ฃ์–ด์„œ ์š”์ฒญ
* Next.js FE -> /places/1 API ๋ฅผ ํ˜ธ์ถœํ•  ๋•Œ, Authorization ํ—ค๋”์— ์ธ์ฆ ํ† ํฐ์„ ์„ธํŒ…ํ•ด์„œ ์š”์ฒญํ•ด์ฃผ์„ธ์š”.
* FE -> ์ธ์ฆ์ด ํ•„์š”ํ•œ API -> ํ—ค๋”์— ์ธ์ฆ ์ •๋ณด๋ฅผ ์„ธํŒ…ํ•ด์•ผ ํ•œ๋‹ค.
*
* FIXME #2: ํ”„๋ก ํŠธ๊ฐ€ /places/1 API, ์ฟ ํ‚ค์— ์ €์žฅ๋˜์–ด ์žˆ๋Š” refresh Token ์„ API ์ „๋‹ฌ
* ํ—ค๋”์— refreshToken ์„ ๊บผ๋‚ด์„œ ๋งŒ๋ฃŒ ์•ˆ๋๋‹ค -> ๋งŒ๋ฃŒ ๊ธฐ๊ฐ„ ๊ฐฑ์‹ ๋„ ์ƒˆ๋กœ ํ•ด์ฃผ๊ณ , ์ƒˆ๋กœ์šด access Token ๋ฐœ๊ธ‰ํ•ด์ค„ ์ˆ˜ ์žˆ์Œ (์ •ํ•˜๊ธฐ ๋‚˜๋ฆ„)
* API ์—์„œ๋Š” refresh ํ† ํฐ์„ DB ๋กœ ์กฐํšŒํ•ด์„œ ๋งŒ๋ฃŒ ์—ฌ๋ถ€ ํ™•์ธ
*/

private void addRefreshEntity(String username, String refreshToken, Long expiredMs) {
// Access Token, Refresh Token
Expand All @@ -97,9 +113,6 @@ private Cookie createCookie(String key, String value) {
cookie.setMaxAge(2592000); // 30์ผ
cookie.setHttpOnly(true);
cookie.setPath("/");

return cookie;
}


}
Loading
Loading