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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

@RestController
@RequiredArgsConstructor
@RequestMapping("admin/exhibits")
@RequestMapping("/admin/exhibits")
@Slf4j
@Tag(name = "Admin - Exhibit", description = "관리자 전시 관리 API")
public class AdminExhibitController {
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/org/atdev/artrip/domain/auth/data/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ public class User {
@Column(name = "push_token")
private String pushToken;


@Column(nullable = false)
private boolean onboardingCompleted=false;

@Email
@Column(name = "email",nullable = true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;

@Slf4j
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {

Expand All @@ -26,6 +28,8 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse

if (token != null && jwtProvider.validateToken(token)) {
Authentication authentication = jwtProvider.getAuthentication(token);
log.info("Authentication principal: {}", authentication.getPrincipal());
log.info("Authorities: {}", authentication.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authentication);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public JwtToken generateToken(User user, Role roles) {

long now = (new Date()).getTime();

String authorities = roles.name();
String authorities = "ROLE_" + roles.name();

String accessToken = Jwts.builder()
.setIssuer(jwtIssuer)
Expand All @@ -60,6 +60,8 @@ public JwtToken generateToken(User user, Role roles) {
public String createAccessToken(String subject, String roles) {// refresh 재발행때 사용
long now = System.currentTimeMillis();

// String authorities = "Role_" + roles;

return Jwts.builder()
.setIssuer(jwtIssuer)
.setSubject(subject)
Expand Down
37 changes: 25 additions & 12 deletions src/main/java/org/atdev/artrip/domain/auth/service/AuthService.java
Original file line number Diff line number Diff line change
Expand Up @@ -159,18 +159,22 @@ public SocialLoginResponse loginWithSocial(String provider, String idToken) {
? socialUser.getEmail()
: provider.toLowerCase() + socialUser.getProviderId() + "@example.com";

Optional<User> optionalUser = userRepository.findByEmail(email);
User user;
boolean isFirstLogin;

if (optionalUser.isPresent()) {
user = optionalUser.get();
isFirstLogin = false; // 기존 사용자
} else {
user = createNewUser(socialUser, email, provider);
isFirstLogin = true; // 신규 생성
}
log.info("user:{}",user);
// Optional<User> optionalUser = userRepository.findByEmail(email);

User user = userRepository.findByEmail(email).
orElseGet(()->createNewUser(socialUser, email, provider));


boolean isFirstLogin= !user.isOnboardingCompleted();

// if (optionalUser.isPresent()) {
// user = optionalUser.get();
// isFirstLogin = false; // 기존 사용자
// } else {
// user = createNewUser(socialUser, email, provider);
// isFirstLogin = true; // 신규 생성
// }
// log.info("user:{}",user);

JwtToken jwt = jwtGenerator.generateToken(user, user.getRole());

Expand All @@ -187,6 +191,14 @@ public SocialLoginResponse loginWithSocial(String provider, String idToken) {
);
}

@Transactional
public void completeOnboarding(Long userId) {
User user = userRepository.findById(userId)
.orElseThrow();

user.setOnboardingCompleted(!user.isOnboardingCompleted());
}

private User createNewUser(SocialUserInfo info, String email, String providerStr) {

Provider provider = switch (providerStr.toUpperCase()) {
Expand All @@ -199,6 +211,7 @@ private User createNewUser(SocialUserInfo info, String email, String providerStr
.email(email)
.name(info.getNickname())
.role(Role.USER)
.onboardingCompleted(false)
.build();

SocialAccounts social = SocialAccounts.builder()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import jakarta.annotation.security.PermitAll;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.atdev.artrip.domain.auth.jwt.JwtToken;
Expand All @@ -18,6 +19,8 @@
import org.atdev.artrip.global.apipayload.code.status.UserError;
import org.atdev.artrip.global.swagger.ApiErrorResponses;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.bind.annotation.*;

@RestController
Expand All @@ -27,7 +30,7 @@ public class AuthController {

private final AuthService authService;


@PermitAll
@Operation(summary = "토큰 재발행 (웹 전용)", description = "refresh토큰으로 access토큰을 재발행합니다")
@ApiErrorResponses(
user = {UserError._USER_NOT_FOUND, UserError._INVALID_REFRESH_TOKEN, UserError._INVALID_USER_REFRESH_TOKEN},
Expand All @@ -43,6 +46,7 @@ public ResponseEntity<CommonResponse<String>> webReissue(
return ResponseEntity.ok(CommonResponse.onSuccess(newAccessToken));
}

@PermitAll
@Operation(summary = "토큰 재발행 (앱 전용)", description = "refresh토큰으로 access토큰을 재발행합니다")
@ApiErrorResponses(
user = {
Expand All @@ -61,6 +65,7 @@ public ResponseEntity<CommonResponse<SocialLoginResponse>> appReissue(@RequestBo
return ResponseEntity.ok(CommonResponse.onSuccess(jwt));
}

@PermitAll
@Operation(summary = "로그아웃 (웹 전용)", description = "refresh, access 토큰을 제거합니다.")
@ApiErrorResponses(
user = {UserError._INVALID_REFRESH_TOKEN},
Expand All @@ -75,6 +80,7 @@ public ResponseEntity<String> webLogout(@CookieValue(value = "refreshToken", req
return ResponseEntity.ok("로그아웃 완료");
}

@PermitAll
@Operation(summary = "로그아웃 (앱 전용)", description = "refresh, access 토큰을 제거합니다.")
@ApiErrorResponses(
user = {UserError._INVALID_REFRESH_TOKEN},
Expand All @@ -88,6 +94,7 @@ public ResponseEntity<String> appLogout(@RequestBody(required = false) ReissueRe
return ResponseEntity.ok("로그아웃 완료");
}

@PermitAll
@Operation(summary = "소셜 SDK 토큰 검증 후 jwt 발급", description = "만료일 : refresh: 7일 , access: 15분 ,isFirstLogin true:회원가입 false:로그인")
@ApiErrorResponses(
user = {
Expand All @@ -108,4 +115,17 @@ public ResponseEntity<CommonResponse<SocialLoginResponse>> socialLogin(@RequestB
return ResponseEntity.ok(CommonResponse.onSuccess(jwt));
}

@Operation(summary = "isFirstLogin값 반전 api")
@PostMapping("/complete")
public ResponseEntity<String> completeOnboarding(
@AuthenticationPrincipal UserDetails userDetails) {

long userId = Long.parseLong(userDetails.getUsername());

authService.completeOnboarding(userId);

return ResponseEntity.noContent().build();
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import org.atdev.artrip.global.apipayload.code.status.UserError;
import org.atdev.artrip.global.apipayload.exception.GeneralException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.List;
Expand All @@ -25,6 +26,7 @@ public class KeywordService {
private final UserKeywordRepository userKeywordRepository;
private final UserRepository userRepository;

@Transactional
public void saveUserKeywords(Long userId, List<Long> keywordIds) {

User user = userRepository.findById(userId)
Expand All @@ -45,13 +47,15 @@ public void saveUserKeywords(Long userId, List<Long> keywordIds) {
userKeywordRepository.saveAll(userKeywords);
}

@Transactional
public List<KeywordResponse> getAllKeywords() {
return keywordRepository.findAll()
.stream()
.map(k -> new KeywordResponse(k.getKeywordId(), k.getName(), k.getType()))
.collect(Collectors.toList());
}

@Transactional
public List<KeywordResponse> getUserKeywords(Long userId) {
return userKeywordRepository.findAllByUserUserId(userId) // UserKeyword 테이블에서 조회
.stream()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public enum FavoriteError implements BaseErrorCode {

_FAVORITE_NOT_FOUND(HttpStatus.NOT_FOUND, "FAVORITE404-NOT_FOUND", "즐겨찾기를 찾을 수 없습니다."),
_FAVORITE_ALREADY_EXISTS(HttpStatus.CONFLICT, "FAVORITE409-ALREADY_EXISTS", "이미 즐겨찾기에 추가된 전시입니다."),
_FAVORITE_LIMIT_EXCEEDED(HttpStatus.BAD_REQUEST, "FAVORITE4002-LIMIT_EXCEEDED", "즐겨찾기는 최대 100개까지 추가할 수 있습니다."),
_FAVORITE_LIMIT_EXCEEDED(HttpStatus.BAD_REQUEST, "FAVORITE400-LIMIT_EXCEEDED", "즐겨찾기는 최대 100개까지 추가할 수 있습니다."),
_EXHIBIT_NOT_FOUND_FOR_FAVORITE(HttpStatus.NOT_FOUND, "FAVORITE404-EXHIBIT_NOT_FOUND", "즐겨찾기하려는 전시를 찾을 수 없습니다."),
_FAVORITE_UNAUTHORIZED(HttpStatus.FORBIDDEN, "FAVORITE403-UNAUTHORIZED", "다른 사용자의 즐겨찾기에 접근할 수 없습니다.");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@
@AllArgsConstructor
public enum HomeError implements BaseErrorCode {

_HOME_INVALID_DATE_RANGE(HttpStatus.BAD_REQUEST, "HOME401-INVALID_DATE_RANGE", "전시 기간 설정이 올바르지 않습니다. 종료일이 시작일보다 빠릅니다."),
_HOME_UNRECOGNIZED_REGION(HttpStatus.BAD_REQUEST, "HOME402-UNRECOGNIZED_REGION", "요청하신 국가 또는 지역 정보를 인식할 수 없습니다."),
_HOME_EXHIBIT_NOT_FOUND(HttpStatus.BAD_REQUEST, "HOME404-EXHIBIT_NOT_FOUND", "해당 ID의 전시 상세 정보를 찾을 수 없습니다."),
_HOME_GENRE_NOT_FOUND(HttpStatus.BAD_REQUEST, "HOME404-GENRE_NOT_FOUND", "요청하신 장르에 해당하는 전시 데이터가 없습니다.");
_HOME_INVALID_DATE_RANGE(HttpStatus.BAD_REQUEST, "HOME400-INVALID_DATE_RANGE", "전시 기간 설정이 올바르지 않습니다. 종료일이 시작일보다 빠릅니다."),
_HOME_UNRECOGNIZED_REGION(HttpStatus.BAD_REQUEST, "HOME400-UNRECOGNIZED_REGION", "요청하신 국가 또는 지역 정보를 인식할 수 없습니다."),
_HOME_EXHIBIT_NOT_FOUND(HttpStatus.NOT_FOUND, "HOME404-EXHIBIT_NOT_FOUND", "해당 ID의 전시 상세 정보를 찾을 수 없습니다."),
_HOME_GENRE_NOT_FOUND(HttpStatus.NOT_FOUND, "HOME404-GENRE_NOT_FOUND", "요청하신 장르에 해당하는 전시 데이터가 없습니다.");

private final HttpStatus httpStatus;
private final String code;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
public enum KeywordError implements BaseErrorCode {

_KEYWORD_NOT_FOUND(HttpStatus.NOT_FOUND, "KEYWORD404-NOT_FOUND", "존재하지 않거나 유효하지 않은 키워드 ID가 요청되었습니다."),
_KEYWORD_SELECTION_LIMIT_EXCEEDED(HttpStatus.BAD_REQUEST, "KEYWORD401-SELECTION_LIMIT_EXCEEDED", "키워드 선택 최대 개수를 초과했습니다."),
_KEYWORD_INVALID_REQUEST(HttpStatus.BAD_REQUEST, "KEYWORD402-INVALID_REQUEST", "키워드 요청 데이터가 올바르지 않습니다.");
_KEYWORD_SELECTION_LIMIT_EXCEEDED(HttpStatus.BAD_REQUEST, "KEYWORD400-SELECTION_LIMIT_EXCEEDED", "키워드 선택 최대 개수를 초과했습니다."),
_KEYWORD_INVALID_REQUEST(HttpStatus.BAD_REQUEST, "KEYWORD400-INVALID_REQUEST", "키워드 요청 데이터가 올바르지 않습니다.");

private final HttpStatus httpStatus;
private final String code;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
public enum ReviewError implements BaseErrorCode {

_REVIEW_NOT_FOUND(HttpStatus.NOT_FOUND, "REVIEW404-NOT_FOUND", "리뷰 정보를 찾을 수 없습니다."),
_REVIEW_USER_NOT_FOUND(HttpStatus.NOT_FOUND, "REVIEW403-NO_PERMISSION", "해당 유저에게 리뷰 수정권한이 없습니다.");
_REVIEW_USER_NOT_FOUND(HttpStatus.FORBIDDEN, "REVIEW403-NO_PERMISSION", "해당 유저에게 리뷰 수정권한이 없습니다.");

private final HttpStatus httpStatus;
private final String code;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ public enum UserError implements BaseErrorCode {

// User Errors
_USER_NOT_FOUND(HttpStatus.NOT_FOUND, "USER404-NOT_FOUND", "존재하지 않는 회원입니다."),
_USER_FORBIDDEN(HttpStatus.FORBIDDEN, "USER403-FORBIDDEN", "접근 권한이 없습니다."),

// JWT Errors
_JWT_EXPIRED_ACCESS_TOKEN(HttpStatus.UNAUTHORIZED, "JWT401-EXPIRED_ACCESS", "만료된 엑세스 토큰입니다."),
Expand All @@ -22,6 +23,7 @@ public enum UserError implements BaseErrorCode {
_JWT_EXPIRED_REFRESH_TOKEN(HttpStatus.UNAUTHORIZED, "JWT401-EXPIRED_REFRESH", "만료된 리프레시 토큰입니다."),
_INVALID_REFRESH_TOKEN(HttpStatus.UNAUTHORIZED, "JWT401-INVALID_REFRESH", "리프레시 토큰이 유효하지 않습니다."),
_INVALID_USER_REFRESH_TOKEN(HttpStatus.UNAUTHORIZED, "JWT401-INVALID_USER_REFRESH", "리프레시 토큰에 유저ID가 유효하지 않습니다."),

// Social Login Errors
_SOCIAL_VERIFICATION_FAILED(HttpStatus.UNAUTHORIZED, "SOCIAL401-VERIFICATION_FAILED", "소셜 토큰 검증 중 오류가 발생했습니다."),
_SOCIAL_ID_TOKEN_INVALID(HttpStatus.UNAUTHORIZED, "IDTOKEN401-INVALID", "소셜 ID 토큰이 유효하지 않습니다."),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package org.atdev.artrip.global.apipayload.exception.handler;


import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.atdev.artrip.global.apipayload.code.status.UserError;
import org.atdev.artrip.global.apipayload.code.ErrorReasonDTO;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;

import java.io.IOException;

@Component
@RequiredArgsConstructor
public class JwtAccessDeniedHandler implements AccessDeniedHandler {

private final ObjectMapper objectMapper;

@Override
public void handle(
HttpServletRequest request,
HttpServletResponse response,
AccessDeniedException accessDeniedException
) throws IOException {

UserError errorCode = UserError._USER_FORBIDDEN;

response.setStatus(HttpServletResponse.SC_FORBIDDEN);
response.setContentType("application/json;charset=UTF-8");

ErrorReasonDTO errorResponse = errorCode.getReasonHttpStatus();

response.getWriter().write(
objectMapper.writeValueAsString(errorResponse)
);
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package org.atdev.artrip.global.apipayload.exception.handler;

import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.atdev.artrip.global.apipayload.code.status.UserError;
import org.atdev.artrip.global.apipayload.code.ErrorReasonDTO;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;

import java.io.IOException;

@Component
@RequiredArgsConstructor
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {

private final ObjectMapper objectMapper;

@Override
public void commence(
HttpServletRequest request,
HttpServletResponse response,
AuthenticationException authException
) throws IOException {

UserError errorCode = UserError._JWT_INVALID_TOKEN;

response.setStatus(errorCode.getHttpStatus().value());
response.setContentType("application/json;charset=UTF-8");

ErrorReasonDTO errorResponse = errorCode.getReasonHttpStatus();

response.getWriter().write(
objectMapper.writeValueAsString(errorResponse)
);
}
}
Loading
Loading