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
@@ -0,0 +1,62 @@
package com.lgcns.Docking.planet.controller;

import com.lgcns.Docking.planet.dto.PlanetListResponseDto;
import com.lgcns.Docking.planet.dto.PlanetRequestDto;
import com.lgcns.Docking.planet.dto.PlanetResponseDto;
import com.lgcns.Docking.planet.entity.Letter;
import com.lgcns.Docking.planet.service.LetterService;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@RestController
@RequestMapping("/planet")
@RequiredArgsConstructor
public class PlanetController {

private final LetterService letterService;

// 1. 행성 선택 (편지에 행성 이미지 URL 저장)
@PutMapping("/{letterId}")
public ResponseEntity<PlanetResponseDto> choosePlanet(
@PathVariable Long letterId,
@RequestBody PlanetRequestDto request
) {
Letter updated = letterService.savePlanetToLetter(letterId, request.getPlanetUrl());
return ResponseEntity.ok(new PlanetResponseDto(
updated.getId(),
updated.getPlanet(),
"성공입니다"
));
}

@GetMapping("/main")
public ResponseEntity<Map<String, Object>> getPlanetList(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "15") int size
) {
Page<PlanetListResponseDto> resultPage = letterService.getPlanetList(page, size);

Map<String, Object> response = new HashMap<>();
response.put("isSuccess", true);
response.put("code", "COMMON200");
response.put("message", "성공입니다.");

Map<String, Object> result = new HashMap<>();
result.put("content", resultPage.getContent());
result.put("page", resultPage.getNumber());
result.put("size", resultPage.getSize());
result.put("totalPages", resultPage.getTotalPages());
result.put("totalElements", resultPage.getTotalElements());

response.put("result", result);

return ResponseEntity.ok(response);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.lgcns.Docking.planet.dto;

import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public class PlanetListResponseDto {
private String planetUrl;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.lgcns.Docking.planet.dto;

import lombok.Getter;

@Getter
public class PlanetRequestDto {
private String planetUrl;
}
12 changes: 12 additions & 0 deletions src/main/java/com/lgcns/Docking/planet/dto/PlanetResponseDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.lgcns.Docking.planet.dto;

import lombok.AllArgsConstructor;
import lombok.Getter;

@AllArgsConstructor
@Getter
public class PlanetResponseDto {
private Long letterId;
private String planetUrl;
private String message;
}
35 changes: 35 additions & 0 deletions src/main/java/com/lgcns/Docking/planet/entity/Letter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.lgcns.Docking.planet.entity;

import com.lgcns.Docking.user.entity.User;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Entity
@Table(name = "letter")
@Getter
@Setter
@NoArgsConstructor
public class Letter {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(nullable = false)
private String title;

@Column(nullable = false)
private String description;

@Column
private String planet;

@Column(nullable = false)
private String sticker;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id", nullable = false)
private User user;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.lgcns.Docking.planet.repository;

import com.lgcns.Docking.planet.entity.Letter;
import com.lgcns.Docking.user.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;

public interface LetterRepository extends JpaRepository<Letter, Long> {
boolean existsByUser(User user);
}

28 changes: 28 additions & 0 deletions src/main/java/com/lgcns/Docking/planet/service/LetterService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.lgcns.Docking.planet.service;

import com.lgcns.Docking.planet.dto.PlanetListResponseDto;
import com.lgcns.Docking.planet.entity.Letter;
import com.lgcns.Docking.planet.repository.LetterRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class LetterService {

private final LetterRepository letterRepository;

public Letter savePlanetToLetter(Long letterId, String planetUrl) {
Letter letter = letterRepository.findById(letterId)
.orElseThrow(() -> new IllegalArgumentException("편지를 찾을 수 없습니다."));
letter.setPlanet(planetUrl);
return letterRepository.save(letter);
}

public Page<PlanetListResponseDto> getPlanetList(int page, int size) {
Page<Letter> letters = letterRepository.findAll(PageRequest.of(page, size));
return letters.map(letter -> new PlanetListResponseDto(letter.getPlanet()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
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.oauth2.client.web.OAuth2LoginAuthenticationFilter;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
Expand Down Expand Up @@ -57,18 +58,28 @@ public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
// 경로별 인가 설정
http.authorizeHttpRequests(auth -> auth
.requestMatchers("/").permitAll()
.requestMatchers("/news/post", "/admin/users/news/","/api/comment/").authenticated()
.requestMatchers("/swagger-ui","/news/**","/api/comment/**","/auth/logout").permitAll()
.requestMatchers("/planet/**").authenticated()
.requestMatchers("/oauth2/**", "/login/**").permitAll()
.requestMatchers("/images/**", "/css/**", "/js/**", "/favicon.ico").permitAll()


.anyRequest().authenticated()
);

// 로그인 및 인증 방식 비활성화
http.formLogin(form -> form.disable());
http.httpBasic(basic -> basic.disable());

/*
// JWT 필터 추가
http.addFilterAfter(new JWTFilter(jwtUtil), OAuth2LoginAuthenticationFilter.class);
*/


// JWT 필터 추가
http.addFilterBefore(new JWTFilter(jwtUtil), UsernamePasswordAuthenticationFilter.class);


// OAuth2 로그인 설정
http.oauth2Login(oauth2 -> oauth2
.userInfoEndpoint(userInfo -> userInfo.userService(customOAuth2UserService))
Expand All @@ -79,7 +90,7 @@ public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
http.exceptionHandling(exceptionHandling -> exceptionHandling
.authenticationEntryPoint((request, response, authException) -> {
// 로그인되지 않은 사용자는 네이버 로그인 페이지로 이동
response.sendRedirect("oauth2/authorization/kakao");
response.sendRedirect("/oauth2/authorization/kakao");
})
.accessDeniedHandler((request, response, accessDeniedException) -> {
// 로그인은 했지만 ADMIN 권한이 없는 경우 403 응답
Expand Down
70 changes: 26 additions & 44 deletions src/main/java/com/lgcns/Docking/security/jwt/JWTFilter.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,17 @@

import com.lgcns.Docking.user.dto.CustomOAuth2User;
import com.lgcns.Docking.user.dto.UserDTO;

import jakarta.servlet.*;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;

@Slf4j
public class JWTFilter extends OncePerRequestFilter {

Expand All @@ -25,63 +23,47 @@ public JWTFilter(JWTUtil jwtUtil) {
}

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

//cookie들을 불러온 뒤 Authorization Key에 담긴 쿠키를 찾음

// 헤더 검증
String authorization = null;
Cookie[] cookies = request.getCookies();
for (Cookie cookie : cookies) {

System.out.println(cookie.getName());
if (cookie.getName().equals("Authorization")) {

authorization = cookie.getValue();
}
}
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {

//Authorization 헤더 검증
if (authorization == null) {
String authHeader = request.getHeader("Authorization");

System.out.println("token null");
// JWT가 헤더에 없거나 Bearer로 시작하지 않으면 다음 필터로 넘김
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
log.debug("Authorization header missing or malformed");
filterChain.doFilter(request, response);

//조건이 해당되면 메소드 종료
return;
}

//토큰
String token = authorization;
// Bearer 제거하고 순수 토큰 추출
String token = authHeader.substring(7);

//토큰 소멸 시간 검증
// 만료된 토큰
if (jwtUtil.isExpired(token)) {

System.out.println("token expired");
filterChain.doFilter(request, response);

//조건이 해당되면 메소드 종료 (필수)
log.debug("Token expired");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
response.getWriter().write("{\"error\": \"토큰이 만료되었습니다.\"}");
return;
}

//토큰에서 username과 role 획득
String password = jwtUtil.getUsername(token);
// 토큰에서 사용자 정보 추출
String username = jwtUtil.getUsername(token);
String id = jwtUtil.getId(token);

//userDTO를 생성하여 값 set
UserDTO userDTO = new UserDTO();
userDTO.setUsername(password);
userDTO.setUsername(username);
userDTO.setId(id);

//UserDetails에 회원 정보 객체 담기
CustomOAuth2User customOAuth2User = new CustomOAuth2User(userDTO);

//스프링 시큐리티 인증 토큰 생성
Authentication authToken = new UsernamePasswordAuthenticationToken(customOAuth2User, null, customOAuth2User.getAuthorities());
CustomOAuth2User customUser = new CustomOAuth2User(userDTO);
Authentication authToken = new UsernamePasswordAuthenticationToken(
customUser, null, customUser.getAuthorities());

//세션에 사용자 등록
SecurityContextHolder.getContext().setAuthentication(authToken);
logger.info("jwtFilter success");
log.info("JWTFilter 인증 완료: {}", username);

filterChain.doFilter(request, response);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package com.lgcns.Docking.security.oauth2;

import com.lgcns.Docking.planet.repository.LetterRepository;
import com.lgcns.Docking.security.jwt.JWTUtil;
import com.lgcns.Docking.user.dto.CustomOAuth2User;
import com.lgcns.Docking.user.entity.User;
import com.lgcns.Docking.user.repository.UserRepository;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
Expand All @@ -19,9 +22,15 @@
public class CustomSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {

private final JWTUtil jwtUtil;
private final LetterRepository letterRepository;
private final UserRepository userRepository;

public CustomSuccessHandler(JWTUtil jwtUtil) {

public CustomSuccessHandler(JWTUtil jwtUtil, LetterRepository letterRepository, UserRepository userRepository) {
this.jwtUtil = jwtUtil;
this.letterRepository = letterRepository;
this.userRepository = userRepository;

}

@Override
Expand All @@ -47,8 +56,16 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo

System.out.println(token);

// 유저 조회
User user = userRepository.findById(Long.valueOf(id))
.orElseThrow(() -> new RuntimeException("유저를 찾을 수 없습니다."));

boolean hasSubmitted = letterRepository.existsByUser(user);

// 쿠키 방식 전달 + 프론트에 리다이렉트
response.addCookie(createCookie("Authorization", token));
response.addCookie(createCookie("hasSubmittedLetter", String.valueOf(hasSubmitted)));

response.sendRedirect("http://localhost/");
}

Expand Down
2 changes: 1 addition & 1 deletion src/main/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect
spring.jpa.properties.hibernate.show_sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.use_sql_comments=true
spring.jpa.hibernate.ddl-auto=create
spring.jpa.hibernate.ddl-auto=update
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
spring.jpa.properties.hibernate.default_batch_fetch_size=1000

Expand Down
Binary file added src/main/resources/static/images/img.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.