Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dev->Main #84

Merged
merged 2 commits into from
Oct 7, 2024
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
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
plugins {
id 'java'
id 'org.springframework.boot' version '3.2.1'
id 'org.springframework.boot' version '3.3.2'
id 'io.spring.dependency-management' version '1.1.4'
id "org.sonarqube" version "4.0.0.2929"
id 'checkstyle'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,15 @@
import org.dnd.timeet.common.security.CustomUserDetails;
import org.dnd.timeet.common.utils.ApiUtils;
import org.dnd.timeet.common.utils.ApiUtils.ApiResult;
import org.dnd.timeet.member.domain.Member;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.messaging.Message;
import org.springframework.messaging.handler.annotation.DestinationVariable;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
Expand All @@ -28,6 +32,8 @@
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.security.Principal;

@Tag(name = "안건 컨트롤러", description = "Agenda API입니다.")
@RestController
@RequestMapping("/api/meetings")
Expand All @@ -39,13 +45,22 @@ public class AgendaController {
@Operation(summary = "안건(+쉬는시간) 생성", description = "안건(+쉬는시간)을 생성한다.")
@MessageMapping("/meeting/{meeting-id}/agendas/create")
@SendTo("/topic/meeting/{meeting-id}/agendas/create")
public ResponseEntity<ApiResult<Long>> createMeeting(
public ResponseEntity<ApiResult<Long>> createAgenda(
@DestinationVariable("meeting-id") Long meetingId,
@RequestBody @Valid AgendaCreateRequest agendaCreateRequest,
@AuthenticationPrincipal CustomUserDetails userDetails) {
Long agendaId = agendaService.createAgenda(meetingId, agendaCreateRequest, userDetails.getMember());
@Valid AgendaCreateRequest agendaCreateRequest,
Principal principal) {

if (principal instanceof UsernamePasswordAuthenticationToken) {
UsernamePasswordAuthenticationToken authentication = (UsernamePasswordAuthenticationToken) principal;
CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal();

return ResponseEntity.ok(ApiUtils.success(agendaId));
// userDetails 객체를 사용하여 작업 수행
Long agendaId = agendaService.createAgenda(meetingId, agendaCreateRequest, userDetails.getMember());
return ResponseEntity.ok(ApiUtils.success(agendaId));
}

// 인증 정보가 없을 때
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}

@GetMapping("/{meeting-id}/agendas")
Expand Down Expand Up @@ -78,19 +93,22 @@ public AgendaActionResponse handleAgendaAction(@DestinationVariable("meeting-id"

@DeleteMapping("/{meeting-id}/agendas/{agenda-id}")
@Operation(summary = "안건 삭제", description = "지정된 ID에 해당하는 안건을 삭제한다.")
@MessageMapping("/meeting/{meeting-id}/agendas/{delete-id}")
@SendTo("/topic/meeting/{meeting-id}/delete/{delete-id}")
public ResponseEntity deleteAgenda(
@PathVariable("meeting-id") Long meetingId,
@PathVariable("agenda-id") Long agendaId) {
@DestinationVariable("meeting-id") Long meetingId,
@DestinationVariable("agenda-id") Long agendaId) {
agendaService.cancelAgenda(meetingId, agendaId);

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

@PatchMapping("/{meeting-id}/agendas/order")
@Operation(summary = "안건 순서 변경", description = "안건의 순서를 변경한다.")
@MessageMapping("/meeting/{meeting-id}/agendas/order")
@SendTo("/topic/meeting/{meeting-id}/agendas/order")
public ResponseEntity<ApiResult<AgendaInfoResponse>> changeAgendaOrder(
@PathVariable("meeting-id") Long meetingId,
@RequestBody @Valid AgendaOrderRequest agendaOrderRequest) {
@DestinationVariable("meeting-id") Long meetingId,
@Valid AgendaOrderRequest agendaOrderRequest) {
AgendaInfoResponse agendaInfoResponse = agendaService.changeAgendaOrder(meetingId,
agendaOrderRequest.getAgendaIds());
return ResponseEntity.ok(ApiUtils.success(agendaInfoResponse));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package org.dnd.timeet.common.interceptor;

import jakarta.servlet.http.HttpServletRequest;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.HandshakeInterceptor;

import java.util.Map;

@Component
public class CustomHandshakeInterceptor implements HandshakeInterceptor {

@Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response,
WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
if (request instanceof ServletServerHttpRequest) {
HttpServletRequest servletRequest = ((ServletServerHttpRequest) request).getServletRequest();
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

if (authentication != null) {
attributes.put("user", authentication.getPrincipal());
}
}
return true;
}

@Override
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response,
WebSocketHandler wsHandler, Exception exception) {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@
import org.springframework.messaging.simp.stomp.StompCommand;
import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
import org.springframework.messaging.support.ChannelInterceptor;
import org.springframework.messaging.support.MessageHeaderAccessor;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;

Expand All @@ -34,28 +36,33 @@ public class JwtChannelInterceptor implements ChannelInterceptor {

@Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message);
// 연결 요청시 JWT 검증
StompHeaderAccessor accessor = MessageHeaderAccessor
.getAccessor(message, StompHeaderAccessor.class);

// 연결 요청 시 JWT 검증 및 인증 정보 설정
if (StompCommand.CONNECT.equals(accessor.getCommand())) {
// Authorization 헤더 추출
List<String> authorization = accessor.getNativeHeader(JwtProvider.HEADER);
if (authorization != null && !authorization.isEmpty()) {
String jwt = authorization.get(0).substring(JwtProvider.TOKEN_PREFIX.length());
try {
// JWT 토큰 검증
DecodedJWT decodedJWT = JwtProvider.verify(jwt);
Long memberId = decodedJWT.getClaim("id").asLong();

// 사용자 정보 조회
Member member = userUtilityService.getUserById(memberId);

// 사용자 인증 정보 설정
// 사용자 인증 정보 생성
CustomUserDetails userDetails = new CustomUserDetails(member);
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
Authentication authentication =
new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());

// 세션 매니저에 사용자 세션 추가

accessor.setUser(authentication);
SecurityContextHolder.getContext().setAuthentication(authentication);

// 세션 추가
String sessionId = accessor.getSessionId();
sessionManager.addUserSession(sessionId, memberId);
log.info("User Added. Active User Count: " + sessionManager.getActiveUserCount());
Expand All @@ -67,7 +74,6 @@ public Message<?> preSend(Message<?> message, MessageChannel channel) {
return null;
}
} else {
// 클라이언트 측 타임아웃 처리
log.error("Authorization header is not found");
return null;
}
Expand All @@ -84,8 +90,10 @@ public void afterSendCompletion(Message<?> message, MessageChannel channel, bool
String sessionId = accessor.getSessionId();
sessionManager.removeUserSession(sessionId);
log.info("User Disconnected. Active User Count: " + sessionManager.getActiveUserCount());

// SecurityContextHolder의 컨텍스트 제거
SecurityContextHolder.clearContext();
}
}
}


1 change: 1 addition & 0 deletions src/main/java/org/dnd/timeet/config/WebSocketConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.ChannelRegistration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.security.config.annotation.web.socket.EnableWebSocketSecurity;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
Expand Down
Loading