mirror of
https://github.com/hwanny1128/HGZero.git
synced 2026-01-21 13:46:26 +00:00
feat: jwt 토큰 비활성화 및 회의예약API 개발
This commit is contained in:
parent
d9261bad2c
commit
6e7b910a8d
File diff suppressed because it is too large
Load Diff
1
meeting/logs/meeting.log
Normal file
1
meeting/logs/meeting.log
Normal file
@ -0,0 +1 @@
|
|||||||
|
nohup: ./gradlew: No such file or directory
|
||||||
@ -37,6 +37,16 @@ public class Meeting {
|
|||||||
*/
|
*/
|
||||||
private LocalDateTime scheduledAt;
|
private LocalDateTime scheduledAt;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 회의 종료 예정 일시
|
||||||
|
*/
|
||||||
|
private LocalDateTime endTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 회의 장소
|
||||||
|
*/
|
||||||
|
private String location;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 회의 시작 일시
|
* 회의 시작 일시
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -78,8 +78,8 @@ public class MeetingDTO {
|
|||||||
.meetingId(meeting.getMeetingId())
|
.meetingId(meeting.getMeetingId())
|
||||||
.title(meeting.getTitle())
|
.title(meeting.getTitle())
|
||||||
.startTime(meeting.getStartedAt() != null ? meeting.getStartedAt() : meeting.getScheduledAt())
|
.startTime(meeting.getStartedAt() != null ? meeting.getStartedAt() : meeting.getScheduledAt())
|
||||||
.endTime(meeting.getEndedAt())
|
.endTime(meeting.getEndedAt() != null ? meeting.getEndedAt() : meeting.getEndTime())
|
||||||
.location(null) // Meeting 도메인에 location 필드가 없어서 null로 설정
|
.location(meeting.getLocation())
|
||||||
.agenda(meeting.getDescription())
|
.agenda(meeting.getDescription())
|
||||||
.participants(meeting.getParticipants().stream()
|
.participants(meeting.getParticipants().stream()
|
||||||
.map(participantId -> ParticipantDTO.builder()
|
.map(participantId -> ParticipantDTO.builder()
|
||||||
|
|||||||
@ -49,6 +49,8 @@ public class MeetingService implements
|
|||||||
.title(command.title())
|
.title(command.title())
|
||||||
.description(command.description())
|
.description(command.description())
|
||||||
.scheduledAt(command.scheduledAt())
|
.scheduledAt(command.scheduledAt())
|
||||||
|
.endTime(command.endTime())
|
||||||
|
.location(command.location())
|
||||||
.status("SCHEDULED")
|
.status("SCHEDULED")
|
||||||
.organizerId(command.organizerId())
|
.organizerId(command.organizerId())
|
||||||
.participants(command.participants())
|
.participants(command.participants())
|
||||||
|
|||||||
@ -22,6 +22,8 @@ public interface CreateMeetingUseCase {
|
|||||||
String title,
|
String title,
|
||||||
String description,
|
String description,
|
||||||
LocalDateTime scheduledAt,
|
LocalDateTime scheduledAt,
|
||||||
|
LocalDateTime endTime,
|
||||||
|
String location,
|
||||||
String organizerId,
|
String organizerId,
|
||||||
List<String> participants,
|
List<String> participants,
|
||||||
String templateId
|
String templateId
|
||||||
|
|||||||
@ -12,11 +12,15 @@ import org.springframework.security.config.annotation.web.configurers.AbstractHt
|
|||||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||||
import org.springframework.security.web.SecurityFilterChain;
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||||
|
import org.springframework.security.web.firewall.HttpFirewall;
|
||||||
|
import org.springframework.security.web.firewall.StrictHttpFirewall;
|
||||||
import org.springframework.web.cors.CorsConfiguration;
|
import org.springframework.web.cors.CorsConfiguration;
|
||||||
import org.springframework.web.cors.CorsConfigurationSource;
|
import org.springframework.web.cors.CorsConfigurationSource;
|
||||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Spring Security 설정
|
* Spring Security 설정
|
||||||
@ -48,7 +52,8 @@ public class SecurityConfig {
|
|||||||
// WebSocket endpoints
|
// WebSocket endpoints
|
||||||
.requestMatchers("/ws/**").permitAll()
|
.requestMatchers("/ws/**").permitAll()
|
||||||
// All other requests require authentication
|
// All other requests require authentication
|
||||||
.anyRequest().authenticated()
|
// .anyRequest().authenticated()
|
||||||
|
.anyRequest().permitAll()
|
||||||
)
|
)
|
||||||
.addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider),
|
.addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider),
|
||||||
UsernamePasswordAuthenticationFilter.class)
|
UsernamePasswordAuthenticationFilter.class)
|
||||||
@ -69,7 +74,8 @@ public class SecurityConfig {
|
|||||||
// 허용할 헤더
|
// 허용할 헤더
|
||||||
configuration.setAllowedHeaders(Arrays.asList(
|
configuration.setAllowedHeaders(Arrays.asList(
|
||||||
"Authorization", "Content-Type", "X-Requested-With", "Accept",
|
"Authorization", "Content-Type", "X-Requested-With", "Accept",
|
||||||
"Origin", "Access-Control-Request-Method", "Access-Control-Request-Headers"
|
"Origin", "Access-Control-Request-Method", "Access-Control-Request-Headers",
|
||||||
|
"X-User-Id", "X-User-Name", "X-User-Email"
|
||||||
));
|
));
|
||||||
|
|
||||||
// 자격 증명 허용
|
// 자격 증명 허용
|
||||||
@ -82,4 +88,24 @@ public class SecurityConfig {
|
|||||||
source.registerCorsConfiguration("/**", configuration);
|
source.registerCorsConfiguration("/**", configuration);
|
||||||
return source;
|
return source;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HttpFirewall 설정
|
||||||
|
* 한글을 포함한 모든 문자를 헤더 값으로 허용
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public HttpFirewall allowUrlEncodedSlashHttpFirewall() {
|
||||||
|
StrictHttpFirewall firewall = new StrictHttpFirewall();
|
||||||
|
|
||||||
|
// 한글을 포함한 모든 문자를 허용하도록 설정
|
||||||
|
firewall.setAllowedHeaderValues(header -> true);
|
||||||
|
|
||||||
|
// URL 인코딩된 슬래시 허용
|
||||||
|
firewall.setAllowUrlEncodedSlash(true);
|
||||||
|
|
||||||
|
// 세미콜론 허용
|
||||||
|
firewall.setAllowSemicolon(true);
|
||||||
|
|
||||||
|
return firewall;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -32,6 +32,36 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
|||||||
HttpServletResponse response,
|
HttpServletResponse response,
|
||||||
FilterChain filterChain) throws ServletException, IOException {
|
FilterChain filterChain) throws ServletException, IOException {
|
||||||
|
|
||||||
|
// 1. X-User-* 헤더를 통한 인증 (개발/테스트용)
|
||||||
|
String headerUserId = request.getHeader("X-User-Id");
|
||||||
|
String headerUserName = request.getHeader("X-User-Name");
|
||||||
|
String headerUserEmail = request.getHeader("X-User-Email");
|
||||||
|
|
||||||
|
if (StringUtils.hasText(headerUserId)) {
|
||||||
|
// X-User-* 헤더가 있으면 이를 사용하여 인증
|
||||||
|
UserPrincipal userPrincipal = UserPrincipal.builder()
|
||||||
|
.userId(headerUserId)
|
||||||
|
.username(headerUserName != null ? headerUserName : "unknown")
|
||||||
|
.email(headerUserEmail)
|
||||||
|
.authority("USER")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
UsernamePasswordAuthenticationToken authentication =
|
||||||
|
new UsernamePasswordAuthenticationToken(
|
||||||
|
userPrincipal,
|
||||||
|
null,
|
||||||
|
Collections.singletonList(new SimpleGrantedAuthority("USER"))
|
||||||
|
);
|
||||||
|
|
||||||
|
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
|
||||||
|
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||||
|
|
||||||
|
log.debug("헤더 기반 인증된 사용자: {} ({})", userPrincipal.getUsername(), headerUserId);
|
||||||
|
filterChain.doFilter(request, response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. JWT 토큰을 통한 인증
|
||||||
String token = jwtTokenProvider.resolveToken(request);
|
String token = jwtTokenProvider.resolveToken(request);
|
||||||
|
|
||||||
if (StringUtils.hasText(token) && jwtTokenProvider.validateToken(token)) {
|
if (StringUtils.hasText(token) && jwtTokenProvider.validateToken(token)) {
|
||||||
@ -69,7 +99,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
|||||||
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
|
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
|
||||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||||
|
|
||||||
log.debug("인증된 사용자: {} ({})", userPrincipal.getUsername(), userId);
|
log.debug("JWT 기반 인증된 사용자: {} ({})", userPrincipal.getUsername(), userId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -23,6 +23,11 @@ public class UserPrincipal {
|
|||||||
*/
|
*/
|
||||||
private final String username;
|
private final String username;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 사용자 이메일
|
||||||
|
*/
|
||||||
|
private final String email;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 사용자 권한
|
* 사용자 권한
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -65,6 +65,8 @@ public class MeetingController {
|
|||||||
request.getTitle(),
|
request.getTitle(),
|
||||||
request.getAgenda(),
|
request.getAgenda(),
|
||||||
request.getStartTime(),
|
request.getStartTime(),
|
||||||
|
request.getEndTime(),
|
||||||
|
request.getLocation(),
|
||||||
userId,
|
userId,
|
||||||
request.getParticipants(),
|
request.getParticipants(),
|
||||||
null // 템플릿 ID는 나중에 적용
|
null // 템플릿 ID는 나중에 적용
|
||||||
|
|||||||
@ -37,6 +37,12 @@ public class MeetingEntity extends BaseTimeEntity {
|
|||||||
@Column(name = "scheduled_at", nullable = false)
|
@Column(name = "scheduled_at", nullable = false)
|
||||||
private LocalDateTime scheduledAt;
|
private LocalDateTime scheduledAt;
|
||||||
|
|
||||||
|
@Column(name = "end_time")
|
||||||
|
private LocalDateTime endTime;
|
||||||
|
|
||||||
|
@Column(name = "location", length = 200)
|
||||||
|
private String location;
|
||||||
|
|
||||||
@Column(name = "started_at")
|
@Column(name = "started_at")
|
||||||
private LocalDateTime startedAt;
|
private LocalDateTime startedAt;
|
||||||
|
|
||||||
@ -62,6 +68,8 @@ public class MeetingEntity extends BaseTimeEntity {
|
|||||||
.title(this.title)
|
.title(this.title)
|
||||||
.description(this.description)
|
.description(this.description)
|
||||||
.scheduledAt(this.scheduledAt)
|
.scheduledAt(this.scheduledAt)
|
||||||
|
.endTime(this.endTime)
|
||||||
|
.location(this.location)
|
||||||
.startedAt(this.startedAt)
|
.startedAt(this.startedAt)
|
||||||
.endedAt(this.endedAt)
|
.endedAt(this.endedAt)
|
||||||
.status(this.status)
|
.status(this.status)
|
||||||
@ -77,6 +85,8 @@ public class MeetingEntity extends BaseTimeEntity {
|
|||||||
.title(meeting.getTitle())
|
.title(meeting.getTitle())
|
||||||
.description(meeting.getDescription())
|
.description(meeting.getDescription())
|
||||||
.scheduledAt(meeting.getScheduledAt())
|
.scheduledAt(meeting.getScheduledAt())
|
||||||
|
.endTime(meeting.getEndTime())
|
||||||
|
.location(meeting.getLocation())
|
||||||
.startedAt(meeting.getStartedAt())
|
.startedAt(meeting.getStartedAt())
|
||||||
.endedAt(meeting.getEndedAt())
|
.endedAt(meeting.getEndedAt())
|
||||||
.status(meeting.getStatus())
|
.status(meeting.getStatus())
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user