Skip to content

Commit

Permalink
Merge pull request #233 from haedoang/develop
Browse files Browse the repository at this point in the history
[feature/room-delete] 룸삭제 & 토큰 권한 구분 & 403 에러 핸들링
  • Loading branch information
haedoang authored Oct 12, 2023
2 parents 51c39e1 + 1d7851b commit aa15970
Show file tree
Hide file tree
Showing 17 changed files with 153 additions and 77 deletions.
15 changes: 6 additions & 9 deletions src/main/java/com/koliving/api/GlobalExceptionHandler.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package com.koliving.api;

import static com.koliving.api.base.ServiceError.*;
import static com.koliving.api.token.confirmation.ConfirmationTokenType.RESET_PASSWORD;
import static com.koliving.api.token.confirmation.ConfirmationTokenType.SIGN_UP;

import com.koliving.api.base.ErrorResponse;
import com.koliving.api.base.ServiceError;
import com.koliving.api.base.exception.KolivingServiceException;
import com.koliving.api.dto.ConfirmationTokenErrorDto;
import com.koliving.api.dto.ResponseDto;
Expand Down Expand Up @@ -133,16 +135,11 @@ public ResponseEntity<ResponseDto<ValidationResult>> handleMethodArgumentNotVali
}

@ExceptionHandler(value = AccessDeniedException.class) // Authorization
public ResponseEntity<ResponseDto<String>> handleAccessDeniedException(RuntimeException e, Locale locale) {
locale = httpUtils.getLocaleForLanguage(locale);

String messageKey = e.getMessage();
String errorMessage = messageSource.getMessage(messageKey, null, locale);
public ResponseEntity<ErrorResponse> handleAccessDeniedException(RuntimeException e, Locale locale) {
log.error("accessDeniedException", e);

return httpUtils.createResponseEntityWithRedirect(
httpUtils.createFailureResponse(errorMessage, unauthorized.value()),
httpUtils.getCurrentVersionPath("login")
);
return ResponseEntity.status(HttpStatus.FORBIDDEN)
.body(ErrorResponse.valueOf(FORBIDDEN));
}

@ExceptionHandler(value = KolivingServiceException.class)
Expand Down
19 changes: 6 additions & 13 deletions src/main/java/com/koliving/api/KolivingApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import com.koliving.api.room.infra.RoomRepository;
import com.koliving.api.user.User;
import com.koliving.api.user.UserRepository;
import com.koliving.api.user.UserRole;
import jakarta.annotation.PostConstruct;
import java.time.LocalDate;
import java.util.Arrays;
Expand Down Expand Up @@ -51,15 +52,6 @@ public static void main(String[] args) {
SpringApplication.run(KolivingApplication.class, args);
}

@RequestMapping
@RestController
public static class IndexController {
@GetMapping
public String index() {
return "invoke";
}
}

@PostConstruct
void started() {
TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
Expand Down Expand Up @@ -87,10 +79,11 @@ CommandLineRunner commandLineRunner(
}

private User initUser(UserRepository userRepository, PasswordEncoder encoder) {
final User user = User.builder()
.email("[email protected]")
.build();
user.setPassword(encoder.encode("test1234!@"));
final User user = User.valueOf("[email protected]", encoder.encode("test1234!@"), UserRole.USER);
final User user2 = User.valueOf("[email protected]", encoder.encode("test1234!@"), UserRole.USER);
final User manager = User.valueOf("[email protected]", encoder.encode("test1234!@"), UserRole.ADMIN);

userRepository.saveAll(List.of(user2, manager));
return userRepository.save(user);
}

Expand Down
3 changes: 2 additions & 1 deletion src/main/java/com/koliving/api/auth/AuthFacade.java
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,8 @@ private BlackAccessToken parseBlackAccessToken(String accessToken) {
public TokenResponse createToken(TokenRequest request) {
final User user = userRepository.findByEmail(request.email())
.orElseThrow(() -> new KolivingServiceException(UNAUTHORIZED));
user.checkPassword(passwordEncoder.encode(request.password()));

user.checkPassword(passwordEncoder, request.password());

final JwtTokenDto jwtTokenDto = issueAuthTokens(user);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse
Locale locale = localeResolver.resolveLocale(request);

try {

filterChain.doFilter(request, response);
} catch (LoginInvalidException e) {
ValidationResult errors = ValidationResult.of(e.getErrors(), messageSource, locale);
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/com/koliving/api/base/ServiceError.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ public enum ServiceError {
ILLEGAL_MAINTENANCE(BAD_REQUEST, "0005", "관리비 객체 생성 유효성 실패"),
ILLEGAL_ROOM_INFO(BAD_REQUEST, "0006", "방 정보 객체 생성 유효성 실패"),
UPLOAD_FAIL(BAD_REQUEST, "0007", "파일 업로드 실패"),
UNAUTHORIZED(HttpStatus.UNAUTHORIZED, "0008", "사용자 인증 실패");
UNAUTHORIZED(HttpStatus.UNAUTHORIZED, "0008", "사용자 인증 실패"),
FORBIDDEN(HttpStatus.FORBIDDEN, "0009", "사용자 권한 없음");

private final HttpStatus status;
private final String code;
Expand Down
6 changes: 6 additions & 0 deletions src/main/java/com/koliving/api/base/domain/BaseEntity.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

import java.time.LocalDateTime;

import static java.lang.Boolean.TRUE;

@Getter
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
Expand All @@ -23,4 +25,8 @@ public abstract class BaseEntity {
@LastModifiedDate
@Column(nullable = false)
private LocalDateTime updatedAt;

public void delete() {
this.deleted = TRUE;
}
}
19 changes: 7 additions & 12 deletions src/main/java/com/koliving/api/config/SecurityConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,33 +12,35 @@
import com.koliving.api.utils.HttpUtils;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.security.servlet.PathRequest;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;
import org.springframework.security.web.header.HeaderWriterFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import java.util.Arrays;
import java.util.List;

import static org.springframework.security.config.Customizer.withDefaults;
Expand Down Expand Up @@ -69,7 +71,6 @@ public class SecurityConfig {
@PostConstruct
private void init() {
AUTHENTICATION_WHITELIST = new String[]{
"/",
httpUtils.getCurrentVersionPath("auth/**"),
"/api-docs/**",
"/swagger-ui/**",
Expand All @@ -85,6 +86,7 @@ private void init() {
@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
return web -> web.ignoring()
.requestMatchers(PathRequest.toH2Console())
.requestMatchers(PathRequest.toStaticResources().atCommonLocations())
.requestMatchers(AUTHENTICATION_WHITELIST);
}
Expand All @@ -98,20 +100,14 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti
JwtAuthenticationFilter jwtAuthenticationFilter = createJwtAuthenticationFilter();

http.cors(withDefaults())
.headers()
.frameOptions().disable()
.and()
.csrf().disable()
.formLogin(AbstractHttpConfigurer::disable)
.httpBasic(AbstractHttpConfigurer::disable)
.sessionManagement((session) -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(req -> {
req
.requestMatchers(AntPathRequestMatcher.antMatcher("/h2-console/**")).permitAll()
.requestMatchers(HttpMethod.OPTIONS, "/**").permitAll()
.requestMatchers(HttpMethod.POST, "/login").permitAll() // test
.requestMatchers(AUTHENTICATION_WHITELIST).permitAll()
.requestMatchers(AUTHORIZATION_WHITELIST).permitAll()
.requestMatchers("/api/v1/management/**").hasRole("ADMIN")
.requestMatchers("/api/v1/**").hasRole("USER")
.anyRequest().authenticated();
})
.logout(config -> {
Expand All @@ -123,7 +119,6 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti
config.authenticationEntryPoint(authenticationEntryPoint)
.accessDeniedHandler(accessDeniedHandler);
})
//TODO filter 순서 조정 필요
.addFilterBefore(customExceptionHandlerFilter, HeaderWriterFilter.class)
.addFilterAfter(jwtAuthenticationFilter, loginFilter.getClass())
.addFilterAfter(loginFilter, LogoutFilter.class);
Expand Down
19 changes: 0 additions & 19 deletions src/main/java/com/koliving/api/config/WebConfig.java

This file was deleted.

36 changes: 28 additions & 8 deletions src/main/java/com/koliving/api/room/application/RoomService.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package com.koliving.api.room.application;

import static com.koliving.api.base.ServiceError.RECORD_NOT_EXIST;

import com.google.common.collect.Sets;
import com.koliving.api.base.ServiceError;
import com.koliving.api.base.exception.KolivingServiceException;
Expand All @@ -16,11 +14,6 @@
import com.koliving.api.room.domain.Room;
import com.koliving.api.room.infra.FurnishingRepository;
import com.koliving.api.room.infra.RoomRepository;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import com.koliving.api.user.User;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
Expand All @@ -29,6 +22,15 @@
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import static com.koliving.api.base.ServiceError.FORBIDDEN;
import static com.koliving.api.base.ServiceError.RECORD_NOT_EXIST;
import static com.koliving.api.base.ServiceError.UNAUTHORIZED;

/**
* author : haedoang date : 2023/08/26 description :
*/
Expand All @@ -43,7 +45,7 @@ public class RoomService {
private final ImageFileRepository imageFileRepository;

public List<RoomResponse> list() {
return roomRepository.findAll()
return roomRepository.findAllWithUser()
.stream()
.map(RoomResponse::valueOf)
.collect(Collectors.toList());
Expand Down Expand Up @@ -103,4 +105,22 @@ public Room findOne(Long id) {
return roomRepository.findByIdWithUser(id)
.orElseThrow(() -> new KolivingServiceException(RECORD_NOT_EXIST));
}

@Transactional
public void deleteRoomById(Long id) {
Room room = roomRepository.findByIdWithUser(id)
.orElseThrow(() -> new KolivingServiceException(RECORD_NOT_EXIST));

room.delete();
}

@Transactional
public void deleteRoomById(Long id, User user) {
Room room = roomRepository.findById(id).orElseThrow(() -> new KolivingServiceException(RECORD_NOT_EXIST));
if (!room.checkUser(user)) {
throw new KolivingServiceException(FORBIDDEN);
}

room.delete();
}
}
6 changes: 5 additions & 1 deletion src/main/java/com/koliving/api/room/domain/Room.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
import jakarta.persistence.JoinTable;
import jakarta.persistence.Lob;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.OneToMany;
import jakarta.persistence.OneToOne;
import jakarta.persistence.Transient;
import lombok.EqualsAndHashCode;
Expand All @@ -31,6 +30,7 @@
import java.util.Set;

import static com.koliving.api.base.ServiceError.ILLEGAL_ROOM_INFO;
import static com.koliving.api.base.ServiceError.UNAUTHORIZED;
import static jakarta.persistence.GenerationType.IDENTITY;
import static lombok.AccessLevel.PROTECTED;

Expand Down Expand Up @@ -136,4 +136,8 @@ public Room by(User user) {
this.user = user;
return this;
}

public boolean checkUser(User user) {
return this.user.equals(user);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@

import com.koliving.api.room.application.dto.RoomSearchCondition;
import com.koliving.api.room.domain.FurnishingType;
import com.koliving.api.room.domain.QFurnishing;
import com.koliving.api.room.domain.Room;
import com.koliving.api.room.domain.RoomType;
import com.querydsl.core.types.Predicate;
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.jpa.impl.JPAQueryFactory;
import lombok.RequiredArgsConstructor;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,7 @@ public class FurnishingController {
),
})
@GetMapping
public ResponseEntity<List<FurnishingResponse>> list(@AuthenticationPrincipal Principal principal) {

log.info("principal: {}", principal);
public ResponseEntity<List<FurnishingResponse>> list() {
final List<FurnishingResponse> responses = furnishingService.list();
return ResponseEntity.ok()
.body(responses);
Expand Down
21 changes: 21 additions & 0 deletions src/main/java/com/koliving/api/room/ui/RoomController.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.springframework.data.web.PageableDefault;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
Expand Down Expand Up @@ -100,4 +101,24 @@ public ResponseEntity<Room> findById(@PathVariable Long id) {
return ResponseEntity.ok()
.body(roomService.findOne(id));
}

@Operation(
summary = "방 삭제",
description = "방을 삭제합니다.",
responses = {
@ApiResponse(
responseCode = "204",
description = "방 삭제 성공"
),
@ApiResponse(
responseCode = "400",
description = "방 삭제 실패",
content = @Content(schema = @Schema(implementation = ErrorResponse.class))
),
})
@DeleteMapping("{id}")
public ResponseEntity<Void> deleteById(Long id, @AuthenticationPrincipal User user) {
roomService.deleteRoomById(id, user);
return ResponseEntity.noContent().build();
}
}
Loading

0 comments on commit aa15970

Please sign in to comment.