diff --git a/src/main/java/com/back/domain/studyroom/controller/RoomController.java b/src/main/java/com/back/domain/studyroom/controller/RoomController.java index 99389c7f..6d43d576 100644 --- a/src/main/java/com/back/domain/studyroom/controller/RoomController.java +++ b/src/main/java/com/back/domain/studyroom/controller/RoomController.java @@ -130,11 +130,10 @@ public ResponseEntity> leaveRoom( @GetMapping("/all") @Operation( summary = "모든 방 목록 조회", - description = "공개 방과 비공개 방 전체를 조회합니다. 비공개 방은 제목과 방장 정보가 마스킹됩니다. 열린 방(WAITING, ACTIVE)이 우선 표시되고, 닫힌 방(PAUSED, TERMINATED)은 뒤로 밀립니다." + description = "공개 방과 비공개 방 전체를 조회합니다. 열린 방(WAITING, ACTIVE)이 우선 표시되고, 닫힌 방(PAUSED, TERMINATED)은 뒤로 밀립니다. 비로그인 사용자도 조회 가능합니다." ) @ApiResponses({ - @ApiResponse(responseCode = "200", description = "조회 성공"), - @ApiResponse(responseCode = "401", description = "인증 실패") + @ApiResponse(responseCode = "200", description = "조회 성공") }) public ResponseEntity>> getAllRooms( @Parameter(description = "페이지 번호 (0부터 시작)") @RequestParam(defaultValue = "0") int page, @@ -143,8 +142,8 @@ public ResponseEntity>> getAllRooms( Pageable pageable = PageRequest.of(page, size); Page rooms = roomService.getAllRooms(pageable); - // 비공개 방 마스킹 포함한 변환 - List roomList = roomService.toRoomResponseListWithMasking(rooms.getContent()); + // 모든 정보 공개 + List roomList = roomService.toRoomResponseList(rooms.getContent()); Map response = new HashMap<>(); response.put("rooms", roomList); @@ -162,11 +161,10 @@ public ResponseEntity>> getAllRooms( @GetMapping("/public") @Operation( summary = "공개 방 목록 조회", - description = "공개 방 전체를 조회합니다. includeInactive=true로 설정하면 닫힌 방도 포함됩니다 (기본값: true). 열린 방이 우선 표시됩니다." + description = "공개 방 전체를 조회합니다. includeInactive=true로 설정하면 닫힌 방도 포함됩니다 (기본값: true). 열린 방이 우선 표시됩니다. 비로그인 사용자도 조회 가능합니다." ) @ApiResponses({ - @ApiResponse(responseCode = "200", description = "조회 성공"), - @ApiResponse(responseCode = "401", description = "인증 실패") + @ApiResponse(responseCode = "200", description = "조회 성공") }) public ResponseEntity>> getPublicRooms( @Parameter(description = "페이지 번호 (0부터 시작)") @RequestParam(defaultValue = "0") int page, @@ -261,11 +259,10 @@ public ResponseEntity>> getMyHostingRooms( @GetMapping @Operation( summary = "입장 가능한 공개 방 목록 조회 (기존)", - description = "입장 가능한 공개 스터디 룸 목록을 페이징하여 조회합니다. 최신 생성 순으로 정렬됩니다." + description = "입장 가능한 공개 스터디 룸 목록을 페이징하여 조회합니다. 최신 생성 순으로 정렬됩니다. 비로그인 사용자도 조회 가능합니다." ) @ApiResponses({ - @ApiResponse(responseCode = "200", description = "조회 성공"), - @ApiResponse(responseCode = "401", description = "인증 실패") + @ApiResponse(responseCode = "200", description = "조회 성공") }) public ResponseEntity>> getRooms( @Parameter(description = "페이지 번호 (0부터 시작)") @RequestParam(defaultValue = "0") int page, @@ -292,18 +289,17 @@ public ResponseEntity>> getRooms( @GetMapping("/{roomId}") @Operation( summary = "방 상세 정보 조회", - description = "특정 방의 상세 정보와 현재 온라인 멤버 목록을 조회합니다. 비공개 방은 멤버만 조회 가능합니다." + description = "특정 방의 상세 정보와 현재 온라인 멤버 목록을 조회합니다. 비로그인 사용자도 조회 가능합니다." ) @ApiResponses({ @ApiResponse(responseCode = "200", description = "조회 성공"), - @ApiResponse(responseCode = "403", description = "비공개 방에 대한 접근 권한 없음"), - @ApiResponse(responseCode = "404", description = "존재하지 않는 방"), - @ApiResponse(responseCode = "401", description = "인증 실패") + @ApiResponse(responseCode = "404", description = "존재하지 않는 방") }) public ResponseEntity> getRoomDetail( @Parameter(description = "방 ID", required = true) @PathVariable Long roomId) { - Long currentUserId = currentUser.getUserId(); + // 비로그인 사용자는 userId = null로 처리 + Long currentUserId = currentUser.getUserIdOrNull(); Room room = roomService.getRoomDetail(roomId, currentUserId); List members = roomService.getRoomMembers(roomId, currentUserId); @@ -522,11 +518,10 @@ public ResponseEntity>> getRoomMembers( @GetMapping("/popular") @Operation( summary = "인기 방 목록 조회", - description = "참가자 수가 많은 인기 방 목록을 페이징하여 조회합니다. 참가자 수 내림차순에서 최신순으로 정렬됩니다." + description = "참가자 수가 많은 인기 방 목록을 페이징하여 조회합니다. 공개방과 비공개방 모두 포함됩니다. 참가자 수 내림차순에서 최신순으로 정렬됩니다. 비로그인 사용자도 조회 가능합니다." ) @ApiResponses({ - @ApiResponse(responseCode = "200", description = "조회 성공"), - @ApiResponse(responseCode = "401", description = "인증 실패") + @ApiResponse(responseCode = "200", description = "조회 성공") }) public ResponseEntity>> getPopularRooms( @Parameter(description = "페이지 번호 (0부터 시작)") @RequestParam(defaultValue = "0") int page, diff --git a/src/main/java/com/back/domain/studyroom/repository/RoomRepositoryImpl.java b/src/main/java/com/back/domain/studyroom/repository/RoomRepositoryImpl.java index 26e76819..5a67ac3c 100644 --- a/src/main/java/com/back/domain/studyroom/repository/RoomRepositoryImpl.java +++ b/src/main/java/com/back/domain/studyroom/repository/RoomRepositoryImpl.java @@ -157,13 +157,13 @@ public Page findRoomsWithFilters(String title, RoomStatus status, Boolean } /** - * 인기 방 조회 (참가자 수 기준) + * 인기 방 조회 (참가자 수 기준) - 공개+비공개 포함 * * 참고: 참가자 수는 Redis에서 조회하므로 DB에서는 정렬 불가 * 서비스 레이어에서 Redis 데이터로 정렬 필요 * * 조회 조건: - * - 공개 방만 (isPrivate = false) + * - 공개 방 + 비공개 방 모두 포함 * - 활성화된 방만 (isActive = true) * @param pageable 페이징 정보 * @return 페이징된 방 목록 (최신순 정렬) @@ -174,7 +174,6 @@ public Page findPopularRooms(Pageable pageable) { .selectFrom(room) .leftJoin(room.createdBy, user).fetchJoin() // N+1 방지 .where( - room.isPrivate.eq(false), room.isActive.eq(true) ) .orderBy(room.createdAt.desc()) // 최신순 (서비스에서 Redis 기반으로 재정렬) @@ -187,7 +186,6 @@ public Page findPopularRooms(Pageable pageable) { .select(room.count()) .from(room) .where( - room.isPrivate.eq(false), room.isActive.eq(true) ) .fetchOne(); diff --git a/src/main/java/com/back/domain/studyroom/service/RoomService.java b/src/main/java/com/back/domain/studyroom/service/RoomService.java index 0c389550..460b64e7 100644 --- a/src/main/java/com/back/domain/studyroom/service/RoomService.java +++ b/src/main/java/com/back/domain/studyroom/service/RoomService.java @@ -257,12 +257,8 @@ public Room getRoomDetail(Long roomId, Long userId) { Room room = roomRepository.findById(roomId) .orElseThrow(() -> new CustomException(ErrorCode.ROOM_NOT_FOUND)); - if (room.isPrivate()) { - boolean isMember = roomMemberRepository.existsByRoomIdAndUserId(roomId, userId); - if (!isMember) { - throw new CustomException(ErrorCode.ROOM_FORBIDDEN); - } - } + // ⭐ 비공개 방 접근 제한 제거 - 모든 사용자가 조회 가능 + // (프론트엔드에서 입장 시 로그인 체크) return room; } @@ -534,12 +530,7 @@ public List getRoomMembers(Long roomId, Long userId) { Room room = roomRepository.findById(roomId) .orElseThrow(() -> new CustomException(ErrorCode.ROOM_NOT_FOUND)); - if (room.isPrivate()) { - boolean isMember = roomMemberRepository.existsByRoomIdAndUserId(roomId, userId); - if (!isMember) { - throw new CustomException(ErrorCode.ROOM_FORBIDDEN); - } - } + // ⭐ 비공개 방 접근 제한 제거 - 모든 사용자가 조회 가능 // 1. Redis에서 온라인 사용자 ID 조회 Set onlineUserIds = roomParticipantService.getParticipants(roomId); diff --git a/src/main/java/com/back/global/security/SecurityConfig.java b/src/main/java/com/back/global/security/SecurityConfig.java index edcde415..edad354b 100644 --- a/src/main/java/com/back/global/security/SecurityConfig.java +++ b/src/main/java/com/back/global/security/SecurityConfig.java @@ -42,6 +42,12 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .requestMatchers("api/ws/**", "/ws/**").permitAll() .requestMatchers(HttpMethod.GET, "/api/posts/**").permitAll() .requestMatchers("/api/rooms/*/messages/**").permitAll() //스터디 룸 내에 잡혀있어 있는 채팅 관련 전체 허용 + // 방 목록 조회 API 비로그인 허용 + .requestMatchers(HttpMethod.GET, "/api/rooms").permitAll() + .requestMatchers(HttpMethod.GET, "/api/rooms/all").permitAll() + .requestMatchers(HttpMethod.GET, "/api/rooms/public").permitAll() + .requestMatchers(HttpMethod.GET, "/api/rooms/popular").permitAll() + .requestMatchers(HttpMethod.GET, "/api/rooms/*").permitAll() // 방 상세 조회 //.requestMatchers("/api/rooms/RoomChatApiControllerTest").permitAll() // 테스트용 임시 허용 .requestMatchers("/","/swagger-ui/**", "/v3/api-docs/**").permitAll() // Swagger 허용 .requestMatchers("/h2-console/**").permitAll() // H2 Console 허용 diff --git a/src/main/java/com/back/global/security/user/CurrentUser.java b/src/main/java/com/back/global/security/user/CurrentUser.java index c97deab8..0fd00942 100644 --- a/src/main/java/com/back/global/security/user/CurrentUser.java +++ b/src/main/java/com/back/global/security/user/CurrentUser.java @@ -30,6 +30,21 @@ public boolean isAuthenticated() { } public Long getUserId() { return getDetails().getUserId(); } + + /** + * 현재 사용자 ID 조회 (비로그인 시 null 반환) + * 비로그인 접근이 허용되는 API에서 사용 + */ + public Long getUserIdOrNull() { + try { + return getDetails().getUserId(); + } catch (CustomException e) { + if (e.getErrorCode() == ErrorCode.UNAUTHORIZED) { + return null; + } + throw e; + } + } public String getUsername() { return getDetails().getUsername(); } diff --git a/src/test/java/com/back/domain/studyroom/controller/RoomControllerTest.java b/src/test/java/com/back/domain/studyroom/controller/RoomControllerTest.java index 26ab1961..edb98059 100644 --- a/src/test/java/com/back/domain/studyroom/controller/RoomControllerTest.java +++ b/src/test/java/com/back/domain/studyroom/controller/RoomControllerTest.java @@ -208,7 +208,7 @@ void getRooms() { @DisplayName("방 상세 정보 조회 API 테스트 - JWT 인증") void getRoomDetail() { // given - given(currentUser.getUserId()).willReturn(1L); + given(currentUser.getUserIdOrNull()).willReturn(1L); given(roomService.getRoomDetail(eq(1L), eq(1L))).willReturn(testRoom); given(roomService.getRoomMembers(eq(1L), eq(1L))).willReturn(Arrays.asList(testMember)); @@ -229,7 +229,7 @@ void getRoomDetail() { assertThat(response.getBody().isSuccess()).isTrue(); assertThat(response.getBody().getData().getTitle()).isEqualTo("테스트 방"); - verify(currentUser, times(1)).getUserId(); + verify(currentUser, times(1)).getUserIdOrNull(); verify(roomService, times(1)).getRoomDetail(eq(1L), eq(1L)); verify(roomService, times(1)).getRoomMembers(eq(1L), eq(1L)); verify(roomService, times(1)).toRoomDetailResponse(any(Room.class), anyList()); diff --git a/src/test/java/com/back/domain/studyroom/service/RoomServiceTest.java b/src/test/java/com/back/domain/studyroom/service/RoomServiceTest.java index 815ac8e1..d9cf5b83 100644 --- a/src/test/java/com/back/domain/studyroom/service/RoomServiceTest.java +++ b/src/test/java/com/back/domain/studyroom/service/RoomServiceTest.java @@ -258,8 +258,8 @@ void getRoomDetail_Success() { } @Test - @DisplayName("방 상세 정보 조회 - 비공개 방 권한 없음") - void getRoomDetail_PrivateRoomForbidden() { + @DisplayName("방 상세 정보 조회 - 비공개 방도 조회 가능") + void getRoomDetail_PrivateRoomAllowed() { // given Room privateRoom = Room.create( "비공개 방", @@ -273,12 +273,16 @@ void getRoomDetail_PrivateRoomForbidden() { null // thumbnailUrl ); given(roomRepository.findById(1L)).willReturn(Optional.of(privateRoom)); - given(roomMemberRepository.existsByRoomIdAndUserId(1L, 2L)).willReturn(false); + // ⭐ 비공개 방 접근 제한 제거되었으므로 권한 체크 안 함 - // when & then - assertThatThrownBy(() -> roomService.getRoomDetail(1L, 2L)) - .isInstanceOf(CustomException.class) - .hasFieldOrPropertyWithValue("errorCode", ErrorCode.ROOM_FORBIDDEN); + // when + Room result = roomService.getRoomDetail(1L, 2L); + + // then + assertThat(result).isNotNull(); + assertThat(result.getTitle()).isEqualTo("비공개 방"); + assertThat(result.isPrivate()).isTrue(); + verify(roomMemberRepository, never()).existsByRoomIdAndUserId(any(), any()); } @Test