-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Feat & Refactor : security, jwt 설정 추가 및 카카오 로그인 진행중 / member 엔티티 수정 /…
… config 파일 구조변경
- Loading branch information
Showing
49 changed files
with
1,905 additions
and
607 deletions.
There are no files selected for viewing
79 changes: 79 additions & 0 deletions
79
src/main/java/umc/kkijuk/server/auth/controller/KakaoAuthController.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
package umc.kkijuk.server.auth.controller; | ||
|
||
import lombok.RequiredArgsConstructor; | ||
import lombok.extern.slf4j.Slf4j; | ||
import org.springframework.http.ResponseEntity; | ||
import org.springframework.web.bind.annotation.*; | ||
import umc.kkijuk.server.auth.jwt.JwtUtil; | ||
import umc.kkijuk.server.auth.service.KakaoAuthService; | ||
import umc.kkijuk.server.member.domain.Member; | ||
import umc.kkijuk.server.member.service.MemberService; | ||
|
||
import java.time.LocalDate; | ||
import java.util.HashMap; | ||
import java.util.Map; | ||
|
||
@RestController | ||
@RequestMapping("/auth/kakao") | ||
@RequiredArgsConstructor | ||
@Slf4j | ||
public class KakaoAuthController { | ||
|
||
private final KakaoAuthService kakaoAuthService; | ||
private final MemberService memberService; | ||
private final JwtUtil jwtUtil; | ||
|
||
@PostMapping("/login") | ||
public ResponseEntity<Map<String, Object>> loginWithKakao(@RequestHeader("Authorization") String kakaoAccessToken) { | ||
try { | ||
// Bearer 제거 | ||
if (kakaoAccessToken.toLowerCase().startsWith("bearer ")) { | ||
kakaoAccessToken = kakaoAccessToken.substring(7).trim(); | ||
} | ||
|
||
// 카카오 사용자 정보 가져오기 | ||
Map<String, Object> kakaoUserInfo = kakaoAuthService.getKakaoUserInfo(kakaoAccessToken); | ||
log.info("카카오 사용자 정보: {}", kakaoUserInfo); | ||
|
||
// 사용자 정보 추출 | ||
String email = kakaoAuthService.extractEmail(kakaoUserInfo); | ||
String name = kakaoAuthService.extractName(kakaoUserInfo); | ||
String phoneNumber = kakaoAuthService.extractPhoneNumber(kakaoUserInfo); | ||
LocalDate birthDate = kakaoAuthService.extractBirthDate(kakaoUserInfo); | ||
|
||
// // 이메일 검증 | ||
// if (email == null || email.isEmpty()) { | ||
// log.error("카카오 사용자 정보에 이메일이 없습니다."); | ||
// return ResponseEntity.badRequest().body(Map.of("error", "카카오 사용자 정보에 이메일이 없습니다.")); | ||
// } | ||
|
||
// 핸드폰 번호 검증 | ||
if (phoneNumber == null || phoneNumber.isEmpty()) { | ||
log.error("카카오 사용자 정보에 핸드폰 번호가 없습니다."); | ||
return ResponseEntity.badRequest().body(Map.of("error", "카카오 사용자 정보에 핸드폰 번호가 없습니다.")); | ||
} | ||
|
||
Member member = memberService.findByPhoneNumber(phoneNumber); | ||
if (member == null) { | ||
log.info("신규 사용자 생성 - 핸드폰 번호: {}", phoneNumber); | ||
member = memberService.createMember(email, name, phoneNumber, birthDate); | ||
} | ||
|
||
// JWT 토큰 생성 | ||
String accessToken = jwtUtil.createAccessToken(member.getEmail()); | ||
String refreshToken = jwtUtil.createRefreshToken(member.getEmail()); | ||
|
||
// 응답 데이터 생성 | ||
Map<String, Object> response = new HashMap<>(); | ||
response.put("accessToken", accessToken); | ||
response.put("refreshToken", refreshToken); | ||
|
||
log.info("로그인 성공: 이메일={}, accessToken 생성 완료", email); | ||
return ResponseEntity.ok(response); | ||
|
||
} catch (Exception e) { | ||
log.error("카카오 로그인 처리 중 오류 발생: {}", e.getMessage(), e); | ||
return ResponseEntity.internalServerError().body(Map.of("error", "로그인 처리 실패")); | ||
} | ||
} | ||
} |
16 changes: 16 additions & 0 deletions
16
src/main/java/umc/kkijuk/server/auth/dto/AuthResponse.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package umc.kkijuk.server.auth.dto; | ||
|
||
import lombok.Getter; | ||
import lombok.Setter; | ||
|
||
@Getter | ||
@Setter | ||
public class AuthResponse { | ||
private String accessToken; | ||
private String refreshToken; | ||
|
||
public AuthResponse(String accessToken, String refreshToken) { | ||
this.accessToken = accessToken; | ||
this.refreshToken = refreshToken; | ||
} | ||
} |
38 changes: 38 additions & 0 deletions
38
src/main/java/umc/kkijuk/server/auth/dto/CustomOAuth2User.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package umc.kkijuk.server.auth.dto; | ||
|
||
import java.util.Collection; | ||
import java.util.Map; | ||
|
||
import org.springframework.security.core.GrantedAuthority; | ||
import org.springframework.security.oauth2.core.user.OAuth2User; | ||
|
||
public class CustomOAuth2User implements OAuth2User { | ||
|
||
private final Collection<? extends GrantedAuthority> authorities; | ||
private final Map<String, Object> attributes; | ||
private final String nameAttributeKey; | ||
|
||
public CustomOAuth2User( | ||
Collection<? extends GrantedAuthority> authorities, | ||
Map<String, Object> attributes, | ||
String nameAttributeKey) { | ||
this.authorities = authorities; | ||
this.attributes = attributes; | ||
this.nameAttributeKey = nameAttributeKey; | ||
} | ||
|
||
@Override | ||
public Map<String, Object> getAttributes() { | ||
return attributes; | ||
} | ||
|
||
@Override | ||
public Collection<? extends GrantedAuthority> getAuthorities() { | ||
return authorities; | ||
} | ||
|
||
@Override | ||
public String getName() { | ||
return (String) attributes.get(nameAttributeKey); | ||
} | ||
} |
38 changes: 38 additions & 0 deletions
38
src/main/java/umc/kkijuk/server/auth/dto/KakaoResponse.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package umc.kkijuk.server.auth.dto; | ||
|
||
import java.util.Map; | ||
|
||
public class KakaoResponse implements OAuth2Response { | ||
|
||
private final Map<String, Object> attributes; // 전체 응답 데이터 | ||
private final Map<String, Object> kakaoAccountAttributes; // 카카오 계정 데이터 | ||
private final Map<String, Object> profileAttributes; // 프로필 데이터 | ||
|
||
// 생성자 | ||
public KakaoResponse(Map<String, Object> attributes) { | ||
this.attributes = attributes; | ||
this.kakaoAccountAttributes = (Map<String, Object>) attributes.get("kakao_account"); | ||
this.profileAttributes = | ||
kakaoAccountAttributes != null | ||
? (Map<String, Object>) kakaoAccountAttributes.get("profile") | ||
: null; | ||
} | ||
|
||
@Override | ||
public String getProvider() { | ||
return "kakao"; // 고정 값 | ||
} | ||
|
||
@Override | ||
public String getProviderId() { | ||
return attributes.get("id").toString(); // 카카오 사용자 고유 ID | ||
} | ||
|
||
@Override | ||
public String getEmail() { | ||
return kakaoAccountAttributes != null && kakaoAccountAttributes.containsKey("email") | ||
? kakaoAccountAttributes.get("email").toString() | ||
: null; // 이메일이 없는 경우 null 반환 | ||
} | ||
|
||
} |
10 changes: 10 additions & 0 deletions
10
src/main/java/umc/kkijuk/server/auth/dto/OAuth2Response.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package umc.kkijuk.server.auth.dto; | ||
|
||
public interface OAuth2Response { | ||
String getProvider(); | ||
|
||
String getProviderId(); | ||
|
||
String getEmail(); | ||
|
||
} |
23 changes: 23 additions & 0 deletions
23
src/main/java/umc/kkijuk/server/auth/dto/OAuth2ResponseImpl.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package umc.kkijuk.server.auth.dto; | ||
|
||
import lombok.Getter; | ||
import umc.kkijuk.server.member.domain.Member; | ||
|
||
@Getter | ||
public class OAuth2ResponseImpl implements OAuth2Response { | ||
private final String provider = "kakao"; // Provider 고정 (예: Kakao) | ||
private final String providerId; | ||
private final String email; | ||
|
||
public OAuth2ResponseImpl(String providerId, String email) { | ||
this.providerId = providerId; | ||
this.email = email; | ||
} | ||
|
||
public static OAuth2ResponseImpl fromUser(Member member) { | ||
return new OAuth2ResponseImpl( | ||
member.getId().toString(), // 예시로 user ID를 providerId로 설정 | ||
member.getEmail() | ||
); | ||
} | ||
} |
11 changes: 11 additions & 0 deletions
11
src/main/java/umc/kkijuk/server/auth/dto/RefreshTokenRequest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package umc.kkijuk.server.auth.dto; | ||
|
||
import lombok.Getter; | ||
import lombok.Setter; | ||
|
||
@Getter | ||
@Setter | ||
public class RefreshTokenRequest { | ||
private String email; | ||
private String refreshToken; | ||
} |
38 changes: 38 additions & 0 deletions
38
src/main/java/umc/kkijuk/server/auth/handler/CustomSuccessHandler.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package umc.kkijuk.server.auth.handler; | ||
|
||
import java.io.IOException; | ||
|
||
import lombok.RequiredArgsConstructor; | ||
import lombok.extern.slf4j.Slf4j; | ||
|
||
import jakarta.servlet.http.HttpServletRequest; | ||
import jakarta.servlet.http.HttpServletResponse; | ||
|
||
import org.springframework.security.core.Authentication; | ||
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; | ||
import org.springframework.stereotype.Component; | ||
|
||
@Component | ||
@RequiredArgsConstructor | ||
@Slf4j | ||
public class CustomSuccessHandler extends SimpleUrlAuthenticationSuccessHandler { | ||
|
||
@Override | ||
public void onAuthenticationSuccess( | ||
HttpServletRequest request, HttpServletResponse response, Authentication authentication) | ||
throws IOException { | ||
// 기존 리다이렉트 동작 제거 | ||
clearAuthenticationAttributes(request); | ||
|
||
// JSON 응답 | ||
response.setContentType("application/json"); | ||
response.setCharacterEncoding("UTF-8"); | ||
|
||
// 인가코드 반환 (테스트용 코드, 프론트엔드에서 처리 가능) | ||
String authorizationCode = request.getParameter("code"); | ||
log.info("로그인 성공, 인가코드 반환: {}", authorizationCode); | ||
|
||
response.getWriter().write(String.format( | ||
"{\"authorizationCode\": \"%s\"}", authorizationCode)); | ||
} | ||
} |
34 changes: 34 additions & 0 deletions
34
src/main/java/umc/kkijuk/server/auth/jwt/JwtCustomFilter.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
package umc.kkijuk.server.auth.jwt; | ||
|
||
import java.io.IOException; | ||
|
||
import lombok.RequiredArgsConstructor; | ||
|
||
import jakarta.servlet.FilterChain; | ||
import jakarta.servlet.ServletException; | ||
import jakarta.servlet.http.HttpServletRequest; | ||
import jakarta.servlet.http.HttpServletResponse; | ||
|
||
import org.springframework.web.filter.OncePerRequestFilter; | ||
|
||
@RequiredArgsConstructor | ||
public class JwtCustomFilter extends OncePerRequestFilter { | ||
|
||
private final JwtFilter jwtFilter; | ||
|
||
@Override | ||
protected void doFilterInternal( | ||
HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) | ||
throws ServletException, IOException { | ||
String requestUri = request.getRequestURI(); | ||
|
||
// 특정 요청 URI에 대해 필터 제외 | ||
if (requestUri.matches("^\\/login(?:\\/.*)?$") || requestUri.matches("^\\/oauth2(?:\\/.*)?$")) { | ||
filterChain.doFilter(request, response); | ||
return; | ||
} | ||
|
||
// JWT 필터 실행 | ||
jwtFilter.doFilterInternal(request, response, filterChain); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
package umc.kkijuk.server.auth.jwt; | ||
|
||
import java.io.IOException; | ||
import java.util.Collections; | ||
|
||
import lombok.RequiredArgsConstructor; | ||
import lombok.extern.slf4j.Slf4j; | ||
|
||
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.authority.SimpleGrantedAuthority; | ||
import org.springframework.security.core.context.SecurityContextHolder; | ||
import org.springframework.security.core.userdetails.UserDetails; | ||
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; | ||
import org.springframework.stereotype.Component; | ||
import org.springframework.web.filter.OncePerRequestFilter; | ||
import umc.kkijuk.server.member.domain.Member; | ||
import umc.kkijuk.server.member.repository.MemberRepository; | ||
|
||
@Slf4j | ||
@Component | ||
@RequiredArgsConstructor | ||
public class JwtFilter extends OncePerRequestFilter { | ||
|
||
private final JwtUtil jwtUtil; | ||
private final MemberRepository memberRepository; | ||
|
||
@Override | ||
protected void doFilterInternal( | ||
HttpServletRequest request, HttpServletResponse response, FilterChain chain) | ||
throws ServletException, IOException { | ||
|
||
String requestUri = request.getRequestURI(); | ||
|
||
// 카카오 로그인 경로 제외 | ||
if (requestUri.startsWith("/api/auth/kakao/login")) { | ||
chain.doFilter(request, response); | ||
return; | ||
} | ||
|
||
try { | ||
final String authorizationHeader = request.getHeader("Authorization"); | ||
String email = null; | ||
String jwt = null; | ||
|
||
if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) { | ||
jwt = authorizationHeader.substring(7); | ||
email = jwtUtil.extractEmail(jwt); | ||
} | ||
|
||
if (email != null && SecurityContextHolder.getContext().getAuthentication() == null) { | ||
Member member = memberRepository.findByEmail(email).orElse(null); | ||
|
||
if (member != null && jwtUtil.validateToken(jwt, member.getEmail())) { | ||
UserDetails userDetails = | ||
new org.springframework.security.core.userdetails.User( | ||
member.getEmail(), | ||
"", | ||
Collections.singletonList(new SimpleGrantedAuthority(member.getRole().name()))); | ||
|
||
UsernamePasswordAuthenticationToken authentication = | ||
new UsernamePasswordAuthenticationToken( | ||
userDetails, null, userDetails.getAuthorities()); | ||
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); | ||
|
||
SecurityContextHolder.getContext().setAuthentication(authentication); | ||
} else { | ||
log.warn("Invalid JWT Token for member: {}", email); | ||
} | ||
} | ||
} catch (Exception e) { | ||
log.warn("JWT Authentication Error: {}", e.getMessage()); | ||
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized"); | ||
return; | ||
} | ||
|
||
chain.doFilter(request, response); | ||
} | ||
} |
Oops, something went wrong.