깃 init 부터~
이젠 자연스러워진 세팅~
ackage com.example.foodduck.common.config.jwt.filter;
import com.example.foodduck.common.config.jwt.JwtUtil;
import io.jsonwebtoken.Claims;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtUtil jwtUtil;
private final UserDetailsService userDetailsService;
public JwtAuthenticationFilter(JwtUtil jwtUtil, UserDetailsService userDetailsService) {
this.jwtUtil = jwtUtil;
this.userDetailsService = userDetailsService;
}
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)
throws ServletException, IOException {
String jwt = jwtUtil.parseJwt(request);
if (jwt != null && jwtUtil.validateJwtToken(jwt)) {
Claims claims = jwtUtil.extractClaims(jwt);
String email = claims.getSubject();
UserDetails userDetails = userDetailsService.loadUserByUsername(email);
if (SecurityContextHolder.getContext().getAuthentication() == null) {
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
filterChain.doFilter(request, response);
}
}
package com.example.foodduck.common.config.jwt;
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.nio.charset.StandardCharsets;
import java.security.Key;
import java.util.Date;
@Component
public class JwtUtil {
private final Key key;
private final long expirationMs;
public JwtUtil(@Value("${jwt.secret}") String jwtSecret,
@Value("${jwt.expirationMs}") long expirationMs) {
this.key = Keys.hmacShaKeyFor(jwtSecret.getBytes(StandardCharsets.UTF_8));
this.expirationMs = expirationMs;
}
// jwt 생성
public String generateJwtToken(String email, Long userId) {
return Jwts.builder()
.setSubject(email)
.claim("userId", userId)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + expirationMs))
.signWith(key, SignatureAlgorithm.HS512)
.compact();
}
// jwt에서 클레임 추출 - 참고 https://velog.io/@oring7070/Spring-Security-jwt-claim
public Claims extractClaims(String token) {
return Jwts.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(token)
.getBody();
}
// jwt에서 이메일 추출
public String getEmailFromJwtToken(String token) {
return extractClaims(token).getSubject();
}
// jwt에서 사용자 id 추출
public Long getUserIdFromJwtToken(String token) {
return extractClaims(token).get("userId", Long.class);
}
// jwt 유효성 검사
public boolean validateJwtToken(String token) {
try {
extractClaims(token); //유효성 검증
return true;
} catch (SecurityException | MalformedJwtException e) {
System.err.println("잘못된 JWT: " + e.getMessage());
} catch (ExpiredJwtException e) {
System.err.println("만료된 JWT: " + e.getMessage());
} catch (UnsupportedJwtException e) {
System.err.println("지원되지 않는 JWT: " + e.getMessage());
} catch (IllegalArgumentException e) {
System.err.println("JWT가 빈 값입니다: " + e.getMessage());
}
return false;
}
//http request 헤더에서 jwt 추출
public String parseJwt(HttpServletRequest request) {
String headerAuth = request.getHeader("Authorization");
if (headerAuth != null && headerAuth.startsWith("Bearer ")) {
return headerAuth.substring(7);
}
return null;
}
}
package com.example.foodduck.common.config.jwt;
import com.example.foodduck.common.config.jwt.filter.JwtAuthenticationFilter;
import com.example.foodduck.user.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
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;
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {
private final JwtUtil jwtUtil;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http, JwtAuthenticationFilter jwtAuthenticationFilter) throws Exception {
http
.csrf().disable()
.authorizeHttpRequests(auth -> auth
.requestMatchers("/users/register", "/users/login").permitAll()
.requestMatchers("/stores/**").hasAuthority("OWNER")
.anyRequest().authenticated()
)
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public UserDetailsService userDetailsService(UserRepository userRepository) {
return email -> (UserDetails) userRepository.findByEmail(email)
.orElseThrow(() -> new RuntimeException("사용자를 찾을 수 없습니다."));
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
@Bean
public JwtAuthenticationFilter jwtAuthenticationFilter(UserDetailsService userDetailsService) {
return new JwtAuthenticationFilter(jwtUtil, userDetailsService);
}
}
package com.example.foodduck.common.entity;
import jakarta.persistence.*;
import lombok.Getter;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import java.time.LocalDateTime;
@Getter
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseEntity {
@CreatedDate
@Column(updatable = false)
@Temporal(TemporalType.TIMESTAMP)
private LocalDateTime createdAt;
@LastModifiedDate
@Column
@Temporal(TemporalType.TIMESTAMP)
private LocalDateTime updatedAt;
}
package com.example.foodduck.exception;
import lombok.Getter;
import org.springframework.http.HttpStatus;
@Getter
public class ApplicationException extends RuntimeException {
private final HttpStatus status;
public ApplicationException(String message, HttpStatus status) {
super(message);
this.status = status;
}
}
package com.example.foodduck.exception;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<String> handleIllegalArgumentException(IllegalArgumentException ex) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(ex.getMessage());
}
@ExceptionHandler(Exception.class)
public ResponseEntity<String> handleException(Exception ex) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("서버 오류가 발생했습니다.");
}
}
package com.example.foodduck.exception;
import org.springframework.http.HttpStatus;
public class InvalidCredentialException extends ApplicationException {
public InvalidCredentialException(String message) {
super(message, HttpStatus.UNAUTHORIZED);
}
}
package com.example.foodduck.user.controller;
import com.example.foodduck.user.dto.request.UserLoginRequest;
import com.example.foodduck.user.dto.request.UserPasswordUpdateRequest;
import com.example.foodduck.user.dto.request.UserJoinRequest;
import com.example.foodduck.user.dto.response.JwtResponse;
import com.example.foodduck.user.dto.response.UserResponse;
import com.example.foodduck.user.service.UserService;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/users")
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
// 회원가입
@PostMapping("/register")
public ResponseEntity<UserResponse> registerUser(@Valid @RequestBody UserJoinRequest request) {
return ResponseEntity.ok(userService.registerUser(request));
}
// 로그인
@PostMapping("/login")
public ResponseEntity<JwtResponse> loginUser(@Valid @RequestBody UserLoginRequest request) {
return ResponseEntity.ok(userService.loginUser(request));
}
// 회원 탈퇴
@DeleteMapping("/{userId}")
public ResponseEntity<String> deleteUser(@PathVariable Long userId, @RequestParam String password) {
userService.deleteUser(userId, password);
return ResponseEntity.ok("회원 탈퇴가 완료되었습니다.");
}
// 비밀번호 변경
@PatchMapping("/{userId}/password")
public ResponseEntity<String> updatePassword(@PathVariable Long userId, @Valid @RequestBody UserPasswordUpdateRequest request) {
userService.updatePassword(userId, request);
return ResponseEntity.ok("비밀번호가 변경되었습니다.");
}
@PostMapping("/logout")
public ResponseEntity<String> logout() {
return ResponseEntity.ok("로그아웃했습니다.");
}
}
package com.example.foodduck.user.dto.request;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Getter
@NoArgsConstructor
public class UserJoinRequest {
@NotBlank(message="이름은 필수 입력 항목이에요")
private String name;
@Email(message = "유효한 이메일 주소를 입력해주세요.")
private String email;
@Pattern(regexp = "^(?=.*[A-Za-z])(?=.*\\d)(?=.*[@$!%*?&])[A-Za-z\\d@$!%*?&]{8,}$",
message = "비밀번호는 최소 8자 이상이며, 영문+숫자+특수문자를 포함해야 합니다.")
private String password;
private String role;
}
package com.example.foodduck.user.dto.request;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Getter
@NoArgsConstructor
public class UserLoginRequest {
@Email(message="유효한 이메일 주소를 입력해주세요")
private String email;
@NotBlank(message = "비밀번호는 필수로 입력해주세요")
private String password;
}
package com.example.foodduck.user.dto.request;
import jakarta.validation.constraints.NotBlank;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Getter
@NoArgsConstructor
public class UserPasswordUpdateRequest {
@NotBlank(message = "기존 비밀번호를 입력해주셔야합니다.")
private String oldPassword;
@NotBlank(message = "새 비밀번호를 입력해주세요.")
private String newPassword;
}
package com.example.foodduck.user.dto.response;
import lombok.Getter;
@Getter
public class JwtResponse {
private final String token;
private final Long userId;
private final String email;
public JwtResponse(String token, Long userId, String email) {
this.token = token;
this.userId = userId;
this.email = email;
}
}
package com.example.foodduck.user.dto.response;
import com.example.foodduck.user.entity.User;
import lombok.Getter;
@Getter
public class UserResponse {
private final Long id;
private final String name;
private final String email;
private final String role;
public UserResponse(User user) {
this.id = user.getId();
this.name = user.getName();
this.email = user.getEmail();
this.role = user.getRole().name();
}
}
package com.example.foodduck.user.entity;
import com.example.foodduck.common.entity.BaseEntity;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.NoArgsConstructor;
import java.math.BigInteger;
@Getter
@Entity
@NoArgsConstructor
@Table(name = "users")
public class User extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@Column(unique = true, nullable = false)
private String email;
@Column(nullable = false)
private String password;
@Enumerated(EnumType.STRING)
@Column(nullable = false)
private UserRole role;
@Column(nullable = false)
private boolean isDeleted = false;
@Column(nullable = false)
private int storeCount = 0;
public User(String name, String email, String password, UserRole role) {
this.name = name;
this.email = email;
this.password = password;
this.role = role;
}
public void updatePassword(String newPassword) {
this.password = newPassword;
}
public void updateUserName(String newUserName) {
this.name = newUserName;
}
public void deleteUser() {
this.isDeleted = true;
}
public void updateStoreCount(int storeCount) {
this.storeCount = storeCount;
}
}
package com.example.foodduck.user.entity;
public enum UserRole {
USER, OWNER
}
package com.example.foodduck.user.repository;
import com.example.foodduck.user.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import java.math.BigInteger;
import java.util.Optional;
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByEmail(String email);
boolean existsByEmail(String email);
}
package com.example.foodduck.user.service;
import com.example.foodduck.user.entity.User;
import com.example.foodduck.user.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class CustomizingUserDetailsService implements UserDetailsService {
private final UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
User user = userRepository.findByEmail(email)
.orElseThrow(() -> new UsernameNotFoundException("사용자를 찾을 수 없습니다: " + email));
return org.springframework.security.core.userdetails.User
.withUsername(user.getEmail())
.password(user.getPassword())
.roles(user.getRole().name())
.build();
}
}
package com.example.foodduck.user.service;
import com.example.foodduck.common.config.jwt.JwtUtil;
import com.example.foodduck.exception.InvalidCredentialException;
import com.example.foodduck.user.dto.request.UserLoginRequest;
import com.example.foodduck.user.dto.request.UserPasswordUpdateRequest;
import com.example.foodduck.user.dto.request.UserJoinRequest;
import com.example.foodduck.user.dto.response.JwtResponse;
import com.example.foodduck.user.dto.response.UserResponse;
import com.example.foodduck.user.entity.User;
import com.example.foodduck.user.entity.UserRole;
import com.example.foodduck.user.repository.UserRepository;
import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
private final JwtUtil jwtUtil;
// 회원 가입
public UserResponse registerUser(UserJoinRequest request) {
if (userRepository.existsByEmail(request.getEmail())) {
throw new IllegalArgumentException("이미 존재하는 이메일입니다");
}
User user = new User(
request.getName(),
request.getEmail(),
passwordEncoder.encode(request.getPassword()),
request.getRole().equalsIgnoreCase("OWNER") ? UserRole.OWNER : UserRole.USER
);
userRepository.save(user);
return new UserResponse(user);
}
// 로그인
public JwtResponse loginUser(UserLoginRequest request) {
User user = userRepository.findByEmail(request.getEmail())
.orElseThrow(() -> new IllegalArgumentException("이메일을 찾을 수 없습니다."));
if (!passwordEncoder.matches(request.getPassword(), user.getPassword())) {
throw new IllegalArgumentException("비밀번호가 일치하지 않습니다.");
}
String token = jwtUtil.generateJwtToken(user.getEmail(), user.getId());
return new JwtResponse(token, user.getId(), user.getEmail());
}
// soft delete
public void deleteUser(Long userId, String password) {
User user = userRepository.findById(userId)
.orElseThrow(() -> new IllegalArgumentException("사용자를 찾을 수 없습니다."));
if (!passwordEncoder.matches(password, user.getPassword())) {
throw new IllegalArgumentException("비밀번호가 일치하지 않네요");
}
user.deleteUser();
userRepository.save(user);
}
// 비밀번호 변경
public void updatePassword(Long userId, UserPasswordUpdateRequest request) {
User user = userRepository.findById(userId)
.orElseThrow(() -> new IllegalArgumentException("사용자를 찾을 수 없습니다."));
if (!passwordEncoder.matches(request.getOldPassword(), user.getPassword())) {
throw new IllegalArgumentException("기존 비밀번호와 일치하지 않아요.");
}
user.updatePassword(passwordEncoder.encode(request.getNewPassword()));
userRepository.save(user);
}
}
이전에 수행했던 과제인 뉴스피드 과제를 팀플을 통해 구현했던 것을 바탕으로 일부 수정하면서 jwt, jpa, springboot, mysql 을 활용해서 크게 회원가입, 로그인, 로그아웃, 비밀번호 변경, 회원탈퇴를 구현했습니다.
그러나 회원가입 -> 로그인까지는 정상 동작하나 이후부터는 error 발생.
구글링을 해보니, Jwt 블랙리스트인 in-memory cache를 활용하면 로그아웃 시 현재 jwt를 무효화 한다고 합니다.
이에 기존 로그아웃이 아무 동작을 하지 않으니 해당 방식을 통해 개선하려고 노력해봤습니다.
회원 탈퇴 시 db 반영 개선
isDeleted 값을 true로 변경 후 db에 저장했는데, 이 부분을 탈퇴한 회원은 로그인 불가능 하도록 수정하는 것이 필요하다 판단했습니다.
-> UserDetailsService에서 isDeleted가 true인 회원을 로그인 차단하도록 개선
불필요한 클래스 삭제
지금 보면 이전 프로젝트에서 사용하던 것을 일부 가져와서 그런지 불필요한 클래스들이 다수 보입니다. 필요 없는 부분은 과감히 제거해주고, Jwt 는 1번에서 말했던대로 수정 할 것입니다.
package com.example.foodduck.user.service;
import com.example.foodduck.common.config.jwt.JwtUtil;
import com.example.foodduck.exception.InvalidCredentialException;
import com.example.foodduck.user.dto.request.*;
import com.example.foodduck.user.dto.response.*;
import com.example.foodduck.user.entity.User;
import com.example.foodduck.user.entity.UserRole;
import com.example.foodduck.user.repository.UserRepository;
import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
private final JwtUtil jwtUtil;
private static final Set<String> BLACKLISTED_TOKENS = ConcurrentHashMap.newKeySet();
public UserResponse registerUser(UserJoinRequest request) {
if (userRepository.existsByEmail(request.getEmail())) {
throw new IllegalArgumentException("이미 존재하는 이메일입니다");
}
User user = new User(
request.getName(),
request.getEmail(),
passwordEncoder.encode(request.getPassword()),
request.getRole().equalsIgnoreCase("OWNER") ? UserRole.OWNER : UserRole.USER
);
userRepository.save(user);
return new UserResponse(user);
}
public JwtResponse loginUser(UserLoginRequest request) {
User user = userRepository.findByEmail(request.getEmail())
.orElseThrow(() -> new IllegalArgumentException("이메일을 찾을 수 없습니다."));
if (user.isDeleted()) {
throw new InvalidCredentialException("탈퇴한 계정입니다. 다시 가입해주세요.");
}
if (!passwordEncoder.matches(request.getPassword(), user.getPassword())) {
throw new IllegalArgumentException("비밀번호가 일치하지 않습니다.");
}
String token = jwtUtil.generateJwtToken(user.getEmail(), user.getId());
return new JwtResponse(token, user.getId(), user.getEmail());
}
public void logout(String token) {
if (token != null) {
BLACKLISTED_TOKENS.add(token);
}
}
public boolean isTokenBlacklisted(String token) {
return token != null && BLACKLISTED_TOKENS.contains(token);
}
@Transactional
public void deleteUser(Long userId, String password) {
User user = userRepository.findById(userId)
.orElseThrow(() -> new IllegalArgumentException("사용자를 찾을 수 없습니다."));
if (!passwordEncoder.matches(password, user.getPassword())) {
throw new IllegalArgumentException("비밀번호가 일치하지 않습니다.");
}
user.deleteUser();
userRepository.save(user);
}
@Transactional
public void updatePassword(Long userId, UserPasswordUpdateRequest request) {
User user = userRepository.findById(userId)
.orElseThrow(() -> new IllegalArgumentException("사용자를 찾을 수 없습니다."));
if (!passwordEncoder.matches(request.getOldPassword(), user.getPassword())) {
throw new IllegalArgumentException("기존 비밀번호가 일치하지 않습니다.");
}
user.updatePassword(passwordEncoder.encode(request.getNewPassword()));
userRepository.save(user);
}
}
package com.example.foodduck.common.config.jwt.filter;
import com.example.foodduck.common.config.jwt.JwtUtil;
import com.example.foodduck.user.service.UserService;
import io.jsonwebtoken.Claims;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtUtil jwtUtil;
private final UserDetailsService userDetailsService;
private final UserService userService; // 블랙리스트 확인용
public JwtAuthenticationFilter(JwtUtil jwtUtil, UserDetailsService userDetailsService, UserService userService) {
this.jwtUtil = jwtUtil;
this.userDetailsService = userDetailsService;
this.userService = userService;
}
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)
throws ServletException, IOException {
String jwt = jwtUtil.parseJwt(request);
if (jwt != null && jwtUtil.validateJwtToken(jwt)) {
//블랙리스트 검사 추가
if (userService.isTokenBlacklisted(jwt)) {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "로그아웃된 토큰입니다.");
return;
}
Claims claims = jwtUtil.extractClaims(jwt);
String email = claims.getSubject();
UserDetails userDetails = userDetailsService.loadUserByUsername(email);
if (SecurityContextHolder.getContext().getAuthentication() == null) {
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
filterChain.doFilter(request, response);
}
}
userService.isTokenBlacklisted(jwt)를 호출하여 로그아웃한 사용자가 다시 로그인하지 못하도록 차단.
블랙리스트에 포함된 토큰은 즉시 401 에러 반환
package com.example.foodduck.common.config.jwt;
import com.example.foodduck.common.config.jwt.filter.JwtAuthenticationFilter;
import com.example.foodduck.user.service.UserService;
import com.example.foodduck.user.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
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;
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {
private final JwtUtil jwtUtil;
private final UserService userService; // 추가
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http, JwtAuthenticationFilter jwtAuthenticationFilter) throws Exception {
http
.csrf().disable()
.authorizeHttpRequests(auth -> auth
.requestMatchers("/users/register", "/users/login").permitAll()
.requestMatchers("/users/logout").authenticated() // 로그아웃은 인증 필요
.requestMatchers("/stores/**").hasAuthority("OWNER")
.anyRequest().authenticated()
)
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public UserDetailsService userDetailsService(UserRepository userRepository) {
return email -> userRepository.findByEmail(email)
.orElseThrow(() -> new RuntimeException("사용자를 찾을 수 없습니다."));
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
@Bean
public JwtAuthenticationFilter jwtAuthenticationFilter(UserDetailsService userDetailsService) {
return new JwtAuthenticationFilter(jwtUtil, userDetailsService, userService); // 수정
}
}
// 로그아웃 (현재 사용자의 JWT를 블랙리스트에 추가)
@PostMapping("/logout")
public ResponseEntity<String> logout(HttpServletRequest request) {
String token = request.getHeader("Authorization").substring(7);
userService.logout(token);
return ResponseEntity.ok("로그아웃되었습니다.");
}
logout(HttpServletRequest request)에서 헤더에서 JWT를 추출하여 블랙리스트에 추가함으로써 이제 로그아웃하면 해당 JWT는 더 이상 사용할 수 없게 되었습니다.
다만 여기서 로그아웃에 인증이 추가된 것을 확인할 수 있습니다.
.requestMatchers("/users/logout").authenticated()
-> 로그아웃에 인증이 필요한 이유
로그아웃을 할 때 현재 로그인된 사용자의 토큰을 블랙리스트에 추가해야 하기 때문에, 반드시 현재 사용자의 JWT를 가져와야하기 때문입니다.
만약, 인증없이 진행하게 된다면?
jwt를 response로 받으면 "Bearer "가 포함되어있는 것을 알 수 있습니다.
이를 개선하기 위해 저는
@PostMapping("/logout")
public ResponseEntity<String> logout(HttpServletRequest request) {
String header = request.getHeader("Authorization");
if (header == null || !header.startsWith("Bearer ")) {
return ResponseEntity.badRequest().body("유효한 토큰이 필요합니다.");
}
String token = header.substring(7); // "Bearer " 부분 제거
userService.logout(token);
return ResponseEntity.ok("로그아웃되었습니다.");
}
근데 이렇게 블랙리스트 토큰으로 코드를 수정하니,
┌─────┐
| securityConfig defined in file [/Users/mun/Desktop/2025/foodduck/build/classes/java/main/com/example/foodduck/common/config/jwt/SecurityConfig.class]
↑ ↓
| userService defined in file [/Users/mun/Desktop/2025/foodduck/build/classes/java/main/com/example/foodduck/user/service/UserService.class]
└─────┘
문제의 원인은 SecurityConfig에서 UserService 의존성 주입 문제
현재 SecurityConfig에서 UserService를 직접 주입받고 있는데, SecurityConfig가 UserService보다 먼저 생성되기 때문에 순환 참조 문제가 발생하는 것 같다고 판단하여, 일단 순환 참조가 무엇인지 확실하게 공부.
참고 1, 2, 3, 4, 5, 6, 7 -> 인프런 질문(제일 도움된거), 8
쉽게 말해,
해결 방법은 ?
package com.example.foodduck.common.config.jwt;
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.nio.charset.StandardCharsets;
import java.security.Key;
import java.util.Date;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
@Component
public class JwtUtil {
private final Key key;
private final long expirationMs;
// 블랙리스트: ConcurrentHashMap을 사용하여 동시성 문제 방지
private static final Set<String> BLACKLISTED_TOKENS = ConcurrentHashMap.newKeySet();
public JwtUtil(@Value("${jwt.secret}") String jwtSecret,
@Value("${jwt.expirationMs}") long expirationMs) {
this.key = Keys.hmacShaKeyFor(jwtSecret.getBytes(StandardCharsets.UTF_8));
this.expirationMs = expirationMs;
}
// JWT 생성
public String generateJwtToken(String email, Long userId) {
return Jwts.builder()
.setSubject(email)
.claim("userId", userId)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + expirationMs))
.signWith(key, SignatureAlgorithm.HS512)
.compact();
}
// JWT에서 클레임 추출
public Claims extractClaims(String token) {
return Jwts.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(token)
.getBody();
}
// JWT에서 이메일 추출
public String getEmailFromJwtToken(String token) {
return extractClaims(token).getSubject();
}
// JWT에서 사용자 ID 추출
public Long getUserIdFromJwtToken(String token) {
return extractClaims(token).get("userId", Long.class);
}
// JWT 유효성 검사
public boolean validateJwtToken(String token) {
try {
extractClaims(token); // 유효성 검증
return !BLACKLISTED_TOKENS.contains(token); // 블랙리스트 검증 추가
} catch (JwtException | IllegalArgumentException e) {
return false;
}
}
// JWT 블랙리스트에 추가 (로그아웃 시 호출)
public void blacklistToken(String token) {
BLACKLISTED_TOKENS.add(token);
}
}
한 번 써보고 싶어서 썻던 블랙리스트 토큰 방식을,,, 이해하기엔 아직 어렵네요.
UserService엔 블랙리스트 로직 제거 해야합니다.
또한, UserService에서 BLACKLISTED_TOKENS를 관리하지 않고, jwtUtil.blacklistToken(token)을 호출하도록 변경하면 됩니다.
wtAuthenticationFilter에서 UserService 직접 참조하지 않도록 수정
정리 하면,
현재 전 main 브랜치에 있고, 내가 작업하는 공간은 feat/user 브랜치에서 했었습니다. 나머지 팀원은 feat/store랑 feat/menu랑 feat/order 등에서 작업했었습니다. 그래서 저는 팀원 브랜치에 이동해서 Pull한 다음에 코드 확인하고 테스트하고 싶은데 왜 안뜨지?? pull 하고 push 하면 뜨는거 아닌가? 라는 당연한 생각을 했었습니다.
그러나, 역시 ㅋㅋ
에러를 보면 현재 feat/store 브랜치로 이동하려고 했지만, error: pathspec 'feat/store' did not match any file(s) known to git 오류가 발생한 이유는 로컬에 해당 브랜치가 존재하지 않기 때문입니다.
git branch -r
로 원격 저장소에 feat/store 브랜치가 존재하는지 확인git fetch --all
로 최신 정보 확인하고, 브랜치 정보 확인 -> 브랜치 이동 하면 정상적으로 동작함을 확인할 수 있습니다 ㅎㅎ김선용 멘토님께 질의해서 새로운 실무 경험을 바탕으로한 꿀 팁? 을 얻었다 ㅎ
postman에 나만 테스트가 안되고 있었고, 팀원들은 다 정상 동작하고있었다.
그럼 왜 안되는 것일까?
database drop하고, create하고 재구동해서 테스트하면 정상적으로 동작된다.
jpa만 쓰다보면 db가 가끔씩 꼬일 수 있기 때문이다.
또한, url을 복붙하게되면, 가끔씩 안되는 경우가 있따.
그래서 번거롭겠지만 직접 타이핑 해야한다.
이제 추가 기능인 소셜 로그인을 하려고 합니다.
그 전에 도움이 될만한 유튜브 영상 링크를 남깁니다 -> (1,2,3)
연동은 크게 3단계로 나눌 수 있습니다.
사실 전 몸이 아프기 전에 소셜 로그인 구현까지 개발한 쇼핑몰 프로젝트가 있었습니다. 그러나, 몸이 안좋아 7~8개월 공부를 못하게 되어 쉬고 왔더니 일부만 기억이 나게 되어 다시 공부하여 이번에 적용해보려고 합니다.
'범위 추가 또는 삭제' 버튼 클릭 후 최상단 3개 항목 선택 후 저장
Google 로그인 테스트를 위한 계정 추가 (누구나 사용하기 위해선 별도 애플리케이션 심사 필요)
앱 개시
사용자 인증 정보 탭에서 OAuth 클라이언트 ID 생성
properties에 추가.
https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api
카카오 같은 경우 공식문서에 정리가 잘 되어 있기에, 공식문서 보고 따라하면 됩니다.
그래도 혹시 모르니 저는 공유하겠습니다~
설명이 정말 잘되어있어서 그대로 보고 따라하면 됩니다.
끝. 이제 환경변수로 값 넣어주면 됩니다.
/Users/mun/Library/Java/JavaVirtualMachines/corretto-17.0.14/Contents/Home/bin/java -XX:TieredStopAtLevel=1 -noverify -Dspring.output.ansi.enabled=always -Dcom.sun.management.jmxremote -Dspring.jmx.enabled=true -Dspring.liveBeansView.mbeanDomain -Dspring.application.admin.enabled=true -javaagent:/Applications/IntelliJ IDEA.app/Contents/lib/idea_rt.jar=54593:/Applications/IntelliJ IDEA.app/Contents/bin -Dfile.encoding=UTF-8 -classpath /Users/mun/Desktop/2025/foodduck/build/classes/java/main:/Users/mun/Desktop/2025/foodduck/build/resources/main:/Users/mun/.gradle/caches/modules-2/files-2.1/org.projectlombok/lombok/1.18.36/5a30490a6e14977d97d9c73c924c1f1b5311ea95/lombok-1.18.36.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.springframework.boot/spring-boot-starter-validation/3.4.3/cbbea0ff19a55bc84926a04be99045c7eba2420/spring-boot-starter-validation-3.4.3.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.springframework.boot/spring-boot-starter-web/3.4.3/87e7a401e7d249fefc7f372f5681e8620234624c/spring-boot-starter-web-3.4.3.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.springframework.boot/spring-boot-starter-aop/3.4.3/c45b10557705141ed80953f7bb0736540158c024/spring-boot-starter-aop-3.4.3.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.springframework.boot/spring-boot-starter-data-jpa/3.4.3/29c90fec6e887dc01df09ea4c3548b5a04b7afb4/spring-boot-starter-data-jpa-3.4.3.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/at.favre.lib/bcrypt/0.10.2/430be75a265cb3b5998807f88f1c40fc750bc63c/bcrypt-0.10.2.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/io.jsonwebtoken/jjwt-api/0.11.5/f742940045619d06383e7df37b21ac422b476cf1/jjwt-api-0.11.5.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.springframework.boot/spring-boot-starter-security/3.4.3/b474a2f9fdd7e46cfb662b13bb38d9da9e7682d0/spring-boot-starter-security-3.4.3.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.springframework.boot/spring-boot-starter-oauth2-client/3.4.3/235db1e72915a62aae0459c930082e5a649f802b/spring-boot-starter-oauth2-client-3.4.3.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.springframework.boot/spring-boot-starter/3.4.3/247fdc05cd6de013c3fd26628fa221dd095b391/spring-boot-starter-3.4.3.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.apache.tomcat.embed/tomcat-embed-el/10.1.36/4e4235a18244af30bb32d9b5a9400fb8edb3bc20/tomcat-embed-el-10.1.36.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.hibernate.validator/hibernate-validator/8.0.2.Final/220e64815dd87535525331de20570017f899eb13/hibernate-validator-8.0.2.Final.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.springframework.boot/spring-boot-starter-json/3.4.3/7a4d63165404da67ed3802678994c21a0763723/spring-boot-starter-json-3.4.3.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.springframework.boot/spring-boot-starter-tomcat/3.4.3/40246b02bf8bf905dd5fb3d57f48bfc2c9b49bb9/spring-boot-starter-tomcat-3.4.3.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.springframework/spring-webmvc/6.2.3/485f6e351bba471fc8f841f39eaf6488896369ff/spring-webmvc-6.2.3.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.springframework/spring-web/6.2.3/662ac5ee41af27d183f97032b2fec2b652d379f5/spring-web-6.2.3.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.springframework/spring-aop/6.2.3/7983418fa64505d144b60826fb1352ad336c60a0/spring-aop-6.2.3.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.aspectj/aspectjweaver/1.9.22.1/bca243d0af0db4758fbae45c5f4995cb5dabb612/aspectjweaver-1.9.22.1.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.springframework.boot/spring-boot-starter-jdbc/3.4.3/87c09bd67ea27fcd30d6413fdbc5bfa1f688e5e8/spring-boot-starter-jdbc-3.4.3.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.springframework.data/spring-data-jpa/3.4.3/7e4c0e9c14f4d971a9de6aff3c9beece2001480b/spring-data-jpa-3.4.3.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.springframework/spring-aspects/6.2.3/f90a794dd3f81a1addc0552bc091e3f85b2428de/spring-aspects-6.2.3.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.hibernate.orm/hibernate-core/6.6.8.Final/2738c642d5e505249faa2f923e03541aa6b96916/hibernate-core-6.6.8.Final.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/at.favre.lib/bytes/1.5.0/9617977854566948d767e6da8ec343b1fa107c48/bytes-1.5.0.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.springframework.security/spring-security-web/6.4.3/7e0887e8fccda083d8bb4abb58997909480ea4a0/spring-security-web-6.4.3.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.springframework.security/spring-security-config/6.4.3/52d7cedfde4f9a4e78f27b754e8be89ec25e871/spring-security-config-6.4.3.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.springframework.security/spring-security-oauth2-client/6.4.3/2e380b082a36c60744b01d5b8f4b38d13fedb6d5/spring-security-oauth2-client-6.4.3.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.springframework.security/spring-security-oauth2-jose/6.4.3/4c6835a77e8ba43ade6e22a6f9ec19e391a28be2/spring-security-oauth2-jose-6.4.3.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.springframework.security/spring-security-core/6.4.3/3023090cb32ecfe266726edc498ae8cb112f4e7f/spring-security-core-6.4.3.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.springframework.boot/spring-boot-autoconfigure/3.4.3/6172c599082196b340910d67c5c790c32f10e417/spring-boot-autoconfigure-3.4.3.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.springframework.boot/spring-boot/3.4.3/a7138bcecd59ed27660b3894a7812d65db4951e6/spring-boot-3.4.3.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.springframework.boot/spring-boot-starter-logging/3.4.3/e5aee6af32c2dbcc9fb379bcd6c5e5b931db93f1/spring-boot-starter-logging-3.4.3.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/jakarta.annotation/jakarta.annotation-api/2.1.1/48b9bda22b091b1f48b13af03fe36db3be6e1ae3/jakarta.annotation-api-2.1.1.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.springframework/spring-core/6.2.3/13ec11e345b915d7ceea37446f1b1eefdcaad62c/spring-core-6.2.3.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.yaml/snakeyaml/2.3/936b36210e27320f920536f695cf1af210c44586/snakeyaml-2.3.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/jakarta.validation/jakarta.validation-api/3.0.2/92b6631659ba35ca09e44874d3eb936edfeee532/jakarta.validation-api-3.0.2.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.jboss.logging/jboss-logging/3.6.1.Final/886afbb445b4016a37c8960a7aef6ebd769ce7e5/jboss-logging-3.6.1.Final.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/com.fasterxml/classmate/1.7.0/e98374da1f2143ac8e6e0a95036994bb19137a3/classmate-1.7.0.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.datatype/jackson-datatype-jsr310/2.18.2/7b6ff96adf421f4c6edbd694e797dd8fe434510a/jackson-datatype-jsr310-2.18.2.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.module/jackson-module-parameter-names/2.18.2/72960cb3277347a748911d100c3302d60e8a616a/jackson-module-parameter-names-2.18.2.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.datatype/jackson-datatype-jdk8/2.18.2/9ed6d538ebcc66864e114a7040953dce6ab6ea53/jackson-datatype-jdk8-2.18.2.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.core/jackson-databind/2.18.2/deef8697b92141fb6caf7aa86966cff4eec9b04f/jackson-databind-2.18.2.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.apache.tomcat.embed/tomcat-embed-websocket/10.1.36/222960bc8895aa543e405382094c7fb118544d0d/tomcat-embed-websocket-10.1.36.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.apache.tomcat.embed/tomcat-embed-core/10.1.36/3ccaa558e32e317e35d6674d65d7d9aa182e082f/tomcat-embed-core-10.1.36.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.springframework/spring-context/6.2.3/35acadc000b8aaff77e3412dd9acb51aa70c8515/spring-context-6.2.3.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.springframework/spring-beans/6.2.3/de660324c90e9b015886d85c746860478fa7d99c/spring-beans-6.2.3.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.springframework/spring-expression/6.2.3/bda43c7be1bb69cce2a380c2bbaf05062631d42d/spring-expression-6.2.3.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/io.micrometer/micrometer-observation/1.14.4/c17efe9e6695a0a849a95d0e77422516a345e779/micrometer-observation-1.14.4.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.springframework/spring-jdbc/6.2.3/37a5f7fd424fe555a556138c8ac4f8d6dd312c19/spring-jdbc-6.2.3.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/com.zaxxer/HikariCP/5.1.0/8c96e36c14461fc436bb02b264b96ef3ca5dca8c/HikariCP-5.1.0.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.springframework/spring-orm/6.2.3/b99b9917702591c2ef0cfa7a9bff6bf757015437/spring-orm-6.2.3.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.springframework.data/spring-data-commons/3.4.3/2b61ff4aef4a81071ce7eaf1ec29ef9e553bf930/spring-data-commons-3.4.3.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.springframework/spring-tx/6.2.3/29c8703a756f9a41bc8e14e2aae7890658c54abf/spring-tx-6.2.3.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.antlr/antlr4-runtime/4.13.0/5a02e48521624faaf5ff4d99afc88b01686af655/antlr4-runtime-4.13.0.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.slf4j/slf4j-api/2.0.16/172931663a09a1fa515567af5fbef00897d3c04/slf4j-api-2.0.16.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/jakarta.persistence/jakarta.persistence-api/3.1.0/66901fa1c373c6aff65c13791cc11da72060a8d6/jakarta.persistence-api-3.1.0.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/jakarta.transaction/jakarta.transaction-api/2.0.1/51a520e3fae406abb84e2e1148e6746ce3f80a1a/jakarta.transaction-api-2.0.1.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.springframework.security/spring-security-oauth2-core/6.4.3/b7aef705d9a1b41ae1ff287e9381dbc61985df56/spring-security-oauth2-core-6.4.3.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/com.nimbusds/oauth2-oidc-sdk/9.43.6/a1842456e236f53e30946b2cb0bdeb17a44cdfd3/oauth2-oidc-sdk-9.43.6.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/com.nimbusds/nimbus-jose-jwt/9.37.3/700f71ffefd60c16bd8ce711a956967ea9071cec/nimbus-jose-jwt-9.37.3.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.springframework.security/spring-security-crypto/6.4.3/157bf3d0173f12882938e244ba5c1359d72908d9/spring-security-crypto-6.4.3.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/ch.qos.logback/logback-classic/1.5.16/113979db51dfad6dc895b34460d7b7ff64ffe7d2/logback-classic-1.5.16.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.apache.logging.log4j/log4j-to-slf4j/2.24.3/da1143e2a2531ee1c2d90baa98eb50a28a39d5a7/log4j-to-slf4j-2.24.3.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.slf4j/jul-to-slf4j/2.0.16/6d57da3e961daac65bcca0dd3def6cd11e48a24a/jul-to-slf4j-2.0.16.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.springframework/spring-jcl/6.2.3/ce8bf55aa240fdeab07b3da1f462c79112a33aff/spring-jcl-6.2.3.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.core/jackson-annotations/2.18.2/985d77751ebc7fce5db115a986bc9aa82f973f4a/jackson-annotations-2.18.2.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.core/jackson-core/2.18.2/fb64ccac5c27dca8819418eb4e443a9f496d9ee7/jackson-core-2.18.2.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/io.micrometer/micrometer-commons/1.14.4/3664f95586514d8f9adc81a5f7a5ef9f66b65599/micrometer-commons-1.14.4.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/com.github.stephenc.jcip/jcip-annotations/1.0-1/ef31541dd28ae2cefdd17c7ebf352d93e9058c63/jcip-annotations-1.0-1.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/com.nimbusds/content-type/2.2/9a894bce7646dd4086652d85b88013229f23724b/content-type-2.2.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/net.minidev/json-smart/2.5.2/95d166b18f95907be0f46cdb9e1c0695eed03387/json-smart-2.5.2.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/com.nimbusds/lang-tag/1.7/97c73ecd70bc7e8eefb26c5eea84f251a63f1031/lang-tag-1.7.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/ch.qos.logback/logback-core/1.5.16/4f17700f046900aea2fadf115e2d67fec921f7fd/logback-core-1.5.16.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.apache.logging.log4j/log4j-api/2.24.3/b02c125db8b6d295adf72ae6e71af5d83bce2370/log4j-api-2.24.3.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/net.minidev/accessors-smart/2.5.2/ce16fd235cfee48e67eda33e684423bba09f7d07/accessors-smart-2.5.2.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.ow2.asm/asm/9.7.1/f0ed132a49244b042cd0e15702ab9f2ce3cc8436/asm-9.7.1.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/io.jsonwebtoken/jjwt-impl/0.11.5/40a599f0e8a8e4e0701596fbb15e67bfda64fdf0/jjwt-impl-0.11.5.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/io.jsonwebtoken/jjwt-jackson/0.11.5/3b83a06809e98a69402d7333dcf67df6f6ea4579/jjwt-jackson-0.11.5.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/com.mysql/mysql-connector-j/9.1.0/5fb1d513278e1a9767dfa80ea9d8d7ee909f1a/mysql-connector-j-9.1.0.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.hibernate.common/hibernate-commons-annotations/7.0.3.Final/e183c4be8bb41d12e9f19b374e00c34a0a85f439/hibernate-commons-annotations-7.0.3.Final.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/io.smallrye/jandex/3.2.0/f17ad860f62a08487b9edabde608f8ac55c62fa7/jandex-3.2.0.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/net.bytebuddy/byte-buddy/1.15.11/f61886478e0f9ee4c21d09574736f0ff45e0a46c/byte-buddy-1.15.11.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.glassfish.jaxb/jaxb-runtime/4.0.5/ca84c2a7169b5293e232b9d00d1e4e36d4c3914a/jaxb-runtime-4.0.5.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/jakarta.xml.bind/jakarta.xml.bind-api/4.0.2/6cd5a999b834b63238005b7144136379dc36cad2/jakarta.xml.bind-api-4.0.2.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/jakarta.inject/jakarta.inject-api/2.0.1/4c28afe1991a941d7702fe1362c365f0a8641d1e/jakarta.inject-api-2.0.1.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.glassfish.jaxb/jaxb-core/4.0.5/7b4b11ea5542eea4ad55e1080b23be436795b3/jaxb-core-4.0.5.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/jakarta.activation/jakarta.activation-api/2.1.3/fa165bd70cda600368eee31555222776a46b881f/jakarta.activation-api-2.1.3.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.eclipse.angus/angus-activation/2.0.2/41f1e0ddd157c856926ed149ab837d110955a9fc/angus-activation-2.0.2.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/org.glassfish.jaxb/txw2/4.0.5/f36a4ef12120a9bb06d766d6a0e54b144fd7ed98/txw2-4.0.5.jar:/Users/mun/.gradle/caches/modules-2/files-2.1/com.sun.istack/istack-commons-runtime/4.1.2/18ec117c85f3ba0ac65409136afa8e42bc74e739/istack-commons-runtime-4.1.2.jar com.example.foodduck.FoodduckApplication
OpenJDK 64-Bit Server VM warning: Options -Xverify:none and -noverify were deprecated in JDK 13 and will likely be removed in a future release.
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v3.4.3)
2025-03-05T18:13:33.431+09:00 INFO 42288 --- [foodduck] [ main] c.example.foodduck.FoodduckApplication : Starting FoodduckApplication using Java 17.0.14 with PID 42288 (/Users/mun/Desktop/2025/foodduck/build/classes/java/main started by mun in /Users/mun/Desktop/2025/foodduck)
2025-03-05T18:13:33.433+09:00 INFO 42288 --- [foodduck] [ main] c.example.foodduck.FoodduckApplication : No active profile set, falling back to 1 default profile: "default"
2025-03-05T18:13:33.942+09:00 INFO 42288 --- [foodduck] [ main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode.
2025-03-05T18:13:34.006+09:00 INFO 42288 --- [foodduck] [ main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 55 ms. Found 5 JPA repository interfaces.
2025-03-05T18:13:34.425+09:00 INFO 42288 --- [foodduck] [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port 8080 (http)
2025-03-05T18:13:34.431+09:00 INFO 42288 --- [foodduck] [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2025-03-05T18:13:34.431+09:00 INFO 42288 --- [foodduck] [ main] o.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/10.1.36]
2025-03-05T18:13:34.450+09:00 INFO 42288 --- [foodduck] [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2025-03-05T18:13:34.450+09:00 INFO 42288 --- [foodduck] [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 988 ms
2025-03-05T18:13:34.512+09:00 INFO 42288 --- [foodduck] [ main] o.hibernate.jpa.internal.util.LogHelper : HHH000204: Processing PersistenceUnitInfo [name: default]
2025-03-05T18:13:34.533+09:00 INFO 42288 --- [foodduck] [ main] org.hibernate.Version : HHH000412: Hibernate ORM core version 6.6.8.Final
2025-03-05T18:13:34.555+09:00 INFO 42288 --- [foodduck] [ main] o.h.c.internal.RegionFactoryInitiator : HHH000026: Second-level cache disabled
2025-03-05T18:13:34.675+09:00 INFO 42288 --- [foodduck] [ main] o.s.o.j.p.SpringPersistenceUnitInfo : No LoadTimeWeaver setup: ignoring JPA class transformer
2025-03-05T18:13:34.686+09:00 INFO 42288 --- [foodduck] [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
2025-03-05T18:13:35.010+09:00 INFO 42288 --- [foodduck] [ main] com.zaxxer.hikari.pool.HikariPool : HikariPool-1 - Added connection com.mysql.cj.jdbc.ConnectionImpl@1805ec96
2025-03-05T18:13:35.012+09:00 INFO 42288 --- [foodduck] [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
2025-03-05T18:13:35.073+09:00 WARN 42288 --- [foodduck] [ main] org.hibernate.orm.deprecation : HHH90000025: MySQLDialect does not need to be specified explicitly using 'hibernate.dialect' (remove the property setting and it will be selected by default)
2025-03-05T18:13:35.081+09:00 INFO 42288 --- [foodduck] [ main] org.hibernate.orm.connections.pooling : HHH10001005: Database info:
Database JDBC URL [Connecting through datasource 'HikariDataSource (HikariPool-1)']
Database driver: undefined/unknown
Database version: 8.0.41
Autocommit mode: undefined/unknown
Isolation level: undefined/unknown
Minimum pool size: undefined/unknown
Maximum pool size: undefined/unknown
2025-03-05T18:13:35.469+09:00 INFO 42288 --- [foodduck] [ main] o.h.e.t.j.p.i.JtaPlatformInitiator : HHH000489: No JTA platform available (set 'hibernate.transaction.jta.platform' to enable JTA platform integration)
Hibernate:
alter table menus
drop
foreign key FK4qbknsshykl217vgvbdr5jtr6
Hibernate:
alter table orders
drop
foreign key FKmkjklkcop7gfy47xu3i7aod7w
Hibernate:
alter table orders
drop
foreign key FKnqkwhwveegs6ne9ra90y1gq0e
2025-03-05T18:13:35.617+09:00 WARN 42288 --- [foodduck] [ main] o.h.t.s.i.ExceptionHandlerLoggedImpl : GenerationTarget encountered exception accepting command : Error executing DDL "
alter table orders
drop
foreign key FKnqkwhwveegs6ne9ra90y1gq0e" via JDBC [Can't DROP 'FKnqkwhwveegs6ne9ra90y1gq0e'; check that column/key exists]
org.hibernate.tool.schema.spi.CommandAcceptanceException: Error executing DDL "
alter table orders
drop
foreign key FKnqkwhwveegs6ne9ra90y1gq0e" via JDBC [Can't DROP 'FKnqkwhwveegs6ne9ra90y1gq0e'; check that column/key exists]
at org.hibernate.tool.schema.internal.exec.GenerationTargetToDatabase.accept(GenerationTargetToDatabase.java:94) ~[hibernate-core-6.6.8.Final.jar:6.6.8.Final]
at org.hibernate.tool.schema.internal.Helper.applySqlString(Helper.java:233) ~[hibernate-core-6.6.8.Final.jar:6.6.8.Final]
at org.hibernate.tool.schema.internal.Helper.applySqlStrings(Helper.java:217) ~[hibernate-core-6.6.8.Final.jar:6.6.8.Final]
at org.hibernate.tool.schema.internal.SchemaDropperImpl.applyConstraintDropping(SchemaDropperImpl.java:479) ~[hibernate-core-6.6.8.Final.jar:6.6.8.Final]
at org.hibernate.tool.schema.internal.SchemaDropperImpl.dropConstraintsTablesSequences(SchemaDropperImpl.java:245) ~[hibernate-core-6.6.8.Final.jar:6.6.8.Final]
at org.hibernate.tool.schema.internal.SchemaDropperImpl.dropFromMetadata(SchemaDropperImpl.java:218) ~[hibernate-core-6.6.8.Final.jar:6.6.8.Final]
at org.hibernate.tool.schema.internal.SchemaDropperImpl.performDrop(SchemaDropperImpl.java:186) ~[hibernate-core-6.6.8.Final.jar:6.6.8.Final]
at org.hibernate.tool.schema.internal.SchemaDropperImpl.doDrop(SchemaDropperImpl.java:156) ~[hibernate-core-6.6.8.Final.jar:6.6.8.Final]
at org.hibernate.tool.schema.internal.SchemaDropperImpl.doDrop(SchemaDropperImpl.java:116) ~[hibernate-core-6.6.8.Final.jar:6.6.8.Final]
at org.hibernate.tool.schema.spi.SchemaManagementToolCoordinator.performDatabaseAction(SchemaManagementToolCoordinator.java:238) ~[hibernate-core-6.6.8.Final.jar:6.6.8.Final]
at org.hibernate.tool.schema.spi.SchemaManagementToolCoordinator.lambda$process$5(SchemaManagementToolCoordinator.java:144) ~[hibernate-core-6.6.8.Final.jar:6.6.8.Final]
at java.base/java.util.HashMap.forEach(HashMap.java:1421) ~[na:na]
at org.hibernate.tool.schema.spi.SchemaManagementToolCoordinator.process(SchemaManagementToolCoordinator.java:141) ~[hibernate-core-6.6.8.Final.jar:6.6.8.Final]
at org.hibernate.boot.internal.SessionFactoryObserverForSchemaExport.sessionFactoryCreated(SessionFactoryObserverForSchemaExport.java:37) ~[hibernate-core-6.6.8.Final.jar:6.6.8.Final]
at org.hibernate.internal.SessionFactoryObserverChain.sessionFactoryCreated(SessionFactoryObserverChain.java:35) ~[hibernate-core-6.6.8.Final.jar:6.6.8.Final]
at org.hibernate.internal.SessionFactoryImpl.<init>(SessionFactoryImpl.java:324) ~[hibernate-core-6.6.8.Final.jar:6.6.8.Final]
at org.hibernate.boot.internal.SessionFactoryBuilderImpl.build(SessionFactoryBuilderImpl.java:463) ~[hibernate-core-6.6.8.Final.jar:6.6.8.Final]
at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:1517) ~[hibernate-core-6.6.8.Final.jar:6.6.8.Final]
at org.springframework.orm.jpa.vendor.SpringHibernateJpaPersistenceProvider.createContainerEntityManagerFactory(SpringHibernateJpaPersistenceProvider.java:66) ~[spring-orm-6.2.3.jar:6.2.3]
at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.createNativeEntityManagerFactory(LocalContainerEntityManagerFactoryBean.java:390) ~[spring-orm-6.2.3.jar:6.2.3]
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.buildNativeEntityManagerFactory(AbstractEntityManagerFactoryBean.java:419) ~[spring-orm-6.2.3.jar:6.2.3]
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.afterPropertiesSet(AbstractEntityManagerFactoryBean.java:400) ~[spring-orm-6.2.3.jar:6.2.3]
at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.afterPropertiesSet(LocalContainerEntityManagerFactoryBean.java:366) ~[spring-orm-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1859) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1808) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:601) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:523) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:339) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:346) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:337) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:365) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveValueIfNecessary(BeanDefinitionValueResolver.java:135) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.ConstructorResolver.resolveConstructorArguments(ConstructorResolver.java:691) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:513) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1361) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1191) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:563) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:523) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:339) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:346) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:337) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:365) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveValueIfNecessary(BeanDefinitionValueResolver.java:135) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyPropertyValues(AbstractAutowireCapableBeanFactory.java:1711) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1460) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:600) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:523) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:339) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:346) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:337) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1606) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1552) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:913) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:791) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:240) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1381) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1218) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:563) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:523) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:339) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:346) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:337) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:254) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1664) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1552) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:913) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:791) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:240) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1381) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1218) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:563) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:523) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:339) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:346) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:337) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:413) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1361) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1191) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:563) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:523) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:339) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:346) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:337) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:207) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.boot.web.servlet.ServletContextInitializerBeans.getOrderedBeansOfType(ServletContextInitializerBeans.java:211) ~[spring-boot-3.4.3.jar:3.4.3]
at org.springframework.boot.web.servlet.ServletContextInitializerBeans.addAsRegistrationBean(ServletContextInitializerBeans.java:174) ~[spring-boot-3.4.3.jar:3.4.3]
at org.springframework.boot.web.servlet.ServletContextInitializerBeans.addAsRegistrationBean(ServletContextInitializerBeans.java:169) ~[spring-boot-3.4.3.jar:3.4.3]
at org.springframework.boot.web.servlet.ServletContextInitializerBeans.addAdaptableBeans(ServletContextInitializerBeans.java:154) ~[spring-boot-3.4.3.jar:3.4.3]
at org.springframework.boot.web.servlet.ServletContextInitializerBeans.<init>(ServletContextInitializerBeans.java:87) ~[spring-boot-3.4.3.jar:3.4.3]
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.getServletContextInitializerBeans(ServletWebServerApplicationContext.java:271) ~[spring-boot-3.4.3.jar:3.4.3]
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.selfInitialize(ServletWebServerApplicationContext.java:245) ~[spring-boot-3.4.3.jar:3.4.3]
at org.springframework.boot.web.embedded.tomcat.TomcatStarter.onStartup(TomcatStarter.java:52) ~[spring-boot-3.4.3.jar:3.4.3]
at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:4467) ~[tomcat-embed-core-10.1.36.jar:10.1.36]
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164) ~[tomcat-embed-core-10.1.36.jar:10.1.36]
at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1203) ~[tomcat-embed-core-10.1.36.jar:10.1.36]
at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1193) ~[tomcat-embed-core-10.1.36.jar:10.1.36]
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264) ~[na:na]
at org.apache.tomcat.util.threads.InlineExecutorService.execute(InlineExecutorService.java:75) ~[tomcat-embed-core-10.1.36.jar:10.1.36]
at java.base/java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:145) ~[na:na]
at org.apache.catalina.core.ContainerBase.startInternal(ContainerBase.java:749) ~[tomcat-embed-core-10.1.36.jar:10.1.36]
at org.apache.catalina.core.StandardHost.startInternal(StandardHost.java:772) ~[tomcat-embed-core-10.1.36.jar:10.1.36]
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164) ~[tomcat-embed-core-10.1.36.jar:10.1.36]
at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1203) ~[tomcat-embed-core-10.1.36.jar:10.1.36]
at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1193) ~[tomcat-embed-core-10.1.36.jar:10.1.36]
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264) ~[na:na]
at org.apache.tomcat.util.threads.InlineExecutorService.execute(InlineExecutorService.java:75) ~[tomcat-embed-core-10.1.36.jar:10.1.36]
at java.base/java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:145) ~[na:na]
at org.apache.catalina.core.ContainerBase.startInternal(ContainerBase.java:749) ~[tomcat-embed-core-10.1.36.jar:10.1.36]
at org.apache.catalina.core.StandardEngine.startInternal(StandardEngine.java:203) ~[tomcat-embed-core-10.1.36.jar:10.1.36]
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164) ~[tomcat-embed-core-10.1.36.jar:10.1.36]
at org.apache.catalina.core.StandardService.startInternal(StandardService.java:415) ~[tomcat-embed-core-10.1.36.jar:10.1.36]
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164) ~[tomcat-embed-core-10.1.36.jar:10.1.36]
at org.apache.catalina.core.StandardServer.startInternal(StandardServer.java:870) ~[tomcat-embed-core-10.1.36.jar:10.1.36]
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164) ~[tomcat-embed-core-10.1.36.jar:10.1.36]
at org.apache.catalina.startup.Tomcat.start(Tomcat.java:437) ~[tomcat-embed-core-10.1.36.jar:10.1.36]
at org.springframework.boot.web.embedded.tomcat.TomcatWebServer.initialize(TomcatWebServer.java:128) ~[spring-boot-3.4.3.jar:3.4.3]
at org.springframework.boot.web.embedded.tomcat.TomcatWebServer.<init>(TomcatWebServer.java:107) ~[spring-boot-3.4.3.jar:3.4.3]
at org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory.getTomcatWebServer(TomcatServletWebServerFactory.java:520) ~[spring-boot-3.4.3.jar:3.4.3]
at org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory.getWebServer(TomcatServletWebServerFactory.java:222) ~[spring-boot-3.4.3.jar:3.4.3]
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.createWebServer(ServletWebServerApplicationContext.java:193) ~[spring-boot-3.4.3.jar:3.4.3]
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.onRefresh(ServletWebServerApplicationContext.java:167) ~[spring-boot-3.4.3.jar:3.4.3]
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:621) ~[spring-context-6.2.3.jar:6.2.3]
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146) ~[spring-boot-3.4.3.jar:3.4.3]
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:752) ~[spring-boot-3.4.3.jar:3.4.3]
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:439) ~[spring-boot-3.4.3.jar:3.4.3]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:318) ~[spring-boot-3.4.3.jar:3.4.3]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1361) ~[spring-boot-3.4.3.jar:3.4.3]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1350) ~[spring-boot-3.4.3.jar:3.4.3]
at com.example.foodduck.FoodduckApplication.main(FoodduckApplication.java:16) ~[main/:na]
Caused by: java.sql.SQLSyntaxErrorException: Can't DROP 'FKnqkwhwveegs6ne9ra90y1gq0e'; check that column/key exists
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:112) ~[mysql-connector-j-9.1.0.jar:9.1.0]
at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:114) ~[mysql-connector-j-9.1.0.jar:9.1.0]
at com.mysql.cj.jdbc.StatementImpl.executeInternal(StatementImpl.java:837) ~[mysql-connector-j-9.1.0.jar:9.1.0]
at com.mysql.cj.jdbc.StatementImpl.execute(StatementImpl.java:685) ~[mysql-connector-j-9.1.0.jar:9.1.0]
at com.zaxxer.hikari.pool.ProxyStatement.execute(ProxyStatement.java:94) ~[HikariCP-5.1.0.jar:na]
at com.zaxxer.hikari.pool.HikariProxyStatement.execute(HikariProxyStatement.java) ~[HikariCP-5.1.0.jar:na]
at org.hibernate.tool.schema.internal.exec.GenerationTargetToDatabase.accept(GenerationTargetToDatabase.java:80) ~[hibernate-core-6.6.8.Final.jar:6.6.8.Final]
... 133 common frames omitted
Hibernate:
alter table orders
drop
foreign key FK32ql8ubntj5uh44ph9659tiih
Hibernate:
alter table stores
drop
foreign key FK62smc31fbgclsu56aa4y2hrxg
Hibernate:
drop table if exists menus
Hibernate:
drop table if exists orders
Hibernate:
drop table if exists refresh_tokens
Hibernate:
drop table if exists stores
Hibernate:
drop table if exists users
Hibernate:
create table menus (
price integer not null,
created_at datetime(6),
id bigint not null auto_increment,
store_id bigint,
updated_at datetime(6),
menu_name varchar(255),
menu_state enum ('ON_SALE','REMOVED','SOLD_OUT'),
primary key (id)
) engine=InnoDB
Hibernate:
create table orders (
created_at datetime(6),
id bigint not null auto_increment,
menu_id bigint not null,
store_id bigint not null,
updated_at datetime(6),
user_id bigint not null,
order_status enum ('DELIVERY','REJECTED','REMOVED','REQUESTED'),
primary key (id)
) engine=InnoDB
Hibernate:
create table refresh_tokens (
user_id bigint not null,
token varchar(255) not null,
primary key (user_id)
) engine=InnoDB
Hibernate:
create table stores (
close_time time(6) not null,
like_count integer not null,
min_order_price integer not null,
open_time time(6) not null,
order_count integer not null,
created_at datetime(6),
id bigint not null auto_increment,
owner_id bigint not null,
updated_at datetime(6),
name varchar(255) not null,
break_state enum ('ACTIVE','INACTIVE') not null,
store_state enum ('ACTIVE','INACTIVE') not null,
primary key (id)
) engine=InnoDB
Hibernate:
create table users (
is_deleted bit not null,
store_count integer not null,
created_at datetime(6),
id bigint not null auto_increment,
updated_at datetime(6),
email varchar(255) not null,
name varchar(255),
password varchar(255) not null,
role enum ('OWNER','USER') not null,
primary key (id)
) engine=InnoDB
Hibernate:
alter table users
add constraint UK6dotkott2kjsp8vw4d0m25fb7 unique (email)
Hibernate:
alter table menus
add constraint FK4qbknsshykl217vgvbdr5jtr6
foreign key (store_id)
references stores (id)
Hibernate:
alter table orders
add constraint FKmkjklkcop7gfy47xu3i7aod7w
foreign key (menu_id)
references menus (id)
Hibernate:
alter table orders
add constraint FKnqkwhwveegs6ne9ra90y1gq0e
foreign key (store_id)
references stores (id)
Hibernate:
alter table orders
add constraint FK32ql8ubntj5uh44ph9659tiih
foreign key (user_id)
references users (id)
Hibernate:
alter table stores
add constraint FK62smc31fbgclsu56aa4y2hrxg
foreign key (owner_id)
references users (id)
2025-03-05T18:13:35.921+09:00 INFO 42288 --- [foodduck] [ main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2025-03-05T18:13:36.218+09:00 WARN 42288 --- [foodduck] [ main] JpaBaseConfiguration$JpaWebConfiguration : spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
2025-03-05T18:13:36.227+09:00 WARN 42288 --- [foodduck] [ main] ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'securityFilterChain' defined in class path resource [com/example/foodduck/common/config/jwt/SecurityConfig.class]: Unsatisfied dependency expressed through method 'securityFilterChain' parameter 0: Error creating bean with name 'org.springframework.security.config.annotation.web.configuration.HttpSecurityConfiguration': Unsatisfied dependency expressed through method 'setContentNegotiationStrategy' parameter 0: Error creating bean with name 'org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration$EnableWebMvcConfiguration': Unsatisfied dependency expressed through method 'setConfigurers' parameter 0: Error creating bean with name 'org.springframework.security.config.annotation.web.configuration.OAuth2ClientConfiguration$OAuth2ClientWebMvcSecurityConfiguration': Failed to instantiate [org.springframework.security.config.annotation.web.configuration.OAuth2ClientConfiguration$OAuth2ClientWebMvcSecurityConfiguration]: Constructor threw exception
2025-03-05T18:13:36.227+09:00 INFO 42288 --- [foodduck] [ main] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default'
2025-03-05T18:13:36.229+09:00 INFO 42288 --- [foodduck] [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown initiated...
2025-03-05T18:13:36.233+09:00 INFO 42288 --- [foodduck] [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown completed.
2025-03-05T18:13:36.234+09:00 INFO 42288 --- [foodduck] [ main] o.apache.catalina.core.StandardService : Stopping service [Tomcat]
2025-03-05T18:13:36.240+09:00 INFO 42288 --- [foodduck] [ main] .s.b.a.l.ConditionEvaluationReportLogger :
Error starting ApplicationContext. To display the condition evaluation report re-run your application with 'debug' enabled.
2025-03-05T18:13:36.248+09:00 ERROR 42288 --- [foodduck] [ main] o.s.boot.SpringApplication : Application run failed
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'securityFilterChain' defined in class path resource [com/example/foodduck/common/config/jwt/SecurityConfig.class]: Unsatisfied dependency expressed through method 'securityFilterChain' parameter 0: Error creating bean with name 'org.springframework.security.config.annotation.web.configuration.HttpSecurityConfiguration': Unsatisfied dependency expressed through method 'setContentNegotiationStrategy' parameter 0: Error creating bean with name 'org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration$EnableWebMvcConfiguration': Unsatisfied dependency expressed through method 'setConfigurers' parameter 0: Error creating bean with name 'org.springframework.security.config.annotation.web.configuration.OAuth2ClientConfiguration$OAuth2ClientWebMvcSecurityConfiguration': Failed to instantiate [org.springframework.security.config.annotation.web.configuration.OAuth2ClientConfiguration$OAuth2ClientWebMvcSecurityConfiguration]: Constructor threw exception
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:804) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:546) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1361) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1191) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:563) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:523) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:339) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:346) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:337) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.instantiateSingleton(DefaultListableBeanFactory.java:1155) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingleton(DefaultListableBeanFactory.java:1121) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:1056) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:987) ~[spring-context-6.2.3.jar:6.2.3]
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:627) ~[spring-context-6.2.3.jar:6.2.3]
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146) ~[spring-boot-3.4.3.jar:3.4.3]
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:752) ~[spring-boot-3.4.3.jar:3.4.3]
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:439) ~[spring-boot-3.4.3.jar:3.4.3]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:318) ~[spring-boot-3.4.3.jar:3.4.3]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1361) ~[spring-boot-3.4.3.jar:3.4.3]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1350) ~[spring-boot-3.4.3.jar:3.4.3]
at com.example.foodduck.FoodduckApplication.main(FoodduckApplication.java:16) ~[main/:na]
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'org.springframework.security.config.annotation.web.configuration.HttpSecurityConfiguration': Unsatisfied dependency expressed through method 'setContentNegotiationStrategy' parameter 0: Error creating bean with name 'org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration$EnableWebMvcConfiguration': Unsatisfied dependency expressed through method 'setConfigurers' parameter 0: Error creating bean with name 'org.springframework.security.config.annotation.web.configuration.OAuth2ClientConfiguration$OAuth2ClientWebMvcSecurityConfiguration': Failed to instantiate [org.springframework.security.config.annotation.web.configuration.OAuth2ClientConfiguration$OAuth2ClientWebMvcSecurityConfiguration]: Constructor threw exception
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredMethodElement.resolveMethodArguments(AutowiredAnnotationBeanPostProcessor.java:896) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredMethodElement.inject(AutowiredAnnotationBeanPostProcessor.java:849) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:146) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:509) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1445) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:600) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:523) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:339) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:346) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:337) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:413) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1361) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1191) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:563) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:523) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:357) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:254) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1664) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1552) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:913) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:791) ~[spring-beans-6.2.3.jar:6.2.3]
... 21 common frames omitted
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration$EnableWebMvcConfiguration': Unsatisfied dependency expressed through method 'setConfigurers' parameter 0: Error creating bean with name 'org.springframework.security.config.annotation.web.configuration.OAuth2ClientConfiguration$OAuth2ClientWebMvcSecurityConfiguration': Failed to instantiate [org.springframework.security.config.annotation.web.configuration.OAuth2ClientConfiguration$OAuth2ClientWebMvcSecurityConfiguration]: Constructor threw exception
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredMethodElement.resolveMethodArguments(AutowiredAnnotationBeanPostProcessor.java:896) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredMethodElement.inject(AutowiredAnnotationBeanPostProcessor.java:849) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:146) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:509) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1445) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:600) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:523) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:339) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:346) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:337) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:413) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1361) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1191) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:563) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:523) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:339) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:346) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:337) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:254) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1664) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1552) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredMethodElement.resolveMethodArguments(AutowiredAnnotationBeanPostProcessor.java:888) ~[spring-beans-6.2.3.jar:6.2.3]
... 43 common frames omitted
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.security.config.annotation.web.configuration.OAuth2ClientConfiguration$OAuth2ClientWebMvcSecurityConfiguration': Failed to instantiate [org.springframework.security.config.annotation.web.configuration.OAuth2ClientConfiguration$OAuth2ClientWebMvcSecurityConfiguration]: Constructor threw exception
at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:321) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:309) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1381) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1218) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:563) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:523) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:339) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:346) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:337) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:254) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.addCandidateEntry(DefaultListableBeanFactory.java:1916) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.findAutowireCandidates(DefaultListableBeanFactory.java:1880) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveMultipleBeanCollection(DefaultListableBeanFactory.java:1770) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveMultipleBeans(DefaultListableBeanFactory.java:1738) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1613) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1552) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredMethodElement.resolveMethodArguments(AutowiredAnnotationBeanPostProcessor.java:888) ~[spring-beans-6.2.3.jar:6.2.3]
... 66 common frames omitted
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.security.config.annotation.web.configuration.OAuth2ClientConfiguration$OAuth2ClientWebMvcSecurityConfiguration]: Constructor threw exception
at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:222) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:145) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:318) ~[spring-beans-6.2.3.jar:6.2.3]
... 83 common frames omitted
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'OAuth2AuthorizedClientManager': Failed to instantiate [org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager]: Factory method 'getAuthorizedClientManager' threw exception with message: Error creating bean with name 'clientRegistrationRepository' defined in class path resource [org/springframework/boot/autoconfigure/security/oauth2/client/servlet/OAuth2ClientRegistrationRepositoryConfiguration.class]: Failed to instantiate [org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository]: Factory method 'clientRegistrationRepository' threw exception with message: Provider ID must be specified for client registration 'kakao'
at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:657) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:489) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1361) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1191) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:563) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:523) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:339) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:346) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:337) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:254) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1664) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.DefaultListableBeanFactory$DependencyObjectProvider.getIfUnique(DefaultListableBeanFactory.java:2467) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.security.config.annotation.web.configuration.OAuth2ClientConfiguration$OAuth2ClientWebMvcSecurityConfiguration.<init>(OAuth2ClientConfiguration.java:129) ~[spring-security-config-6.4.3.jar:6.4.3]
at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) ~[na:na]
at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:77) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) ~[na:na]
at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:500) ~[na:na]
at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:481) ~[na:na]
at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:209) ~[spring-beans-6.2.3.jar:6.2.3]
... 85 common frames omitted
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager]: Factory method 'getAuthorizedClientManager' threw exception with message: Error creating bean with name 'clientRegistrationRepository' defined in class path resource [org/springframework/boot/autoconfigure/security/oauth2/client/servlet/OAuth2ClientRegistrationRepositoryConfiguration.class]: Failed to instantiate [org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository]: Factory method 'clientRegistrationRepository' threw exception with message: Provider ID must be specified for client registration 'kakao'
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.lambda$instantiate$0(SimpleInstantiationStrategy.java:199) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiateWithFactoryMethod(SimpleInstantiationStrategy.java:88) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:168) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:653) ~[spring-beans-6.2.3.jar:6.2.3]
... 104 common frames omitted
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'clientRegistrationRepository' defined in class path resource [org/springframework/boot/autoconfigure/security/oauth2/client/servlet/OAuth2ClientRegistrationRepositoryConfiguration.class]: Failed to instantiate [org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository]: Factory method 'clientRegistrationRepository' threw exception with message: Provider ID must be specified for client registration 'kakao'
at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:657) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:645) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1361) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1191) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:563) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:523) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:339) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:346) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:337) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeansOfType(DefaultListableBeanFactory.java:721) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.BeanFactoryUtils.beansOfTypeIncludingAncestors(BeanFactoryUtils.java:372) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.BeanFactoryUtils.beanOfTypeIncludingAncestors(BeanFactoryUtils.java:450) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.security.config.annotation.web.configuration.OAuth2ClientConfiguration$OAuth2AuthorizedClientManagerRegistrar.getAuthorizedClientManager(OAuth2ClientConfiguration.java:220) ~[spring-security-config-6.4.3.jar:6.4.3]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:569) ~[na:na]
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.lambda$instantiate$0(SimpleInstantiationStrategy.java:171) ~[spring-beans-6.2.3.jar:6.2.3]
... 107 common frames omitted
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository]: Factory method 'clientRegistrationRepository' threw exception with message: Provider ID must be specified for client registration 'kakao'
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.lambda$instantiate$0(SimpleInstantiationStrategy.java:199) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiateWithFactoryMethod(SimpleInstantiationStrategy.java:88) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:168) ~[spring-beans-6.2.3.jar:6.2.3]
at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:653) ~[spring-beans-6.2.3.jar:6.2.3]
... 125 common frames omitted
Caused by: java.lang.IllegalStateException: Provider ID must be specified for client registration 'kakao'
at org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientPropertiesMapper.getBuilder(OAuth2ClientPropertiesMapper.java:109) ~[spring-boot-autoconfigure-3.4.3.jar:3.4.3]
at org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientPropertiesMapper.getClientRegistration(OAuth2ClientPropertiesMapper.java:73) ~[spring-boot-autoconfigure-3.4.3.jar:3.4.3]
at org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientPropertiesMapper.lambda$asClientRegistrations$0(OAuth2ClientPropertiesMapper.java:65) ~[spring-boot-autoconfigure-3.4.3.jar:3.4.3]
at java.base/java.util.HashMap.forEach(HashMap.java:1421) ~[na:na]
at org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientPropertiesMapper.asClientRegistrations(OAuth2ClientPropertiesMapper.java:64) ~[spring-boot-autoconfigure-3.4.3.jar:3.4.3]
at org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientRegistrationRepositoryConfiguration.clientRegistrationRepository(OAuth2ClientRegistrationRepositoryConfiguration.java:49) ~[spring-boot-autoconfigure-3.4.3.jar:3.4.3]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:569) ~[na:na]
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.lambda$instantiate$0(SimpleInstantiationStrategy.java:171) ~[spring-beans-6.2.3.jar:6.2.3]
... 128 common frames omitted
Process finished with exit code 1
Error creating bean with name 'clientRegistrationRepository': Provider ID must be specified for client registration 'kakao'
spring.security.oauth2.client.registration.kakao.provider=kakao
spring.security.oauth2.client.provider.kakao.authorization-uri=https://kauth.kakao.com/oauth/authorize
spring.security.oauth2.client.provider.kakao.token-uri=https://kauth.kakao.com/oauth/token
spring.security.oauth2.client.provider.kakao.user-info-uri=https://kapi.kakao.com/v2/user/me
spring.security.oauth2.client.provider.kakao.user-name-attribute=id
혹시 모르니 db도
package com.example.foodduck.common.config.jwt;
import com.example.foodduck.common.config.jwt.filter.JwtAuthenticationFilter;
import com.example.foodduck.user.entity.User;
import com.example.foodduck.user.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.client.OAuth2AuthorizationSuccessHandler;
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
// @EnableWebSecurity -> refactor: 필요하지 않다고 판단되어 삭제
// 3.x 이상부터 @Configuration이 붙은 클래스에서 SecurityFilterChain을 정의하면 자동으로 감지되기 때문에 @EnableWebSecurity가 없어도 잘 동작하기 때문
@RequiredArgsConstructor
public class SecurityConfig {
private final JwtUtil jwtUtil;
private final UserDetailsService userDetailsService;
// private final OAuth2AuthorizationSuccessHandler oAuth2AuthorizationSuccessHandler;
/*
타고 가보면, Spring Security의 OAuth2AuthorizationSuccessHandler 인터페이스는 OAuth 2.0 클라이언트가 성공적으로 인증되었을 때 동작하는 기본적인 후처리 인터페이스임으로
생성해서 활용해야 한다고 판단.
즉, OAuth2 로그인 후 JWT를 발급해야 하는 추가적인 로직이 필요하기 때문에 OAuth2SuccessHandler를 직접 구현해서 사용할 계획.
*/
private final OAuth2SuccessHandler oAuth2SuccessHandler;
private final CustomOAuth2UserService customOAuth2UserService;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeHttpRequests(auth -> auth
.requestMatchers("/users/register", "/users/login", "menus", "menus/{menuId}").permitAll()
.requestMatchers("/users/logout").authenticated()
.requestMatchers("/stores/**").hasAuthority("ROLE_OWNER")
.requestMatchers("/menus/{storeid}", "/menus/{menuId}/update", "menus/{menuId}/delete").hasAuthority("ROLE_OWNER")
.requestMatchers("/orders/request").hasRole("USER")
.anyRequest().authenticated()
)
.oauth2Login(oauth2 -> oauth2
.userInfoEndpoint(userInfo -> userInfo
//.userService(oAuth2UserService()) //CustomOAuth2UserService가 @Service로 등록되어 있으므로 여기서 자동 주입됨
/*
TODO: (FIX) 25.03.05.18:07
oAuth2UserService()는 SecurityConfig 내에서 @Bean으로 등록된 메서드가 아니며,
CustomOAuth2UserService는 @Service로 등록되어 있어서, @Autowired로 주입받아야 합니다.
하지만 oAuth2UserService()를 직접 호출하려고 해서, Spring이 관리하는 빈을 찾을 수 없어서 오류 발생중
<해결 방법>
- CustomOAuth2UserService를 @Autowired 또는 생성자를 통해 주입받아서 사용하면 됨.
- SecurityConfig에 private final CustomOAuth2UserService customOAuth2UserService; 추가 후 사용=.
*/
// fix(25.03.05.18:23) : 직접 메서드 호출이 아니라, 빈 주입된 객체 사용
.userService(customOAuth2UserService)
)
.successHandler(oAuth2SuccessHandler)
)
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class); //필터 직접 등록
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public UserDetailsService userDetailsService(UserRepository userRepository) {
return email -> {
User user = userRepository.findByEmail(email)
.orElseThrow(() -> new RuntimeException("사용자를 찾을 수 없습니다."));
return org.springframework.security.core.userdetails.User
.withUsername(user.getEmail())
.password(user.getPassword())
.roles(user.getRole().name())
.build();
};
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
@Bean
public JwtAuthenticationFilter jwtAuthenticationFilter() { //필터를Bean으로 등록
return new JwtAuthenticationFilter(jwtUtil, userDetailsService);
}
// @Bean
// public DefaultOAuth2UserService oAuth2UserService() {
// return new CustomOAuth2UserService();
// }
/*
이유 : 직접 생성하려고 해서 오류 발생. 어차피 CustomOAuth2UserService는 @Service로 자동 등록되므로, @Bean으로 등록할 필요 x
*/
}
package com.example.foodduck.common.config.jwt;
import lombok.Getter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.core.user.OAuth2User;
import java.util.Collection;
import java.util.List;
import java.util.Map;
@Getter
public class CustomOAuth2User implements OAuth2User {
private final Map<String, Object> attributes;
private final String email;
private final Collection<? extends GrantedAuthority> authorities;
public CustomOAuth2User(Map<String, Object> attributes, com.example.foodduck.user.entity.User user) {
this.attributes = attributes;
this.email = user.getEmail();
this.authorities = List.of(new SimpleGrantedAuthority("ROLE_" + user.getRole().name()));
}
@Override
public Map<String, Object> getAttributes() {
return attributes;
}
@Override
public String getName() {
return (String) attributes.getOrDefault("name", email); // 이름이 없으면 이메일 반환
// attributes의 값은 Map<String, Object>인데, getOrDefault("name", email)을 호출할 때 Object 타입을 String으로 변환하지 않고 사용하고 있어서 타입 변환해줌.
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
}
package com.example.foodduck.common.config.jwt;
import com.example.foodduck.user.entity.User;
import com.example.foodduck.user.entity.UserRole;
import com.example.foodduck.user.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Service;
import java.util.Optional;
@Service
@RequiredArgsConstructor
public class CustomOAuth2UserService extends DefaultOAuth2UserService {
private final UserRepository userRepository;
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) {
OAuth2User oauth2User = super.loadUser(userRequest);
String email = oauth2User.getAttribute("email");
String name = oauth2User.getAttribute("name"); // OAuth2 제공자로부터 이름 가져오기
if (email == null) {
throw new IllegalStateException("OAuth2 로그인에서 이메일을 제공하지 않았습니다.");
}
Optional<User> existingUser = userRepository.findByEmail(email);
User user;
if (existingUser.isPresent()) {
user = existingUser.get();
} else {
user = User.builder()
.name(name != null ? name : "OAuth User") // 이름이 없으면 기본값 설정
.email(email)
.password("OAUTH_USER") // OAuth 사용자는 비밀번호가 필요 없음
.role(UserRole.USER)
.build();
userRepository.save(user);
}
return new CustomOAuth2User(oauth2User.getAttributes(), user);
}
}
package com.example.foodduck.common.config.jwt;
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.nio.charset.StandardCharsets;
import java.security.Key;
import java.util.Date;
/*
Access Token + Refresh Token을 분리해서 관리하는 구조
*/
@Component
public class JwtUtil {
private final Key key;
private final long accessTokenExpirationMs;
private final long refreshTokenExpirationMs;
public JwtUtil(@Value("${jwt.secret}") String jwtSecret,
@Value("${jwt.accessExpirationMs}") long accessTokenExpirationMs,
@Value("${jwt.refreshExpirationMs}") long refreshTokenExpirationMs) {
this.key = Keys.hmacShaKeyFor(jwtSecret.getBytes(StandardCharsets.UTF_8));
this.accessTokenExpirationMs = accessTokenExpirationMs;
this.refreshTokenExpirationMs = refreshTokenExpirationMs;
}
//액세스 토큰 생성
public String generateAccessToken(String email, Long userId) {
return Jwts.builder()
.setSubject(email)
.claim("userId", userId)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + accessTokenExpirationMs))
.signWith(key, SignatureAlgorithm.HS512)
.compact();
}
//리프레시 토큰 생성 (DB에 저장)
public String generateRefreshToken(Long userId) {
return Jwts.builder()
.setSubject(String.valueOf(userId))
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + refreshTokenExpirationMs))
.signWith(key, SignatureAlgorithm.HS512)
.compact();
}
//JWT에서 클레임 추출
public Claims extractClaims(String token) {
return Jwts.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(token)
.getBody();
}
//JWT - access token 에서 이메일 추출
public String getEmailFromJwtToken(String token) {
return extractClaims(token).getSubject();
}
//JWT - access token 에서 사용자 ID 추출
public Long getUserIdFromJwtToken(String token) {
return extractClaims(token).get("userId", Long.class);
}
//JWT 유효성 검사
public boolean validateJwtToken(String token) {
try {
extractClaims(token);
return true;
} catch (JwtException | IllegalArgumentException e) {
return false;
}
}
//HTTP 요청에서 JWT 추출
public String parseJwt(HttpServletRequest request) {
String headerAuth = request.getHeader("Authorization");
if (headerAuth != null && headerAuth.startsWith("Bearer ")) {
return headerAuth.substring(7);
}
return null;
}
}
package com.example.foodduck.common.config.jwt;
import com.example.foodduck.user.entity.RefreshToken;
import com.example.foodduck.user.entity.User;
import com.example.foodduck.user.entity.UserRole;
import com.example.foodduck.user.repository.RefreshTokenRepository;
import com.example.foodduck.user.repository.UserRepository;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
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 java.io.IOException;
import java.util.Optional;
@Component
@RequiredArgsConstructor
public class OAuth2SuccessHandler implements AuthenticationSuccessHandler {
private final JwtUtil jwtUtil;
private final UserRepository userRepository;
private final RefreshTokenRepository refreshTokenRepository;
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {
OAuth2User oAuth2User = (OAuth2User) authentication.getPrincipal();
String email = oAuth2User.getAttribute("email");
if (email == null) {
throw new IllegalStateException("OAuth2 로그인에서 이메일을 제공하지 않았습니다.");
}
Optional<User> existingUser = userRepository.findByEmail(email);
User user;
if (existingUser.isPresent()) {
user = existingUser.get();
} else {
user = User.builder()
.email(email)
.password("OAUTH_USER")
.role(UserRole.USER)
.build();
userRepository.save(user);
}
String accessToken = jwtUtil.generateAccessToken(user.getEmail(), user.getId());
String refreshToken = jwtUtil.generateRefreshToken(user.getId());
// TODO : Refresh Token이 유출되었을 때 무효화할 방법이 없기 떄문에, Refresh Token을 DB에 저장 -> 구글링으로 좀 더 공부해야할듯.
//user.updateRefreshToken(refreshToken); -> 나는 refresh토큰 엔티티를 따로 빼놓고 jpa로 관리하기 때문에 이렇게 말고 아래와 같이 해줌.
refreshTokenRepository.findByUserId(user.getId())
.ifPresent(token -> refreshTokenRepository.deleteByUserId(token.getUserId()));
/*
.isPresent(refreshTokenRepository::delete); 이렇게 받으려고 햇는데,
에러가 발생 -> jpa리포지토리를 자세히보면 알 수 있음.
delete 메서드는 JpaRepository에서 기본 제공하는 메서드인데, Optional<RefreshToken> 타입을 직접 인자로 받을 수 없기 때문임.
하지만, JpaRepository.delete()는 객체(엔티티) 타입을 직접 받아야 하니까, delte()가 Optional 타입을 받을수가 없어서 오류가 발생하는 것.
그래서 그냥 리프레시토큰 리포지토리에 있는 메서드를 활용해서 get()을 사용하고 객체를 직접 꺼내서 직접 삭제하면 됨.
추가적으로, findByUserId()가 Optional.empty()를 반환하면 delete()가 실행되지 않지만, ifPresent()를 사용하면 NPE 걱정 없이 안전하게 됨.
*/
refreshTokenRepository.save(new RefreshToken(user.getId(), refreshToken));
response.sendRedirect("/users/oauth2/success?accessToken=" + accessToken + "&refreshToken=" + refreshToken);
}
}
spring.application.name=foodduck
spring.datasource.url=jdbc:mysql://localhost:3306/foodduck
spring.datasource.username=root
spring.datasource.password=${spring.datasource.password}
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.jpa.hibernate.ddl-auto=create
spring.jpa.properties.hibernate.show_sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.use_sql_comments=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQLDialect
jwt.secret=${jwt.secret}
jwt.expirationMs=86400000
jwt.accessExpirationMs=300000
jwt.refreshExpirationMs=604800000
# Google
# http://localhost:8080/login/oauth2/google
spring.security.oauth2.client.registration.google.scope=profile,email
spring.security.oauth2.client.registration.google.redirect-uri=http://localhost:8080/login/oauth2/callback/google
# Kakao
# http://localhost:8080/login/oauth2/kakao
#spring.security.oauth2.client.registration.kakao.provider=kakao
#spring.security.oauth2.client.provider.kakao.authorization-uri=https://kauth.kakao.com/oauth/authorize
#spring.security.oauth2.client.provider.kakao.token-uri=https://kauth.kakao.com/oauth/token
#spring.security.oauth2.client.provider.kakao.user-info-uri=https://kapi.kakao.com/v2/user/me
#spring.security.oauth2.client.provider.kakao.user-name-attribute=id
#spring.security.oauth2.client.registration.kakao.client-authentication-method=POST
#spring.security.oauth2.client.registration.kakao.authorization-grant-type=authorization_code
#spring.security.oauth2.client.registration.kakao.redirect-uri=http://localhost:8080/login/oauth2/callback/kakao
#spring.security.oauth2.client.registration.kakao.scope=profile_nickname,account_email
# Naver
# http://localhost:8080/login/oauth2/naver
#spring.security.oauth2.client.registration.naver.provider=naver
#spring.security.oauth2.client.registration.naver.redirect-uri=http://localhost:8080/login/oauth2/callback/naver
#spring.security.oauth2.client.registration.naver.authorization-grant-type=authorization_code
#spring.security.oauth2.client.registration.naver.scope=name,email
#spring.security.oauth2.client.provider.naver.authorization-uri=https://nid.naver.com/oauth2.0/authorize
#spring.security.oauth2.client.provider.naver.token-uri=https://nid.naver.com/oauth2.0/token
#spring.security.oauth2.client.provider.naver.user-info-uri=https://openapi.naver.com/v1/nid/me
#spring.security.oauth2.client.provider.naver.user-name-attribute=response
#spring.security.oauth2.client.registration.naver.client-authentication-method=POST