diff --git a/build.gradle b/build.gradle index 6ff9518..af38c67 100644 --- a/build.gradle +++ b/build.gradle @@ -11,6 +11,8 @@ java { toolchain { languageVersion = JavaLanguageVersion.of(21) } + withJavadocJar() + withSourcesJar() } configurations { @@ -44,6 +46,14 @@ dependencies { // 포트원 implementation 'com.github.iamport:iamport-rest-client-java:0.2.23' + // Spring Security + implementation 'org.springframework.boot:spring-boot-starter-security' + + // JWT + implementation 'io.jsonwebtoken:jjwt-api:0.11.5' + implementation 'io.jsonwebtoken:jjwt-impl:0.11.5' + implementation 'io.jsonwebtoken:jjwt-jackson:0.11.5' + } diff --git a/src/main/java/org/groomUniv/meet/MeetApplication.java b/src/main/java/org/groomUniv/meet/MeetApplication.java index 6edc886..6b036fe 100644 --- a/src/main/java/org/groomUniv/meet/MeetApplication.java +++ b/src/main/java/org/groomUniv/meet/MeetApplication.java @@ -2,8 +2,10 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; @SpringBootApplication +@EnableJpaAuditing // JPA Auditing 사용을 위한 설정 public class MeetApplication { public static void main(String[] args) { diff --git a/src/main/java/org/groomUniv/meet/blinddate/entity/BlindDate.java b/src/main/java/org/groomUniv/meet/blinddate/entity/BlindDate.java new file mode 100644 index 0000000..74377fa --- /dev/null +++ b/src/main/java/org/groomUniv/meet/blinddate/entity/BlindDate.java @@ -0,0 +1,48 @@ +package org.groomUniv.meet.blinddate.entity; + +import jakarta.persistence.*; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.groomUniv.meet.common.entity.BaseEntity; +import org.groomUniv.meet.oauth.entity.Member; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +@Entity +@NoArgsConstructor +@Table(name = "blind_date") + + +public class BlindDate extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long blindDateId; + + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "member1_id") + private Member member1; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "member2_id") + private Member member2; + + // 채팅제한 + private Long chatLimit; + + //챗 엔티티 리스트로 관리하기 + @OneToMany(mappedBy = "blindDate", cascade = CascadeType.ALL) + private List chats = new ArrayList<>(); +} +// JPA Auditing 설명 +//@CreatedDate: 엔터티가 처음 생성된 날짜를 자동으로 기록합니다. +// +//@LastModifiedDate: 엔터티가 마지막으로 수정된 날짜를 자동으로 기록합니다. +// +//@CreatedBy: 엔터티를 처음 생성한 사용자를 자동으로 기록합니다. +// +//@LastModifiedBy: 엔터티를 마지막으로 수정한 사용자를 자동으로 기록합니다. \ No newline at end of file diff --git a/src/main/java/org/groomUniv/meet/blinddate/entity/BlindDateChat.java b/src/main/java/org/groomUniv/meet/blinddate/entity/BlindDateChat.java new file mode 100644 index 0000000..37111e0 --- /dev/null +++ b/src/main/java/org/groomUniv/meet/blinddate/entity/BlindDateChat.java @@ -0,0 +1,31 @@ +package org.groomUniv.meet.blinddate.entity; + + +import jakarta.persistence.*; +import lombok.Data; +import org.groomUniv.meet.common.entity.BaseEntity; +import org.groomUniv.meet.oauth.entity.Member; +import org.springframework.data.annotation.CreatedDate; + +import java.time.LocalDateTime; + + +@Entity +public class BlindDateChat extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long blindDataChatId; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "blind_date_id") + private BlindDate blindDate; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "member_id") + private Member sender; + + private String message; + + private Boolean readStatus; +} diff --git a/src/main/java/org/groomUniv/meet/blinddate/repository/BlindDateChatRepository.java b/src/main/java/org/groomUniv/meet/blinddate/repository/BlindDateChatRepository.java new file mode 100644 index 0000000..50cb760 --- /dev/null +++ b/src/main/java/org/groomUniv/meet/blinddate/repository/BlindDateChatRepository.java @@ -0,0 +1,22 @@ +package org.groomUniv.meet.blinddate.repository; + +import org.groomUniv.meet.blinddate.entity.BlindDate; +import org.groomUniv.meet.blinddate.entity.BlindDateChat; +import org.groomUniv.meet.oauth.entity.Member; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; +import java.util.Optional; + +public interface BlindDateChatRepository extends JpaRepository { + + // 채팅 ID로 채팅 검색 + Optional findById(Long blindDataChatId); + + // 특정 블라인드 채팅에 속한 모든 채팅 시간순으로 + //List findAllByBlindDateOrderByTimestampAsc(BlindDate blindDate); + // 읽지 않은 메세지들 가져오기 + List findAllByBlindDateAndReadStatusFalse(BlindDate blindDate); + // 특정 sender 기반으로 조회하기 + List findAllBySender(Member sender); +} diff --git a/src/main/java/org/groomUniv/meet/blinddate/repository/BlindDateRepository.java b/src/main/java/org/groomUniv/meet/blinddate/repository/BlindDateRepository.java new file mode 100644 index 0000000..e7eab9c --- /dev/null +++ b/src/main/java/org/groomUniv/meet/blinddate/repository/BlindDateRepository.java @@ -0,0 +1,20 @@ +package org.groomUniv.meet.blinddate.repository; + +import org.groomUniv.meet.blinddate.entity.BlindDate; +import org.groomUniv.meet.blinddate.entity.BlindDateChat; +import org.groomUniv.meet.oauth.entity.Member; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; +import java.util.Optional; + +public interface BlindDateRepository extends JpaRepository { + // 특정 두 멤버 기반의 챗 가져오기 + Optional findByMember1AndMember2(Member member1, Member member2); + // 특정 멤버중 하나만 있어도 가져오는 함수 + @Query("SELECT b FROM BlindDate b WHERE b.member1 = :member OR b.member2 = :member") + List findAllByMember(@Param("member") Member member); + +} diff --git a/src/main/java/org/groomUniv/meet/common/apiPayload/error/ErrorMessage.java b/src/main/java/org/groomUniv/meet/common/apiPayload/error/ErrorMessage.java index 7f520be..82cdaca 100644 --- a/src/main/java/org/groomUniv/meet/common/apiPayload/error/ErrorMessage.java +++ b/src/main/java/org/groomUniv/meet/common/apiPayload/error/ErrorMessage.java @@ -14,4 +14,9 @@ public ErrorMessage(ErrorType errorType) { this.message = errorType.getMessage(); } + public ErrorMessage(String code, String message) { + this.code = code; + this.message = message; + } + } \ No newline at end of file diff --git a/src/main/java/org/groomUniv/meet/common/apiPayload/error/GlobalErrorType.java b/src/main/java/org/groomUniv/meet/common/apiPayload/error/GlobalErrorType.java index 1eb79ea..c408bdc 100644 --- a/src/main/java/org/groomUniv/meet/common/apiPayload/error/GlobalErrorType.java +++ b/src/main/java/org/groomUniv/meet/common/apiPayload/error/GlobalErrorType.java @@ -9,6 +9,7 @@ public enum GlobalErrorType implements ErrorType { E500(HttpStatus.INTERNAL_SERVER_ERROR, "알 수 없는 내부 오류입니다."), + MEMBER_NOT_FOUND(HttpStatus.UNAUTHORIZED, "Member Not Found."), // "존재하지 않는 사용자" 401 Unauthorized ; private final HttpStatus status; diff --git a/src/main/java/org/groomUniv/meet/common/apiPayload/response/ApiResponse.java b/src/main/java/org/groomUniv/meet/common/apiPayload/response/ApiResponse.java index d44c161..eafa2bb 100644 --- a/src/main/java/org/groomUniv/meet/common/apiPayload/response/ApiResponse.java +++ b/src/main/java/org/groomUniv/meet/common/apiPayload/response/ApiResponse.java @@ -17,4 +17,8 @@ public static ApiResponse error(ErrorType error) { return new ApiResponse<>(ResultType.ERROR, null, new ErrorMessage(error)); } + public static ApiResponse error(String code, String message) { + return new ApiResponse<>(ResultType.ERROR, null, new ErrorMessage(code, message)); + } + } \ No newline at end of file diff --git a/src/main/java/org/groomUniv/meet/common/config/JpaAuditingConfig.java b/src/main/java/org/groomUniv/meet/common/config/JpaAuditingConfig.java new file mode 100644 index 0000000..3454b1d --- /dev/null +++ b/src/main/java/org/groomUniv/meet/common/config/JpaAuditingConfig.java @@ -0,0 +1,24 @@ +package org.groomUniv.meet.common.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.domain.AuditorAware; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; + +import java.util.Optional; + +@Configuration +//@EnableJpaAuditing +// Spring이 CreatedBy에서 어떤 사용자를 넣을지 모르기 때문에 그걸 이 부분을 통해 찾아주려고 적는 Config 파일 +public class JpaAuditingConfig { + + @Bean + public AuditorAware auditorProvider() { + // 실제 사용자 인증이 있는 경우엔 SecurityContext에서 꺼내서 여기 부분 변경해야함 basic으로 쓰면 안됨. + return () -> Optional.of("basic"); // 기본값 + } +} +// Spring Security 사용할 때의 SecurityContextHolder 사용법 +// return () -> Optional.ofNullable(SecurityContextHolder.getContext().getAuthentication()) +// .filter(Authentication::isAuthenticated) +// .map(Authentication::getName); \ No newline at end of file diff --git a/src/main/java/org/groomUniv/meet/common/config/SecurityConfig.java b/src/main/java/org/groomUniv/meet/common/config/SecurityConfig.java new file mode 100644 index 0000000..778db83 --- /dev/null +++ b/src/main/java/org/groomUniv/meet/common/config/SecurityConfig.java @@ -0,0 +1,60 @@ +package org.groomUniv.meet.common.config; + +import lombok.RequiredArgsConstructor; +import org.groomUniv.meet.common.security.CustomUserDetailsService; +import org.groomUniv.meet.common.security.JwtAuthenticationFilter; +import org.groomUniv.meet.common.security.JwtTokenProvider; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; +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.configurers.AbstractHttpConfigurer; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.HttpStatusEntryPoint; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; + +import static jakarta.servlet.DispatcherType.ERROR; + +@Configuration +@EnableWebSecurity +@RequiredArgsConstructor +public class SecurityConfig { + private final JwtTokenProvider jwtTokenProvider; + private final CustomUserDetailsService userDetailsService; + + @Bean + public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception { + return authenticationConfiguration.getAuthenticationManager(); + } + + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + return http + .csrf(AbstractHttpConfigurer::disable) + .httpBasic(AbstractHttpConfigurer::disable) // HTTP 기본 인증 방식 비활성화(브라우저 팝업창 로그인 방지) + .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // 세션을 사용하지 않도록 설정(JWT는 Stateless 방식이므로 세션 필요 없음) + .authorizeHttpRequests(auth -> auth // 요청에 대한 인가(Authorization) 설정 + .dispatcherTypeMatchers(ERROR).permitAll() + .requestMatchers("/api/member/signup", "/api/member/login").permitAll() // 로그인과 회원가입은 인증 없이 접근 가능 + // requestMatchers를 추가해 API를 통한 필터링 가능 + .anyRequest().authenticated() // 그 외 모든 요청은 인증 필요 + ) + //UsernamePasswordAuthenticationFilter 전에 JwtAuthenticationFilter를 실행하겠다 + .addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider), UsernamePasswordAuthenticationFilter.class) + .build(); + + } + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } +} diff --git a/src/main/java/org/groomUniv/meet/common/entity/BaseEntity.java b/src/main/java/org/groomUniv/meet/common/entity/BaseEntity.java new file mode 100644 index 0000000..536ba3d --- /dev/null +++ b/src/main/java/org/groomUniv/meet/common/entity/BaseEntity.java @@ -0,0 +1,29 @@ +package org.groomUniv.meet.common.entity; + +import jakarta.persistence.Column; +import jakarta.persistence.EntityListeners; +import jakarta.persistence.MappedSuperclass; +import lombok.Getter; +import org.springframework.data.annotation.CreatedBy; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import java.time.LocalDateTime; +@Getter +@MappedSuperclass +@EntityListeners(AuditingEntityListener.class) // JPA Auditing 활성화 +// 생성일자, 수정일자, 시간 관리에 대한 것은 공통적인 부분이라 BaseEntity를 통해 관리하고 상속받아서 사용하기 위한 용도 +public class BaseEntity { + // create 날짜 언제인지 + @CreatedDate + @Column(updatable = false) + private LocalDateTime createdAt; + + // 누가 create 했는지 + @CreatedBy + @Column(updatable = false) + private String createdBy; + + + +} diff --git a/src/main/java/org/groomUniv/meet/common/security/CustomUserDetails.java b/src/main/java/org/groomUniv/meet/common/security/CustomUserDetails.java new file mode 100644 index 0000000..25c5af0 --- /dev/null +++ b/src/main/java/org/groomUniv/meet/common/security/CustomUserDetails.java @@ -0,0 +1,56 @@ +package org.groomUniv.meet.common.security; + +import org.groomUniv.meet.oauth.entity.Member; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + +public class CustomUserDetails implements UserDetails { + private final Member member; + + public CustomUserDetails(Member member) { + this.member = member; + } + + + @Override + public Collection getAuthorities() { + return member.getRoles().stream() + .map(role -> new SimpleGrantedAuthority(role.name())) + .collect(Collectors.toList()); + } + + @Override + public String getPassword() { + return member.getPassword(); + } + + @Override + public String getUsername() { + return member.getEmail(); + } + + @Override + public boolean isAccountNonExpired() { + return true; + } + + @Override + public boolean isAccountNonLocked() { + return true; + } + + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + @Override + public boolean isEnabled() { + return true; + } +} diff --git a/src/main/java/org/groomUniv/meet/common/security/CustomUserDetailsService.java b/src/main/java/org/groomUniv/meet/common/security/CustomUserDetailsService.java new file mode 100644 index 0000000..1a57937 --- /dev/null +++ b/src/main/java/org/groomUniv/meet/common/security/CustomUserDetailsService.java @@ -0,0 +1,37 @@ +package org.groomUniv.meet.common.security; + +import lombok.RequiredArgsConstructor; +import org.groomUniv.meet.common.apiPayload.error.CoreException; +import org.groomUniv.meet.common.apiPayload.error.GlobalErrorType; +import org.groomUniv.meet.oauth.entity.Member; +import org.groomUniv.meet.oauth.enums.Role; +import org.groomUniv.meet.oauth.repository.MemberRepository; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class CustomUserDetailsService implements UserDetailsService { + private final MemberRepository memberRepository; + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + return memberRepository.findByEmail(username) + .map(this::createUserDetails) + .orElseThrow(() -> new CoreException(GlobalErrorType.MEMBER_NOT_FOUND)); + } + + + private UserDetails createUserDetails(Member member) { + return User.builder() + .username(member.getEmail()) + .password(member.getPassword()) + .roles(member.getRoles().stream() + .map(Enum::name) + .toArray(String[]::new)) + .build(); + } +} diff --git a/src/main/java/org/groomUniv/meet/common/security/JwtAuthenticationFilter.java b/src/main/java/org/groomUniv/meet/common/security/JwtAuthenticationFilter.java new file mode 100644 index 0000000..6673ca5 --- /dev/null +++ b/src/main/java/org/groomUniv/meet/common/security/JwtAuthenticationFilter.java @@ -0,0 +1,66 @@ +package org.groomUniv.meet.common.security; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.jetbrains.annotations.NotNull; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.util.StringUtils; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; +import java.util.List; + +@Slf4j +@RequiredArgsConstructor +public class JwtAuthenticationFilter extends OncePerRequestFilter { + private final JwtTokenProvider jwtTokenProvider; + + private static final List NO_AUTH_PATHS = List.of( + "/api/member/signup", + "/api/member/login" + ); + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + + String path = request.getServletPath(); + // 인증이 필요 없는 경로는 필터 패스 + if (NO_AUTH_PATHS.contains(path)) { + log.info("No auth needed for path: {}", path); + filterChain.doFilter(request, response); + return; + } + + String token = resolveToken(request); // header에서 jwt 토큰을 추출 + if (token != null) { // 토큰이 존재하고 유효할 경우 + if(jwtTokenProvider.validateToken(token)) { + Authentication authentication = jwtTokenProvider.getAuthentication(token); // 토큰에서 Authentication 객체를 얻어 + SecurityContextHolder.getContext().setAuthentication(authentication); // Spring SecurityContext에 저장 + } + else{ + // 필터는 DispatcherServlet 이전에 동작하므로, 직접 JSON 형태로 응답을 만들어서 반환해야 함 + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); // 유효하지 않은 토큰: 401 Unauthorized 반환 + response.setContentType("application/json;charset=UTF-8"); // 응답의 ContentType을 json으로 지정 + response.getWriter().write("{\"error\": \"Invalid JWT token\"}"); // json 형식의 에러 메시지 + return; // 요청 중단, 다음 필터로 전달하지 않음 + } + + } + filterChain.doFilter(request, response); // 다음 필터로 요청을 전달(이걸 호출하지 않으면 요청이 중간에서 끊겨버려서 다음 단계 진행 불가) + } + + private String resolveToken(HttpServletRequest request) { + String bearerToken = request.getHeader("Authorization"); // 헤더에서 Authorization 값을 추출 + if(StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) { + return bearerToken.substring(7); // "Bearer " 이후의 문자열만 반환 + } + return null; + } + + +} diff --git a/src/main/java/org/groomUniv/meet/common/security/JwtToken.java b/src/main/java/org/groomUniv/meet/common/security/JwtToken.java new file mode 100644 index 0000000..de4aedc --- /dev/null +++ b/src/main/java/org/groomUniv/meet/common/security/JwtToken.java @@ -0,0 +1,11 @@ +package org.groomUniv.meet.common.security; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; + +public record JwtToken ( + String grantType, + String accessToken, + String refreshToken +){ } diff --git a/src/main/java/org/groomUniv/meet/common/security/JwtTokenProvider.java b/src/main/java/org/groomUniv/meet/common/security/JwtTokenProvider.java new file mode 100644 index 0000000..97b7506 --- /dev/null +++ b/src/main/java/org/groomUniv/meet/common/security/JwtTokenProvider.java @@ -0,0 +1,136 @@ +package org.groomUniv.meet.common.security; + +import io.jsonwebtoken.*; +import io.jsonwebtoken.io.Decoders; +import io.jsonwebtoken.security.Keys; +import jakarta.annotation.PostConstruct; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.core.Authentication; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.stereotype.Component; + +import java.security.Key; +import java.util.*; +import java.util.stream.Collectors; + +@Slf4j +@Component +public class JwtTokenProvider { + private Key key; + + @Value("${jwt.secret}") + String secret; + + @Value("${jwt.access-expiration-time}") + private long accessTokenExpirationTime; + + @Value("${jwt.refresh-expiration-time}") + private long refreshTokenExpirationTime; + + //application.yml에 저장한 secret 값을 가져와서 key에 저장하기 + @PostConstruct // 스프링 컨테이너가 빈을 생성하고, 모든 의존성 주입이 끝난 뒤 자동으로 호출되는 메서드, 주입된 값을 안전하게 사용 가능 + public void init() { + byte[] keyBytes = Decoders.BASE64.decode(secret); // secretKey (Base64로 인코딩된 문자열)를 디코딩하여 byte 배열로 변환 + key = Keys.hmacShaKeyFor(keyBytes); // 디코딩된 byte 배열을 사용하여 HMAC-SHA 알고리즘에 맞는 암호화 키 생성 + } + + + //Access Token 생성하기 + public String createAccessToken(String name, String authorities) { + Date now = new Date(); + Date accessTokenExpiresIn = new Date(now.getTime() + accessTokenExpirationTime); // 1시간 + + return Jwts.builder() // JWT 토큰을 생성하는 빌더 패턴 + .setSubject(name) // 토큰의 subject를 인증된 사용자 이름으로 설정 + .claim("auth", authorities) // auth 라는 키로 사용자의 권한 정보 저장 + .setExpiration(accessTokenExpiresIn) // 만료시간 설정(1시간) + .signWith(key, SignatureAlgorithm.HS256) // HS256 알고리즘을 이용해 서명 + .compact(); //토큰을 하나의 문자열로 생성 + } + + + //Refresh Token 생성하기 + public String createRefreshToken() { + Date now = new Date(); + Date refreshTokenExpiresIn = new Date(now.getTime() + refreshTokenExpirationTime); + + return Jwts.builder() + .setExpiration(refreshTokenExpiresIn) + .signWith(key, SignatureAlgorithm.HS256) + .compact(); + } + + + //Member 정보를 가지고 Access Token, Refresh Token 생성하기 + public JwtToken generateToken(Authentication authentication) { + String authorities = authentication.getAuthorities().stream() //UserDetails에서 권한 정보 가져오기 + .map(GrantedAuthority::getAuthority) // 권한을 String으로 추출 + .collect(Collectors.joining(", ")); // ","로 구분된 문자열로 결합 + + String name = authentication.getName(); + + return new JwtToken("Bearer", createAccessToken(name, authorities), createRefreshToken()); + } + + + //JWT 토큰을 복호화하여 토큰에 들어있는 정보를 꺼내기 + public Authentication getAuthentication(String accessToken) { + Claims claims = parseClaims(accessToken); //Jwt 토큰을 복호화하여 claims에 저장 + + if(claims.get("auth") == null) { // claims의 auth가 null이면 예외 throw + throw new RuntimeException("권한 정보가 없는 토큰입니다."); + } + + // claims를 확인하여 권한 정보 가져오기 + List authorities = + Arrays.stream(claims.get("auth").toString().split(",")) //auth 클레임을 가져와 쉼표로 구분된 권한 리스트 생성 + .map(SimpleGrantedAuthority::new) // 각 권한 문자열을 SimpleGrantedAuthority 객체로 변환 + .collect(Collectors.toList()); // 변환된 권한들을 List로 수집하여 authorities에 저장 + + UserDetails principal = new User(claims.getSubject(), "",authorities); + + // principal은 사용자 정보, authorities는 사용자의 권한 정보를 포함 + return new UsernamePasswordAuthenticationToken(principal, "", authorities); // 인증 객체 반환 + } + + + //토큰 유효성 검사 + public boolean validateToken(String token) { + try{ + Jwts.parserBuilder() + .setSigningKey(key) //Jwt 서명 검증에 사용할 키 설정 + .build() + .parseClaimsJws(token); // 토큰을 파싱하고 유효성 검증(서명, 구조, 만료 등 포함) + return true; // 예외가 발생하지 않으면 유효한 토큰 + + }catch(SecurityException | MalformedJwtException exception){ // 서명오류 또는 구조상 잘못된 토큰 + log.info("Invalid JWT token", exception); + }catch (ExpiredJwtException exception){ // 토큰이 만료 + log.info("Expired JWT token", exception); + }catch (UnsupportedJwtException exception){ //지원하지 않는 형식의 토큰 + log.info("Unsupported JWT token", exception); + }catch (IllegalArgumentException exception){ // 토큰이 비어있거나 null + log.info("JWT claims string is empty", exception); + } + return false; // 예외가 발생하면 유효하지 않은 토큰 + } + + + //accessToken을 파싱하여 claims를 추출하기 + private Claims parseClaims(String accessToken) { + try{ + return Jwts.parserBuilder() + .setSigningKey(key) + .build() + .parseClaimsJws(accessToken) + .getBody(); + }catch (ExpiredJwtException exception){ // 토큰이 만료되었을 경우 예외에서 claims만 추출하여 반환 + return exception.getClaims(); + } + } +} diff --git a/src/main/java/org/groomUniv/meet/meeting/entity/Meeting.java b/src/main/java/org/groomUniv/meet/meeting/entity/Meeting.java new file mode 100644 index 0000000..bb6d00b --- /dev/null +++ b/src/main/java/org/groomUniv/meet/meeting/entity/Meeting.java @@ -0,0 +1,31 @@ +package org.groomUniv.meet.meeting.entity; + +import jakarta.persistence.*; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.groomUniv.meet.blinddate.entity.BlindDateChat; +import org.groomUniv.meet.common.entity.BaseEntity; +import org.springframework.data.annotation.CreatedBy; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +@Entity +@NoArgsConstructor +public class Meeting extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long meetingId; + + + private String meetingDetails; + + + @OneToMany(mappedBy = "meeting", cascade = CascadeType.ALL) + private List chats = new ArrayList<>(); +} + diff --git a/src/main/java/org/groomUniv/meet/meeting/entity/MeetingChat.java b/src/main/java/org/groomUniv/meet/meeting/entity/MeetingChat.java new file mode 100644 index 0000000..09c06dc --- /dev/null +++ b/src/main/java/org/groomUniv/meet/meeting/entity/MeetingChat.java @@ -0,0 +1,36 @@ +package org.groomUniv.meet.meeting.entity; + +import jakarta.persistence.*; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.groomUniv.meet.common.entity.BaseEntity; +import org.groomUniv.meet.oauth.entity.Member; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import java.time.LocalDateTime; +import java.util.Date; + +@Entity +@NoArgsConstructor +public class MeetingChat extends BaseEntity { + + @Id + @GeneratedValue + private Long meetingChatId; + + private String message; + + private String senderId; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "meeting_id") + private Meeting meeting; + +} +// 생성 가이드라인 +//MeetingChat chat = new MeetingChat(); +//chat.setMeeting(meeting); // 여기서 meeting은 기존에 조회한 Meeting 객체 +//chat.setSenderId(1L); +//chat.setMessage("안녕하세요!"); +//chat.setTimestamp(LocalDateTime.now()); diff --git a/src/main/java/org/groomUniv/meet/meeting/repository/MeetingChatRepository.java b/src/main/java/org/groomUniv/meet/meeting/repository/MeetingChatRepository.java new file mode 100644 index 0000000..c3ae008 --- /dev/null +++ b/src/main/java/org/groomUniv/meet/meeting/repository/MeetingChatRepository.java @@ -0,0 +1,11 @@ +package org.groomUniv.meet.meeting.repository; + +import org.groomUniv.meet.meeting.entity.MeetingChat; +import org.groomUniv.meet.payment.entity.Payment; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface MeetingChatRepository extends JpaRepository { + Optional findById(Long meetingChatId); +} diff --git a/src/main/java/org/groomUniv/meet/meeting/repository/MeetingRepository.java b/src/main/java/org/groomUniv/meet/meeting/repository/MeetingRepository.java new file mode 100644 index 0000000..cbb338d --- /dev/null +++ b/src/main/java/org/groomUniv/meet/meeting/repository/MeetingRepository.java @@ -0,0 +1,17 @@ +package org.groomUniv.meet.meeting.repository; + +import org.groomUniv.meet.meeting.entity.Meeting; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; +import java.util.Optional; + +public interface MeetingRepository extends JpaRepository { + + //미팅 Id 기반으로 검색 + Optional findById(Long meetingId); + // 생성한 사람 기반으로 검색 + List findAllByCreatedBy(String createdBy); + // 미팅 내용으로 검색 , 키워드 기반 욕, 문제되는 발언 필터링 + List findByMeetingDetailsContaining(String keyword); +} diff --git a/src/main/java/org/groomUniv/meet/oauth/controller/MemberController.java b/src/main/java/org/groomUniv/meet/oauth/controller/MemberController.java new file mode 100644 index 0000000..539e298 --- /dev/null +++ b/src/main/java/org/groomUniv/meet/oauth/controller/MemberController.java @@ -0,0 +1,42 @@ +package org.groomUniv.meet.oauth.controller; + +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.groomUniv.meet.common.apiPayload.error.GlobalErrorType; +import org.groomUniv.meet.common.apiPayload.response.ApiResponse; +import org.groomUniv.meet.common.security.JwtToken; +import org.groomUniv.meet.oauth.dto.LoginRequest; +import org.groomUniv.meet.oauth.dto.SignUpRequest; +import org.groomUniv.meet.oauth.dto.SignUpResponse; +import org.groomUniv.meet.oauth.service.MemberService; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/member") +public class MemberController { + private final MemberService memberService; + + @PostMapping("/login") + public ApiResponse login(@Valid @RequestBody LoginRequest loginRequest){ + try{ + JwtToken jwtToken = memberService.login(loginRequest.email(), loginRequest.password()); + return ApiResponse.success(jwtToken); + }catch (Exception e){ + return ApiResponse.error("INVALID_EMAIL_OR_PASSWORD", "이메일 또는 비밀번호가 일치하지 않습니다."); + } + } + + @PostMapping("/signup") + public ApiResponse signUp(@Valid @RequestBody SignUpRequest signUpRequest){ + try{ + SignUpResponse signUpResponse = memberService.signUp(signUpRequest); + return ApiResponse.success(signUpResponse); + }catch (Exception e){ + return ApiResponse.error("DUPLICATE_EMAIL", "이미 존재하는 회원입니다"); + } + } +} diff --git a/src/main/java/org/groomUniv/meet/oauth/dto/LoginRequest.java b/src/main/java/org/groomUniv/meet/oauth/dto/LoginRequest.java new file mode 100644 index 0000000..e83e8c1 --- /dev/null +++ b/src/main/java/org/groomUniv/meet/oauth/dto/LoginRequest.java @@ -0,0 +1,15 @@ +package org.groomUniv.meet.oauth.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; + +public record LoginRequest ( + @NotBlank // 해당 값이 null이 아니고, 공백(""과 " " 모두 포함)이 아닌지 검증 + @Schema(description = "이메일", example = "abc1234@khu.ac.kr") + String email, + + @NotBlank + @Schema(description = "비밀번호", example = "1234") + String password +){ } diff --git a/src/main/java/org/groomUniv/meet/oauth/dto/SignUpRequest.java b/src/main/java/org/groomUniv/meet/oauth/dto/SignUpRequest.java new file mode 100644 index 0000000..c518127 --- /dev/null +++ b/src/main/java/org/groomUniv/meet/oauth/dto/SignUpRequest.java @@ -0,0 +1,31 @@ +package org.groomUniv.meet.oauth.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import org.groomUniv.meet.oauth.entity.Member; +import org.groomUniv.meet.oauth.enums.Role; + +import java.util.List; + +public record SignUpRequest( + @NotBlank + @Schema(description = "이메일", example = "abc1234@khu.ac.kr") + String email, + + @NotBlank + @Schema(description = "비밀번호", example = "1234") + String password, + + @NotBlank + @Schema(description = "이름", example = "홍길동") + String name +) { + public Member toMember(String encodedPassword) { + return Member.builder() + .email(email) + .password(encodedPassword) + .name(name) + .roles(List.of(Role.ROLE_USER)) + .build(); + } +} diff --git a/src/main/java/org/groomUniv/meet/oauth/dto/SignUpResponse.java b/src/main/java/org/groomUniv/meet/oauth/dto/SignUpResponse.java new file mode 100644 index 0000000..ddbd55b --- /dev/null +++ b/src/main/java/org/groomUniv/meet/oauth/dto/SignUpResponse.java @@ -0,0 +1,27 @@ +package org.groomUniv.meet.oauth.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import org.groomUniv.meet.oauth.entity.Member; +import org.groomUniv.meet.oauth.enums.Role; + +import java.util.List; + +public record SignUpResponse( + @Schema(description = "이메일", example = "abc1234@khu.ac.kr") + String email, + + @Schema(description = "이름", example = "홍길동") + String name, + + @Schema(description = "Role", example = "[\"ROLE_USER\", \"ROLE_ADMIN\"]") + List roles + +) { + public static SignUpResponse of(Member member) { + return new SignUpResponse( + member.getEmail(), + member.getName(), + member.getRoles() + ); + } +} diff --git a/src/main/java/org/groomUniv/meet/oauth/entity/Member.java b/src/main/java/org/groomUniv/meet/oauth/entity/Member.java new file mode 100644 index 0000000..adbd0e0 --- /dev/null +++ b/src/main/java/org/groomUniv/meet/oauth/entity/Member.java @@ -0,0 +1,50 @@ +package org.groomUniv.meet.oauth.entity; + +import jakarta.persistence.*; +import lombok.*; +import org.groomUniv.meet.oauth.enums.Role; + +import java.util.ArrayList; +import java.util.List; + +@NoArgsConstructor +@Entity +@Getter +@Table(name = "member") +@Builder +@AllArgsConstructor +public class Member { + +@Id +@GeneratedValue(strategy = GenerationType.IDENTITY) +private Long memberId; + +@OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL) +@JoinColumn(name = "preference_id") +private MemberPreference memberPreference; + + +@ManyToOne(fetch = FetchType.LAZY) +@JoinColumn(name = "school_id") +private School school; + + +private String email; +// password는 security로 암홓화해야함 +private String password; + +private String name; +private String image; + +private Long height; +private Long age; +private String biography; +private String major; + +private boolean emailVerified; + +@ElementCollection(fetch = FetchType.EAGER) +@Enumerated(EnumType.STRING) +private List roles = new ArrayList<>(); + +} diff --git a/src/main/java/org/groomUniv/meet/oauth/entity/MemberPreference.java b/src/main/java/org/groomUniv/meet/oauth/entity/MemberPreference.java new file mode 100644 index 0000000..203e28e --- /dev/null +++ b/src/main/java/org/groomUniv/meet/oauth/entity/MemberPreference.java @@ -0,0 +1,34 @@ +package org.groomUniv.meet.oauth.entity; + +import jakarta.persistence.*; +import lombok.Data; +import lombok.Getter; +import org.groomUniv.meet.oauth.enums.*; + +@Getter +@Entity +public class MemberPreference { +@Id +@GeneratedValue(strategy = GenerationType.IDENTITY) +private Long preference_id; + +@OneToOne(mappedBy = "memberPreference", fetch = FetchType.LAZY) +private Member member; + +private Long desired_age_min; +private Long desired_age_max; +private Long desired_height_min; +private Long desired_height_max; +private String desired_school; +private String desired_major; +private String additional_criteria; + +// enum타입으로 받아야할 것들 +private BodyType bodyType; +private Drinking drinking; +private Hobby hobby; +private Mbti mbti; +private Religion religion; +private Smoke smoke; +private Style style; +} diff --git a/src/main/java/org/groomUniv/meet/oauth/entity/Report.java b/src/main/java/org/groomUniv/meet/oauth/entity/Report.java new file mode 100644 index 0000000..9401d11 --- /dev/null +++ b/src/main/java/org/groomUniv/meet/oauth/entity/Report.java @@ -0,0 +1,30 @@ +package org.groomUniv.meet.oauth.entity; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import lombok.Data; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.springframework.data.annotation.CreatedDate; + +import java.time.LocalDateTime; + +@Entity +@Getter +@NoArgsConstructor +public class Report { +@Id +@GeneratedValue(strategy = GenerationType.IDENTITY) +private Long reportId; + +private String reportReason; +@CreatedDate +private LocalDateTime createdAt; + +private boolean rewardFlag; +private String reporterId; +private String reportedMemberId; + +} diff --git a/src/main/java/org/groomUniv/meet/oauth/entity/School.java b/src/main/java/org/groomUniv/meet/oauth/entity/School.java new file mode 100644 index 0000000..8540f0c --- /dev/null +++ b/src/main/java/org/groomUniv/meet/oauth/entity/School.java @@ -0,0 +1,24 @@ +package org.groomUniv.meet.oauth.entity; + +import jakarta.persistence.*; +import lombok.Data; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.ArrayList; +import java.util.List; +@NoArgsConstructor +@Entity +@Getter +@Table(name = "school") +public class School { +@Id@GeneratedValue(strategy = GenerationType.IDENTITY) + private Long school_id; +// 학교이름 +private String name; +// 학과 +private String domain; + + @OneToMany(mappedBy = "school", fetch = FetchType.LAZY) + private List members = new ArrayList<>(); +} diff --git a/src/main/java/org/groomUniv/meet/oauth/enums/BodyType.java b/src/main/java/org/groomUniv/meet/oauth/enums/BodyType.java new file mode 100644 index 0000000..54c0310 --- /dev/null +++ b/src/main/java/org/groomUniv/meet/oauth/enums/BodyType.java @@ -0,0 +1,11 @@ +package org.groomUniv.meet.oauth.enums; + +import lombok.AllArgsConstructor; + +@AllArgsConstructor +public enum BodyType { + THIN("마름"),NORMAL("보통"),FAT("통통"),MUSCLE("근육질"); + + private final String description; + +} diff --git a/src/main/java/org/groomUniv/meet/oauth/enums/Drinking.java b/src/main/java/org/groomUniv/meet/oauth/enums/Drinking.java new file mode 100644 index 0000000..8aeb34f --- /dev/null +++ b/src/main/java/org/groomUniv/meet/oauth/enums/Drinking.java @@ -0,0 +1,9 @@ +package org.groomUniv.meet.oauth.enums; + +import lombok.AllArgsConstructor; + +@AllArgsConstructor +public enum Drinking { +NEVER("안함"),SOMETIMES("가끔 마심"),OFTEN("자주"); +private final String description; +} diff --git a/src/main/java/org/groomUniv/meet/oauth/enums/Hobby.java b/src/main/java/org/groomUniv/meet/oauth/enums/Hobby.java new file mode 100644 index 0000000..13c8cac --- /dev/null +++ b/src/main/java/org/groomUniv/meet/oauth/enums/Hobby.java @@ -0,0 +1,11 @@ +package org.groomUniv.meet.oauth.enums; + +import lombok.AllArgsConstructor; + +@AllArgsConstructor +public enum Hobby { + +EXCERCISE("운동"), LISTENMUSIC("음악 듣기"),READBOOK("독서"), GAME("게임"), COOK("요리") + ,TRIP("여행"), DRAWING("그림"), TAKEPICTURE("사진찍기"); +private final String description; +} diff --git a/src/main/java/org/groomUniv/meet/oauth/enums/Mbti.java b/src/main/java/org/groomUniv/meet/oauth/enums/Mbti.java new file mode 100644 index 0000000..02cf300 --- /dev/null +++ b/src/main/java/org/groomUniv/meet/oauth/enums/Mbti.java @@ -0,0 +1,5 @@ +package org.groomUniv.meet.oauth.enums; + +public enum Mbti { + ISTJ, ISFJ, INFJ, INTJ, ISTP, ISFP, INFP, INTP, ESTP, ESFP, ENFP, ENTP, ESTJ, ESFJ, ENFJ, ENTJ +} diff --git a/src/main/java/org/groomUniv/meet/oauth/enums/Religion.java b/src/main/java/org/groomUniv/meet/oauth/enums/Religion.java new file mode 100644 index 0000000..11b8511 --- /dev/null +++ b/src/main/java/org/groomUniv/meet/oauth/enums/Religion.java @@ -0,0 +1,14 @@ +package org.groomUniv.meet.oauth.enums; + +import lombok.AllArgsConstructor; +import lombok.NoArgsConstructor; + + +@AllArgsConstructor +public enum Religion { + + NONE("무교"),Christianity("기독교"),Catholicism("천주교"),Buddhism("불교"), OTHERS("기타"); +private final String description; + + +} diff --git a/src/main/java/org/groomUniv/meet/oauth/enums/Role.java b/src/main/java/org/groomUniv/meet/oauth/enums/Role.java new file mode 100644 index 0000000..e87da49 --- /dev/null +++ b/src/main/java/org/groomUniv/meet/oauth/enums/Role.java @@ -0,0 +1,9 @@ +package org.groomUniv.meet.oauth.enums; + +import lombok.Getter; + +@Getter +public enum Role { + ROLE_USER, + ROLE_ADMIN +} diff --git a/src/main/java/org/groomUniv/meet/oauth/enums/Smoke.java b/src/main/java/org/groomUniv/meet/oauth/enums/Smoke.java new file mode 100644 index 0000000..dd93b71 --- /dev/null +++ b/src/main/java/org/groomUniv/meet/oauth/enums/Smoke.java @@ -0,0 +1,10 @@ +package org.groomUniv.meet.oauth.enums; + +import lombok.AllArgsConstructor; + +@AllArgsConstructor +public enum Smoke { + +NONE("안함"), SOMETIMES("가끔"), OFTEN("자주"); +private final String description; +} diff --git a/src/main/java/org/groomUniv/meet/oauth/enums/Style.java b/src/main/java/org/groomUniv/meet/oauth/enums/Style.java new file mode 100644 index 0000000..36f50c6 --- /dev/null +++ b/src/main/java/org/groomUniv/meet/oauth/enums/Style.java @@ -0,0 +1,10 @@ +package org.groomUniv.meet.oauth.enums; + +import lombok.AllArgsConstructor; + +@AllArgsConstructor +public enum Style { + +CASUAL("캐쥬얼"), STREET("스트릿"), MINIMAL("미니멀"),AMERICANCASUAL("아메카지, 아메리칸 캐쥬얼"); +private final String description; +} diff --git a/src/main/java/org/groomUniv/meet/oauth/repository/MemberRepository.java b/src/main/java/org/groomUniv/meet/oauth/repository/MemberRepository.java new file mode 100644 index 0000000..38f4696 --- /dev/null +++ b/src/main/java/org/groomUniv/meet/oauth/repository/MemberRepository.java @@ -0,0 +1,25 @@ +package org.groomUniv.meet.oauth.repository; + +import org.groomUniv.meet.oauth.entity.Member; +import org.groomUniv.meet.oauth.entity.School; +import org.groomUniv.meet.payment.entity.Payment; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; +import java.util.Optional; + +public interface MemberRepository extends JpaRepository { + +Optional findByEmail(String email); +Optional findByName(String name); +//학교별 모든 멤버 가져오는 + + Optional findAllBySchool(School school); + + boolean existsByEmail(String email); +} +// 간단한 사용법 +//School school = schoolRepository.findByName("경희대학교").orElseThrow(); +//List members = memberRepository.findAllBySchool(school); + + diff --git a/src/main/java/org/groomUniv/meet/oauth/repository/ReportRepository.java b/src/main/java/org/groomUniv/meet/oauth/repository/ReportRepository.java new file mode 100644 index 0000000..1dd0899 --- /dev/null +++ b/src/main/java/org/groomUniv/meet/oauth/repository/ReportRepository.java @@ -0,0 +1,18 @@ +package org.groomUniv.meet.oauth.repository; + +import org.groomUniv.meet.oauth.entity.Report; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.time.LocalDateTime; +import java.util.List; + +public interface ReportRepository extends JpaRepository { +// 신고자 ID로 조회 + List findAllByReporterId(String reporterId); +// 신고당한 사람의 ID로 조회 + List findAllByReportedMemberId(String reportedMemberId); +// 보상 받은 report들 혹은 못받은 report들 기준 + List findAllByRewardFlag(boolean rewardFlag); +// 특정 날짜 기준으로 그 기준 이후의 report들만 가져오기 + List findAllByCreatedAtAfter(LocalDateTime createdAt); +} diff --git a/src/main/java/org/groomUniv/meet/oauth/repository/SchoolRepository.java b/src/main/java/org/groomUniv/meet/oauth/repository/SchoolRepository.java new file mode 100644 index 0000000..d612637 --- /dev/null +++ b/src/main/java/org/groomUniv/meet/oauth/repository/SchoolRepository.java @@ -0,0 +1,11 @@ +package org.groomUniv.meet.oauth.repository; + +import org.groomUniv.meet.oauth.entity.School; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface SchoolRepository extends JpaRepository { +Optional findByName(String name); + +} diff --git a/src/main/java/org/groomUniv/meet/oauth/service/MemberService.java b/src/main/java/org/groomUniv/meet/oauth/service/MemberService.java new file mode 100644 index 0000000..9b0e73a --- /dev/null +++ b/src/main/java/org/groomUniv/meet/oauth/service/MemberService.java @@ -0,0 +1,12 @@ +package org.groomUniv.meet.oauth.service; + +import org.groomUniv.meet.common.security.JwtToken; +import org.groomUniv.meet.oauth.dto.SignUpRequest; +import org.groomUniv.meet.oauth.dto.SignUpResponse; + +public interface MemberService { + + public JwtToken login(String email, String password); + + public SignUpResponse signUp(SignUpRequest signUpRequest); +} diff --git a/src/main/java/org/groomUniv/meet/oauth/service/MemberServiceImpl.java b/src/main/java/org/groomUniv/meet/oauth/service/MemberServiceImpl.java new file mode 100644 index 0000000..8d9a139 --- /dev/null +++ b/src/main/java/org/groomUniv/meet/oauth/service/MemberServiceImpl.java @@ -0,0 +1,47 @@ +package org.groomUniv.meet.oauth.service; + +import lombok.RequiredArgsConstructor; +import org.groomUniv.meet.common.security.JwtToken; +import org.groomUniv.meet.common.security.JwtTokenProvider; +import org.groomUniv.meet.oauth.dto.SignUpRequest; +import org.groomUniv.meet.oauth.dto.SignUpResponse; +import org.groomUniv.meet.oauth.entity.Member; +import org.groomUniv.meet.oauth.repository.MemberRepository; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class MemberServiceImpl implements MemberService { + private final MemberRepository memberRepository; + private final AuthenticationManager authenticationManager; + private final JwtTokenProvider jwtTokenProvider; + private final PasswordEncoder passwordEncoder; + + @Transactional + @Override + public JwtToken login(String email, String password) { + UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(email, password); + Authentication authentication = authenticationManager.authenticate(authenticationToken); + return jwtTokenProvider.generateToken(authentication); + } + + @Transactional + @Override + public SignUpResponse signUp(SignUpRequest signUpRequest) { + + if (memberRepository.existsByEmail(signUpRequest.email())){ + throw new IllegalArgumentException("이미 사용중인 이메일 입니다."); + } + + String encodedPassword = passwordEncoder.encode(signUpRequest.password()); + Member member = memberRepository.save(signUpRequest.toMember(encodedPassword)); + return SignUpResponse.of(member); + + } +}