From c37b61649da07445e8a4a9628dd4af6f31e7b597 Mon Sep 17 00:00:00 2001 From: cloudwi Date: Wed, 7 Jun 2023 20:43:56 +0900 Subject: [PATCH 1/2] ... --- build.gradle | 4 ++ .../config/security/SecurityConfig.java | 43 +++++++++++++ .../oauth2/CustomOAuth2UserService.java | 52 ++++++++++++++++ .../security/oauth2/dto/OAuth2Attribute.java | 49 +++++++++++++++ .../member/dto/KakaoOAuth2UserInfo.java | 16 +++++ .../domain/member/entity/Member.java | 42 +++++++++++++ .../domain/member/entity/vo/Role.java | 18 ++++++ .../member/repository/MemberRepository.java | 10 +++ .../message/email/service/EmailService.java | 61 +++++++++++++++++++ .../error/CustomTelegramApiException.java | 2 +- .../member/domain/Member.java | 22 ------- .../email/service/EmailService.java | 27 -------- .../webClient/TelegramWebClient.java | 28 --------- .../presentation/security/SecurityConfig.java | 5 ++ src/main/resources/application.yml | 24 +++++++- 15 files changed, 324 insertions(+), 79 deletions(-) create mode 100644 src/main/java/com/project/musinsastocknotificationbot/common/config/security/SecurityConfig.java create mode 100644 src/main/java/com/project/musinsastocknotificationbot/common/config/security/oauth2/CustomOAuth2UserService.java create mode 100644 src/main/java/com/project/musinsastocknotificationbot/common/config/security/oauth2/dto/OAuth2Attribute.java create mode 100644 src/main/java/com/project/musinsastocknotificationbot/domain/member/dto/KakaoOAuth2UserInfo.java create mode 100644 src/main/java/com/project/musinsastocknotificationbot/domain/member/entity/Member.java create mode 100644 src/main/java/com/project/musinsastocknotificationbot/domain/member/entity/vo/Role.java create mode 100644 src/main/java/com/project/musinsastocknotificationbot/domain/member/repository/MemberRepository.java create mode 100644 src/main/java/com/project/musinsastocknotificationbot/infrastructure/message/email/service/EmailService.java rename src/main/java/com/project/musinsastocknotificationbot/{message/infrastructure => infrastructure/message}/telegramBot/error/CustomTelegramApiException.java (69%) delete mode 100644 src/main/java/com/project/musinsastocknotificationbot/member/domain/Member.java delete mode 100644 src/main/java/com/project/musinsastocknotificationbot/message/infrastructure/email/service/EmailService.java delete mode 100644 src/main/java/com/project/musinsastocknotificationbot/message/infrastructure/telegramBot/webClient/TelegramWebClient.java create mode 100644 src/main/java/com/project/musinsastocknotificationbot/presentation/security/SecurityConfig.java diff --git a/build.gradle b/build.gradle index 6316c32..5dbf4d6 100644 --- a/build.gradle +++ b/build.gradle @@ -28,6 +28,10 @@ dependencies { implementation 'org.telegram:telegrambotsextensions:6.5.0' //mail implementation 'org.springframework.boot:spring-boot-starter-mail' + //security + implementation 'org.springframework.boot:spring-boot-starter-security' + //oauth + implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' } tasks.named('test') { diff --git a/src/main/java/com/project/musinsastocknotificationbot/common/config/security/SecurityConfig.java b/src/main/java/com/project/musinsastocknotificationbot/common/config/security/SecurityConfig.java new file mode 100644 index 0000000..09fd2dd --- /dev/null +++ b/src/main/java/com/project/musinsastocknotificationbot/common/config/security/SecurityConfig.java @@ -0,0 +1,43 @@ +package com.project.musinsastocknotificationbot.common.config.security; + +import com.project.musinsastocknotificationbot.common.config.security.oauth2.CustomOAuth2UserService; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.web.SecurityFilterChain; + +@Configuration +@EnableWebSecurity +public class SecurityConfig { + + private final CustomOAuth2UserService customOAuth2UserService; + + public SecurityConfig(CustomOAuth2UserService customOAuth2UserService) { + this.customOAuth2UserService = customOAuth2UserService; + } + + @Bean + public BCryptPasswordEncoder bCryptPasswordEncoder() { + return new BCryptPasswordEncoder(); + } + + @Bean + public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception { + httpSecurity + .csrf().disable() + .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) + .and() + .formLogin().disable() + .httpBasic().disable() + .authorizeHttpRequests() + .requestMatchers("/api/*").permitAll() + .and() + .oauth2Login().userInfoEndpoint().userService(customOAuth2UserService); + + return httpSecurity.build(); + + } +} diff --git a/src/main/java/com/project/musinsastocknotificationbot/common/config/security/oauth2/CustomOAuth2UserService.java b/src/main/java/com/project/musinsastocknotificationbot/common/config/security/oauth2/CustomOAuth2UserService.java new file mode 100644 index 0000000..37430e5 --- /dev/null +++ b/src/main/java/com/project/musinsastocknotificationbot/common/config/security/oauth2/CustomOAuth2UserService.java @@ -0,0 +1,52 @@ +package com.project.musinsastocknotificationbot.common.config.security.oauth2; + +import com.project.musinsastocknotificationbot.common.config.security.oauth2.dto.OAuth2Attribute; +import com.project.musinsastocknotificationbot.domain.member.entity.Member; +import com.project.musinsastocknotificationbot.domain.member.repository.MemberRepository; +import java.util.Collections; +import org.h2.engine.Role; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; +import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; +import org.springframework.security.oauth2.client.userinfo.OAuth2UserService; +import org.springframework.security.oauth2.core.OAuth2AuthenticationException; +import org.springframework.security.oauth2.core.user.DefaultOAuth2User; +import org.springframework.security.oauth2.core.user.OAuth2User; + +public class CustomOAuth2UserService implements OAuth2UserService { + + private final MemberRepository memberRepository; + + public CustomOAuth2UserService(MemberRepository memberRepository) { + this.memberRepository = memberRepository; + } + + @Override + public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { + OAuth2UserService oAuth2UserService = new DefaultOAuth2UserService(); + OAuth2User oAuth2User = oAuth2UserService.loadUser(userRequest); + + String registrationId = userRequest.getClientRegistration() + .getRegistrationId(); // kakao + String userNameAttributeName = userRequest.getClientRegistration() + .getProviderDetails() + .getUserInfoEndpoint() + .getUserNameAttributeName(); + + OAuth2Attribute oAuth2Attribute = OAuth2Attribute.of(registrationId, userNameAttributeName, oAuth2User.getAttributes()); + + Member member = saveOrUpdate(oAuth2Attribute); + + return new DefaultOAuth2User( + Collections.singleton(new SimpleGrantedAuthority(member.getRoleKey())), + oAuth2Attribute.convertToMap(), userNameAttributeName); + } + + private Member saveOrUpdate(OAuth2Attribute oAuth2Attribute) { + Member member = memberRepository.findByEmail(oAuth2Attribute.email()) + .map(entity -> entity.update(oAuth2Attribute.provider())) + .orElse(oAuth2Attribute.toEntity()); + + return memberRepository.save(member); + } +} diff --git a/src/main/java/com/project/musinsastocknotificationbot/common/config/security/oauth2/dto/OAuth2Attribute.java b/src/main/java/com/project/musinsastocknotificationbot/common/config/security/oauth2/dto/OAuth2Attribute.java new file mode 100644 index 0000000..6d3e067 --- /dev/null +++ b/src/main/java/com/project/musinsastocknotificationbot/common/config/security/oauth2/dto/OAuth2Attribute.java @@ -0,0 +1,49 @@ +package com.project.musinsastocknotificationbot.common.config.security.oauth2.dto; + +import com.project.musinsastocknotificationbot.domain.member.entity.Member; +import com.project.musinsastocknotificationbot.domain.member.entity.vo.Role; +import java.util.HashMap; +import java.util.Map; + +public record OAuth2Attribute( + Map attributes, + String provider, + String attributeKey, + String email +) { + + public static OAuth2Attribute of(String provider, String attributeKey, + Map attributes) { + switch (provider) { + case "kakao" -> { + return ofKakao(provider, attributeKey, attributes); + } + default -> { + throw new RuntimeException("정상적인 url이 아닙니다."); + } + } + } + + private static OAuth2Attribute ofKakao(String provider, String attributeKey, + Map attributes) { + Map kakaoAccount = (Map) attributes.get("kakao_account"); + + return new OAuth2Attribute(attributes, provider, attributeKey, + (String) kakaoAccount.get("email") + ); + } + + public Map convertToMap() { + Map map = new HashMap<>(); + map.put("id", this.attributes); + map.put("provider", this.provider); + map.put("attributeKey", this.attributeKey); + map.put("email", this.email); + + return map; + } + + public Member toEntity() { + return new Member(this.email, this.provider, Role.USER); + } +} diff --git a/src/main/java/com/project/musinsastocknotificationbot/domain/member/dto/KakaoOAuth2UserInfo.java b/src/main/java/com/project/musinsastocknotificationbot/domain/member/dto/KakaoOAuth2UserInfo.java new file mode 100644 index 0000000..a078bdf --- /dev/null +++ b/src/main/java/com/project/musinsastocknotificationbot/domain/member/dto/KakaoOAuth2UserInfo.java @@ -0,0 +1,16 @@ +package com.project.musinsastocknotificationbot.domain.member.dto; + +import java.util.Map; + +public class KakaoOAuth2UserInfo { + + private Map attributes; + + public KakaoOAuth2UserInfo(Map attributes) { + this.attributes = attributes; + } + + public String getEmail() { + return (String) attributes.get("email"); + } +} diff --git a/src/main/java/com/project/musinsastocknotificationbot/domain/member/entity/Member.java b/src/main/java/com/project/musinsastocknotificationbot/domain/member/entity/Member.java new file mode 100644 index 0000000..f25a8eb --- /dev/null +++ b/src/main/java/com/project/musinsastocknotificationbot/domain/member/entity/Member.java @@ -0,0 +1,42 @@ +package com.project.musinsastocknotificationbot.domain.member.entity; + +import com.project.musinsastocknotificationbot.common.entity.BaseTimeEntity; +import com.project.musinsastocknotificationbot.domain.member.entity.vo.Role; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.Id; + +@Entity +public class Member extends BaseTimeEntity { + + @Id + private Long id; + + @Column + private String email; + + @Column + private String provider; + + @Enumerated(EnumType.STRING) + Role role; + + protected Member() {} + + public Member(String email, String provider, Role role) { + this.email = email; + this.provider = provider; + this.role = role; + } + + public Member update(String provider) { + this.provider = provider; + return this; + } + + public String getRoleKey() { + return this.role.getKey(); + } +} diff --git a/src/main/java/com/project/musinsastocknotificationbot/domain/member/entity/vo/Role.java b/src/main/java/com/project/musinsastocknotificationbot/domain/member/entity/vo/Role.java new file mode 100644 index 0000000..ea6fed3 --- /dev/null +++ b/src/main/java/com/project/musinsastocknotificationbot/domain/member/entity/vo/Role.java @@ -0,0 +1,18 @@ +package com.project.musinsastocknotificationbot.domain.member.entity.vo; + +public enum Role { + GUEST("ROLE_GUEST", "손님"), + USER("ROLE_USER", "일반 사용자"); + + private final String key; + private final String title; + + Role(String key, String title) { + this.key = key; + this.title = title; + } + + public String getKey() { + return key; + } +} diff --git a/src/main/java/com/project/musinsastocknotificationbot/domain/member/repository/MemberRepository.java b/src/main/java/com/project/musinsastocknotificationbot/domain/member/repository/MemberRepository.java new file mode 100644 index 0000000..14c4d89 --- /dev/null +++ b/src/main/java/com/project/musinsastocknotificationbot/domain/member/repository/MemberRepository.java @@ -0,0 +1,10 @@ +package com.project.musinsastocknotificationbot.domain.member.repository; + +import com.project.musinsastocknotificationbot.domain.member.entity.Member; +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface MemberRepository extends JpaRepository { + + Optional findByEmail(String email); +} diff --git a/src/main/java/com/project/musinsastocknotificationbot/infrastructure/message/email/service/EmailService.java b/src/main/java/com/project/musinsastocknotificationbot/infrastructure/message/email/service/EmailService.java new file mode 100644 index 0000000..562c7b8 --- /dev/null +++ b/src/main/java/com/project/musinsastocknotificationbot/infrastructure/message/email/service/EmailService.java @@ -0,0 +1,61 @@ +package com.project.musinsastocknotificationbot.infrastructure.message.email.service; + +import com.project.musinsastocknotificationbot.infrastructure.message.service.MessageService; +import jakarta.mail.MessagingException; +import jakarta.mail.internet.MimeMessage; +import java.io.UnsupportedEncodingException; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Profile; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@Transactional(readOnly = true) +@Profile("email") +public class EmailService implements MessageService { + + private final JavaMailSender javaMailSender; + private final String from; + + public EmailService(JavaMailSender javaMailSender, @Value("${spring.mail.username}") String from) { + this.javaMailSender = javaMailSender; + this.from = from; + } + + @Override + public void sendMessage(String message) { + + } + + //메일 양식 작성 + public MimeMessage createEmailForm(String email) { + + String setFrom = "cloudwiiiii@gmail.com"; //email-config에 설정한 자신의 이메일 주소(보내는 사람) + String toEmail = email; //받는 사람 + String title = "every-time-clone-web"; + String content = "내용"; + + MimeMessage message = javaMailSender.createMimeMessage(); + + try { + message.addRecipients(MimeMessage.RecipientType.TO, toEmail); //보낼 이메일 설정 + message.setSubject(title); + message.setText(content);//제목 설정 + message.setFrom(setFrom); //보내는 이메일 + } catch (MessagingException e) { + throw new RuntimeException(e); + } + + + return message; + } + + //실제 메일 전송 + public String sendEmail(String toEmail) throws MessagingException, UnsupportedEncodingException { + MimeMessage emailForm = createEmailForm(toEmail); + javaMailSender.send(emailForm); + + return null; //인증 코드 반환 + } +} diff --git a/src/main/java/com/project/musinsastocknotificationbot/message/infrastructure/telegramBot/error/CustomTelegramApiException.java b/src/main/java/com/project/musinsastocknotificationbot/infrastructure/message/telegramBot/error/CustomTelegramApiException.java similarity index 69% rename from src/main/java/com/project/musinsastocknotificationbot/message/infrastructure/telegramBot/error/CustomTelegramApiException.java rename to src/main/java/com/project/musinsastocknotificationbot/infrastructure/message/telegramBot/error/CustomTelegramApiException.java index 5b0fd96..f81c413 100644 --- a/src/main/java/com/project/musinsastocknotificationbot/message/infrastructure/telegramBot/error/CustomTelegramApiException.java +++ b/src/main/java/com/project/musinsastocknotificationbot/infrastructure/message/telegramBot/error/CustomTelegramApiException.java @@ -1,4 +1,4 @@ -package com.project.musinsastocknotificationbot.message.infrastructure.telegramBot.error; +package com.project.musinsastocknotificationbot.infrastructure.message.telegramBot.error; import org.telegram.telegrambots.meta.exceptions.TelegramApiException; diff --git a/src/main/java/com/project/musinsastocknotificationbot/member/domain/Member.java b/src/main/java/com/project/musinsastocknotificationbot/member/domain/Member.java deleted file mode 100644 index 0e12cf8..0000000 --- a/src/main/java/com/project/musinsastocknotificationbot/member/domain/Member.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.project.musinsastocknotificationbot.member.domain; - -import com.project.musinsastocknotificationbot.common.entity.BaseTimeEntity; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.Id; - -@Entity -public class Member extends BaseTimeEntity { - - @Id - private Long id; - - @Column - private String chatId; - - protected Member() {} - - public Member(String chatId) { - this.chatId = chatId; - } -} diff --git a/src/main/java/com/project/musinsastocknotificationbot/message/infrastructure/email/service/EmailService.java b/src/main/java/com/project/musinsastocknotificationbot/message/infrastructure/email/service/EmailService.java deleted file mode 100644 index b7de226..0000000 --- a/src/main/java/com/project/musinsastocknotificationbot/message/infrastructure/email/service/EmailService.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.project.musinsastocknotificationbot.message.infrastructure.email.service; - -import com.project.musinsastocknotificationbot.message.service.MessageService; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Profile; -import org.springframework.mail.javamail.JavaMailSender; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -@Service -@Transactional(readOnly = true) -@Profile("email") -public class EmailService implements MessageService { - - private final JavaMailSender javaMailSender; - private final String from; - - public EmailService(JavaMailSender javaMailSender, @Value("${spring.mail.username}") String from) { - this.javaMailSender = javaMailSender; - this.from = from; - } - - @Override - public void sendMessage(String message) { - - } -} diff --git a/src/main/java/com/project/musinsastocknotificationbot/message/infrastructure/telegramBot/webClient/TelegramWebClient.java b/src/main/java/com/project/musinsastocknotificationbot/message/infrastructure/telegramBot/webClient/TelegramWebClient.java deleted file mode 100644 index ec073c4..0000000 --- a/src/main/java/com/project/musinsastocknotificationbot/message/infrastructure/telegramBot/webClient/TelegramWebClient.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.project.musinsastocknotificationbot.message.infrastructure.telegramBot.webClient; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.web.reactive.function.client.WebClient; -import org.springframework.web.util.DefaultUriBuilderFactory; - -public class TelegramWebClient { - - private final WebClient webClient; - DefaultUriBuilderFactory defaultUriBuilderFactory; - - public TelegramWebClient(@Value("${secret.telegramToken}") String token) { - String BASE_URL = "https://api.telegram.org/bot" + token + "/sendmessage"; - - defaultUriBuilderFactory = new DefaultUriBuilderFactory(BASE_URL); - defaultUriBuilderFactory.setEncodingMode(DefaultUriBuilderFactory.EncodingMode.VALUES_ONLY); - this.webClient = WebClient.builder() - .uriBuilderFactory(defaultUriBuilderFactory) - .baseUrl(BASE_URL) - .build(); - } - - public WebClient getWebClient() { - return webClient; - } - - -} diff --git a/src/main/java/com/project/musinsastocknotificationbot/presentation/security/SecurityConfig.java b/src/main/java/com/project/musinsastocknotificationbot/presentation/security/SecurityConfig.java new file mode 100644 index 0000000..44d0152 --- /dev/null +++ b/src/main/java/com/project/musinsastocknotificationbot/presentation/security/SecurityConfig.java @@ -0,0 +1,5 @@ +package com.project.musinsastocknotificationbot.presentation.security; + +public class SecurityConfig { + +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index c6d7afb..2aea94c 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -33,6 +33,26 @@ spring: database: h2 show-sql: true + security: + oauth2: + client: + registration: + kakao: + client-id: ${oauth2_kakao_client-id} + redirect-uri: http://localhost:8080/api/login/oauth2/code/kakao + client-authentication-method: POST + client-secret: ${client-secret} + scope: + - account_email + client_name: kakao + + provider: + kakao: + authorization-uri: https://kauth.kakao.com/oauth/authorize + token-uri: https://kauth.kakao.com/oauth/token + user-info-uri: https://kapi.kakao.com/v2/user/me + user-name-attribute: id + mail: host: smtp.naver.com port: 465 @@ -47,4 +67,6 @@ spring: secret: telegramToken: ${telegramToken} chat_id: ${chat_id} - bot_name: ${bot_name} \ No newline at end of file + bot_name: ${bot_name} + oauth2_kakao_client-id: ${oauth2_kakao_client-id} + client-secret: ${client-secret} \ No newline at end of file From 8ac563ee49ab45c1c3b906fc99d196ca635acfda Mon Sep 17 00:00:00 2001 From: cloudwi Date: Fri, 16 Jun 2023 10:39:24 +0900 Subject: [PATCH 2/2] =?UTF-8?q?feat=20:=20OAuth2=20=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8=20=EA=B8=B0=EB=8A=A5=20=ED=83=91=EC=9E=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 6 ++ .../config/security/SecurityConfig.java | 47 +++++++++-- .../jwt/CustomUserDetailsService.java | 23 ++++++ .../config/security/jwt/JwtTokenProvider.java | 82 +++++++++++++++++++ .../oauth2/CustomOAuth2UserService.java | 30 ++----- .../security/oauth2/OAuth2Attribute.java | 48 +++++++++++ .../OAuth2AuthenticationSuccessHandler.java | 70 ++++++++++++++++ .../config/security/oauth2/Provider.java | 25 ++++++ .../security/oauth2/dto/OAuth2Attribute.java | 49 ----------- .../common/{config => filter}/CorsFilter.java | 4 +- .../filter/JwtAuthenticationFilter.java | 35 ++++++++ .../domain/member/entity/Member.java | 70 ++++++++++++++-- .../member/error/MemberNotFoundException.java | 6 ++ .../domain/member/service/MemberService.java | 21 +++++ .../message/email/service/EmailService.java | 6 +- .../telegramBot/domain/TelegramBot.java | 2 + .../service/TelegramMessageServiceImpl.java | 2 + src/main/resources/application.yml | 37 +++++---- 18 files changed, 455 insertions(+), 108 deletions(-) create mode 100644 src/main/java/com/project/musinsastocknotificationbot/common/config/security/jwt/CustomUserDetailsService.java create mode 100644 src/main/java/com/project/musinsastocknotificationbot/common/config/security/jwt/JwtTokenProvider.java create mode 100644 src/main/java/com/project/musinsastocknotificationbot/common/config/security/oauth2/OAuth2Attribute.java create mode 100644 src/main/java/com/project/musinsastocknotificationbot/common/config/security/oauth2/OAuth2AuthenticationSuccessHandler.java create mode 100644 src/main/java/com/project/musinsastocknotificationbot/common/config/security/oauth2/Provider.java delete mode 100644 src/main/java/com/project/musinsastocknotificationbot/common/config/security/oauth2/dto/OAuth2Attribute.java rename src/main/java/com/project/musinsastocknotificationbot/common/{config => filter}/CorsFilter.java (94%) create mode 100644 src/main/java/com/project/musinsastocknotificationbot/common/filter/JwtAuthenticationFilter.java create mode 100644 src/main/java/com/project/musinsastocknotificationbot/domain/member/error/MemberNotFoundException.java create mode 100644 src/main/java/com/project/musinsastocknotificationbot/domain/member/service/MemberService.java diff --git a/build.gradle b/build.gradle index 5dbf4d6..a4e4927 100644 --- a/build.gradle +++ b/build.gradle @@ -30,8 +30,14 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-mail' //security implementation 'org.springframework.boot:spring-boot-starter-security' + testImplementation 'org.springframework.security:spring-security-test' //oauth implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' + //jwt + // 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' } tasks.named('test') { diff --git a/src/main/java/com/project/musinsastocknotificationbot/common/config/security/SecurityConfig.java b/src/main/java/com/project/musinsastocknotificationbot/common/config/security/SecurityConfig.java index 09fd2dd..48eb932 100644 --- a/src/main/java/com/project/musinsastocknotificationbot/common/config/security/SecurityConfig.java +++ b/src/main/java/com/project/musinsastocknotificationbot/common/config/security/SecurityConfig.java @@ -1,22 +1,37 @@ package com.project.musinsastocknotificationbot.common.config.security; import com.project.musinsastocknotificationbot.common.config.security.oauth2.CustomOAuth2UserService; +import com.project.musinsastocknotificationbot.common.config.security.oauth2.OAuth2AuthenticationSuccessHandler; +import com.project.musinsastocknotificationbot.common.filter.JwtAuthenticationFilter; +import java.util.Arrays; +import java.util.List; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.CorsConfigurationSource; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; @Configuration @EnableWebSecurity public class SecurityConfig { private final CustomOAuth2UserService customOAuth2UserService; + private final OAuth2AuthenticationSuccessHandler oAuth2AuthorizationSuccessHandler; + private final JwtAuthenticationFilter jwtAuthenticationFilter; - public SecurityConfig(CustomOAuth2UserService customOAuth2UserService) { + public SecurityConfig(CustomOAuth2UserService customOAuth2UserService, + OAuth2AuthenticationSuccessHandler oAuth2AuthorizationSuccessHandler, + JwtAuthenticationFilter jwtAuthenticationFilter) { this.customOAuth2UserService = customOAuth2UserService; + this.oAuth2AuthorizationSuccessHandler = oAuth2AuthorizationSuccessHandler; + this.jwtAuthenticationFilter = jwtAuthenticationFilter; } @Bean @@ -25,19 +40,39 @@ public BCryptPasswordEncoder bCryptPasswordEncoder() { } @Bean - public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception { - httpSecurity + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + http .csrf().disable() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .formLogin().disable() .httpBasic().disable() .authorizeHttpRequests() - .requestMatchers("/api/*").permitAll() + .requestMatchers(HttpMethod.OPTIONS).permitAll() + .requestMatchers("/h2-console/**", "favicon.ico").permitAll() + .anyRequest().permitAll() .and() - .oauth2Login().userInfoEndpoint().userService(customOAuth2UserService); + .headers().frameOptions().sameOrigin() + .and() + .oauth2Login() + .userInfoEndpoint().userService(customOAuth2UserService) + .and() + .successHandler(oAuth2AuthorizationSuccessHandler); - return httpSecurity.build(); + http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); + return http.build(); + } + + @Bean + CorsConfigurationSource corsConfigurationSource() { + CorsConfiguration configuration = new CorsConfiguration(); + configuration.setAllowedOrigins(Arrays.asList("http://localhost:3000")); + configuration.setAllowedMethods(Arrays.asList("*")); + configuration.setAllowedHeaders(List.of("*")); + configuration.setAllowCredentials(true); + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/**", configuration); + return source; } } diff --git a/src/main/java/com/project/musinsastocknotificationbot/common/config/security/jwt/CustomUserDetailsService.java b/src/main/java/com/project/musinsastocknotificationbot/common/config/security/jwt/CustomUserDetailsService.java new file mode 100644 index 0000000..0489523 --- /dev/null +++ b/src/main/java/com/project/musinsastocknotificationbot/common/config/security/jwt/CustomUserDetailsService.java @@ -0,0 +1,23 @@ +package com.project.musinsastocknotificationbot.common.config.security.jwt; + +import com.project.musinsastocknotificationbot.domain.member.service.MemberService; +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 +public class CustomUserDetailsService implements UserDetailsService { + + private final MemberService memberService; + + public CustomUserDetailsService(MemberService memberService) { + this.memberService = memberService; + } + + @Override + public UserDetails loadUserByUsername(String memberId) throws UsernameNotFoundException { + long id = Long.parseLong(memberId); + return memberService.findById(id); + } +} diff --git a/src/main/java/com/project/musinsastocknotificationbot/common/config/security/jwt/JwtTokenProvider.java b/src/main/java/com/project/musinsastocknotificationbot/common/config/security/jwt/JwtTokenProvider.java new file mode 100644 index 0000000..007e23a --- /dev/null +++ b/src/main/java/com/project/musinsastocknotificationbot/common/config/security/jwt/JwtTokenProvider.java @@ -0,0 +1,82 @@ +package com.project.musinsastocknotificationbot.common.config.security.jwt; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import jakarta.servlet.http.HttpServletRequest; +import java.time.Duration; +import java.util.Date; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.stereotype.Component; + +@Component +public class JwtTokenProvider { + + private final CustomUserDetailsService customUserDetailsService; + private final String tokenSecretKey; + private final String jwtTokenHeaderName; + private static final long ACCESS_TOKEN_EXPIRED_TIME = Duration.ofMinutes(1).toMillis(); + + public JwtTokenProvider(CustomUserDetailsService customUserDetailsService, @Value("${jwt.secret}") String tokenSecretKey, + @Value("${jwt.header}") String jwtTokenHeaderName) { + this.customUserDetailsService = customUserDetailsService; + this.tokenSecretKey = tokenSecretKey; + this.jwtTokenHeaderName = jwtTokenHeaderName; + } + + public String createToken(long memberId) { + Claims claims = Jwts.claims().setSubject(String.valueOf(memberId)); + + Date now = new Date(); + Date validity = new Date(now.getTime() + ACCESS_TOKEN_EXPIRED_TIME); + + return Jwts.builder() + .setClaims(claims) + .setIssuedAt(now) + .setExpiration(validity) + .signWith(SignatureAlgorithm.HS256, tokenSecretKey) + .compact(); + } + + public String removeBearer(String bearerToken) { + return bearerToken.substring("Bearer ".length()); + } + + public boolean validateToken(String token) { + try { + Jwts.parser().setSigningKey(tokenSecretKey).parseClaimsJws(token); + return true; + } catch (Exception e) { + return false; + } + } + + public String getMemberId(String token) { + return Jwts.parser() + .setSigningKey(tokenSecretKey) + .parseClaimsJws(token) + .getBody() + .getSubject(); + } + + public Authentication getAuthentication(String token) { + String memberId = getMemberId(token); + UserDetails userDetails = customUserDetailsService.loadUserByUsername(memberId); + return new UsernamePasswordAuthenticationToken(userDetails, "", userDetails.getAuthorities()); + } + + + + public String resolveToken(HttpServletRequest request) { + String token = request.getHeader(jwtTokenHeaderName); + + if (token != null) { + return removeBearer(token); + } else { + return null; + } + } +} diff --git a/src/main/java/com/project/musinsastocknotificationbot/common/config/security/oauth2/CustomOAuth2UserService.java b/src/main/java/com/project/musinsastocknotificationbot/common/config/security/oauth2/CustomOAuth2UserService.java index 37430e5..624bdfa 100644 --- a/src/main/java/com/project/musinsastocknotificationbot/common/config/security/oauth2/CustomOAuth2UserService.java +++ b/src/main/java/com/project/musinsastocknotificationbot/common/config/security/oauth2/CustomOAuth2UserService.java @@ -1,10 +1,7 @@ package com.project.musinsastocknotificationbot.common.config.security.oauth2; -import com.project.musinsastocknotificationbot.common.config.security.oauth2.dto.OAuth2Attribute; -import com.project.musinsastocknotificationbot.domain.member.entity.Member; -import com.project.musinsastocknotificationbot.domain.member.repository.MemberRepository; +import com.project.musinsastocknotificationbot.domain.member.entity.vo.Role; import java.util.Collections; -import org.h2.engine.Role; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; @@ -12,41 +9,28 @@ import org.springframework.security.oauth2.core.OAuth2AuthenticationException; import org.springframework.security.oauth2.core.user.DefaultOAuth2User; import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.stereotype.Service; +@Service public class CustomOAuth2UserService implements OAuth2UserService { - private final MemberRepository memberRepository; - - public CustomOAuth2UserService(MemberRepository memberRepository) { - this.memberRepository = memberRepository; - } - @Override public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { OAuth2UserService oAuth2UserService = new DefaultOAuth2UserService(); OAuth2User oAuth2User = oAuth2UserService.loadUser(userRequest); String registrationId = userRequest.getClientRegistration() - .getRegistrationId(); // kakao + .getRegistrationId(); + String userNameAttributeName = userRequest.getClientRegistration() .getProviderDetails() .getUserInfoEndpoint() .getUserNameAttributeName(); - OAuth2Attribute oAuth2Attribute = OAuth2Attribute.of(registrationId, userNameAttributeName, oAuth2User.getAttributes()); - - Member member = saveOrUpdate(oAuth2Attribute); + OAuth2Attribute oAuth2Attribute = OAuth2Attribute.of(Provider.of(registrationId), oAuth2User.getAttributes()); return new DefaultOAuth2User( - Collections.singleton(new SimpleGrantedAuthority(member.getRoleKey())), + Collections.singleton(new SimpleGrantedAuthority(Role.USER.getKey())), oAuth2Attribute.convertToMap(), userNameAttributeName); } - - private Member saveOrUpdate(OAuth2Attribute oAuth2Attribute) { - Member member = memberRepository.findByEmail(oAuth2Attribute.email()) - .map(entity -> entity.update(oAuth2Attribute.provider())) - .orElse(oAuth2Attribute.toEntity()); - - return memberRepository.save(member); - } } diff --git a/src/main/java/com/project/musinsastocknotificationbot/common/config/security/oauth2/OAuth2Attribute.java b/src/main/java/com/project/musinsastocknotificationbot/common/config/security/oauth2/OAuth2Attribute.java new file mode 100644 index 0000000..29b9aa6 --- /dev/null +++ b/src/main/java/com/project/musinsastocknotificationbot/common/config/security/oauth2/OAuth2Attribute.java @@ -0,0 +1,48 @@ +package com.project.musinsastocknotificationbot.common.config.security.oauth2; + +import java.util.HashMap; +import java.util.Map; + +public record OAuth2Attribute( + Map attributes, + Provider provider, + String email +) { + + public static OAuth2Attribute of(Provider provider , + Map attributes) { + switch (provider) { + case NAVER -> { + return ofNaver(provider, attributes); + } + case KAKAO -> { + return ofKakao(provider, attributes); + } + default -> throw new RuntimeException("정상적인 url이 아닙니다."); + + } + } + + private static OAuth2Attribute ofNaver(Provider provider, + Map attributes) { + Map response = (Map) attributes.get("response"); + + return new OAuth2Attribute(attributes, provider, (String) response.get("email")); + } + + private static OAuth2Attribute ofKakao(Provider provider, + Map attributes) { + Map kakaoAccount = (Map) attributes.get("kakao_account"); + + return new OAuth2Attribute(attributes, provider, (String) kakaoAccount.get("email")); + } + + public Map convertToMap() { + Map map = new HashMap<>(); + map.put("response", this.attributes); + map.put("provider", this.provider); + map.put("email", this.email); + + return map; + } +} diff --git a/src/main/java/com/project/musinsastocknotificationbot/common/config/security/oauth2/OAuth2AuthenticationSuccessHandler.java b/src/main/java/com/project/musinsastocknotificationbot/common/config/security/oauth2/OAuth2AuthenticationSuccessHandler.java new file mode 100644 index 0000000..2ae3642 --- /dev/null +++ b/src/main/java/com/project/musinsastocknotificationbot/common/config/security/oauth2/OAuth2AuthenticationSuccessHandler.java @@ -0,0 +1,70 @@ +package com.project.musinsastocknotificationbot.common.config.security.oauth2; + +import com.project.musinsastocknotificationbot.common.config.security.jwt.JwtTokenProvider; +import com.project.musinsastocknotificationbot.domain.member.entity.Member; +import com.project.musinsastocknotificationbot.domain.member.entity.vo.Role; +import com.project.musinsastocknotificationbot.domain.member.repository.MemberRepository; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.Map; +import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; +import org.springframework.stereotype.Component; +import org.springframework.web.util.UriComponentsBuilder; + +@Component +public class OAuth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler { + + private final MemberRepository memberRepository; + private final JwtTokenProvider jwtTokenProvider; + + public OAuth2AuthenticationSuccessHandler(MemberRepository memberRepository, + JwtTokenProvider jwtTokenProvider) { + this.memberRepository = memberRepository; + this.jwtTokenProvider = jwtTokenProvider; + } + + @Override + public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, + Authentication authentication) throws IOException { + var principal = authentication.getPrincipal(); + + if (principal instanceof OAuth2User oauth2User) { + + Member member = saveOrUpdate(oauth2User.getAttributes()); + String targetUrl = determineTargetUrl(member); + getRedirectStrategy().sendRedirect(request, response, targetUrl); + } + } + + private Member saveOrUpdate(Map attributes) { + String email = (String) attributes.get("email"); + Provider provider = (Provider) attributes.get("provider"); + + Member member = memberRepository.findByEmail(email) + .map(entity -> entity.update(provider)) + .orElse(toEntity(attributes)); + + return memberRepository.save(member); + } + + private Member toEntity(Map attributes) { + String email = (String) attributes.get("email"); + Provider provider = (Provider) attributes.get("provider"); + Role role = Role.USER; + + return Member.from(email, provider, role); + } + + protected String determineTargetUrl(Member member) { + + String targetUrl = "http://localhost:3000/"; + + return UriComponentsBuilder.fromOriginHeader(targetUrl) + .queryParam("Authorization", jwtTokenProvider.createToken(member.getId())) + .build() + .toUriString(); + } +} diff --git a/src/main/java/com/project/musinsastocknotificationbot/common/config/security/oauth2/Provider.java b/src/main/java/com/project/musinsastocknotificationbot/common/config/security/oauth2/Provider.java new file mode 100644 index 0000000..c2c6a66 --- /dev/null +++ b/src/main/java/com/project/musinsastocknotificationbot/common/config/security/oauth2/Provider.java @@ -0,0 +1,25 @@ +package com.project.musinsastocknotificationbot.common.config.security.oauth2; + +import java.util.Arrays; + +public enum Provider { + + NAVER("naver"), KAKAO("kakao"), DEFAULT("default"); + + private final String key; + + Provider(String key) { + this.key = key; + } + + public String getKey() { + return this.key; + } + + public static Provider of(String key) { + return Arrays.stream(Provider.values()) + .filter(provider -> provider.key.equals(key)) + .findFirst() + .orElse(Provider.DEFAULT); + } +} diff --git a/src/main/java/com/project/musinsastocknotificationbot/common/config/security/oauth2/dto/OAuth2Attribute.java b/src/main/java/com/project/musinsastocknotificationbot/common/config/security/oauth2/dto/OAuth2Attribute.java deleted file mode 100644 index 6d3e067..0000000 --- a/src/main/java/com/project/musinsastocknotificationbot/common/config/security/oauth2/dto/OAuth2Attribute.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.project.musinsastocknotificationbot.common.config.security.oauth2.dto; - -import com.project.musinsastocknotificationbot.domain.member.entity.Member; -import com.project.musinsastocknotificationbot.domain.member.entity.vo.Role; -import java.util.HashMap; -import java.util.Map; - -public record OAuth2Attribute( - Map attributes, - String provider, - String attributeKey, - String email -) { - - public static OAuth2Attribute of(String provider, String attributeKey, - Map attributes) { - switch (provider) { - case "kakao" -> { - return ofKakao(provider, attributeKey, attributes); - } - default -> { - throw new RuntimeException("정상적인 url이 아닙니다."); - } - } - } - - private static OAuth2Attribute ofKakao(String provider, String attributeKey, - Map attributes) { - Map kakaoAccount = (Map) attributes.get("kakao_account"); - - return new OAuth2Attribute(attributes, provider, attributeKey, - (String) kakaoAccount.get("email") - ); - } - - public Map convertToMap() { - Map map = new HashMap<>(); - map.put("id", this.attributes); - map.put("provider", this.provider); - map.put("attributeKey", this.attributeKey); - map.put("email", this.email); - - return map; - } - - public Member toEntity() { - return new Member(this.email, this.provider, Role.USER); - } -} diff --git a/src/main/java/com/project/musinsastocknotificationbot/common/config/CorsFilter.java b/src/main/java/com/project/musinsastocknotificationbot/common/filter/CorsFilter.java similarity index 94% rename from src/main/java/com/project/musinsastocknotificationbot/common/config/CorsFilter.java rename to src/main/java/com/project/musinsastocknotificationbot/common/filter/CorsFilter.java index e223eb5..269cf3d 100644 --- a/src/main/java/com/project/musinsastocknotificationbot/common/config/CorsFilter.java +++ b/src/main/java/com/project/musinsastocknotificationbot/common/filter/CorsFilter.java @@ -1,4 +1,4 @@ -package com.project.musinsastocknotificationbot.common.config; +package com.project.musinsastocknotificationbot.common.filter; import jakarta.servlet.*; @@ -29,4 +29,6 @@ public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) chain.doFilter(req, res); } } + + } diff --git a/src/main/java/com/project/musinsastocknotificationbot/common/filter/JwtAuthenticationFilter.java b/src/main/java/com/project/musinsastocknotificationbot/common/filter/JwtAuthenticationFilter.java new file mode 100644 index 0000000..e155eaa --- /dev/null +++ b/src/main/java/com/project/musinsastocknotificationbot/common/filter/JwtAuthenticationFilter.java @@ -0,0 +1,35 @@ +package com.project.musinsastocknotificationbot.common.filter; + +import com.project.musinsastocknotificationbot.common.config.security.jwt.JwtTokenProvider; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +@Component +public class JwtAuthenticationFilter extends OncePerRequestFilter { + + private final JwtTokenProvider jwtTokenProvider; + + public JwtAuthenticationFilter(JwtTokenProvider jwtTokenProvider) { + this.jwtTokenProvider = jwtTokenProvider; + } + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, + FilterChain filterChain) throws ServletException, IOException { + String token = jwtTokenProvider.resolveToken(request); + + if (jwtTokenProvider.validateToken(token)) { + Authentication authentication = jwtTokenProvider.getAuthentication(token); + SecurityContextHolder.getContext().setAuthentication(authentication); + } + + filterChain.doFilter(request, response); + } +} diff --git a/src/main/java/com/project/musinsastocknotificationbot/domain/member/entity/Member.java b/src/main/java/com/project/musinsastocknotificationbot/domain/member/entity/Member.java index f25a8eb..992a694 100644 --- a/src/main/java/com/project/musinsastocknotificationbot/domain/member/entity/Member.java +++ b/src/main/java/com/project/musinsastocknotificationbot/domain/member/entity/Member.java @@ -1,42 +1,94 @@ package com.project.musinsastocknotificationbot.domain.member.entity; +import com.project.musinsastocknotificationbot.common.config.security.oauth2.Provider; import com.project.musinsastocknotificationbot.common.entity.BaseTimeEntity; import com.project.musinsastocknotificationbot.domain.member.entity.vo.Role; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.EnumType; import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; import jakarta.persistence.Id; +import java.util.Collection; +import java.util.Collections; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; @Entity -public class Member extends BaseTimeEntity { +public class Member extends BaseTimeEntity implements UserDetails { @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @Column + @Column(nullable = false, unique = true) private String email; - @Column - private String provider; + @Enumerated(EnumType.STRING) + private Provider provider; @Enumerated(EnumType.STRING) - Role role; + private Role role; protected Member() {} - public Member(String email, String provider, Role role) { + private Member(String email, Provider provider, Role role) { this.email = email; this.provider = provider; this.role = role; } - public Member update(String provider) { + public static Member from(String email, Provider provider, Role role) { + return new Member(email, provider, role); + } + + public Member update(Provider provider) { this.provider = provider; return this; } - public String getRoleKey() { - return this.role.getKey(); + public Long getId() { + return id; + } + + public String getEmail() { + return email; + } + + @Override + public Collection getAuthorities() { + return Collections.singleton(new SimpleGrantedAuthority(this.role.getKey())); + } + + @Override + public String getPassword() { + return null; + } + + @Override + public String getUsername() { + return this.email; + } + + @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/com/project/musinsastocknotificationbot/domain/member/error/MemberNotFoundException.java b/src/main/java/com/project/musinsastocknotificationbot/domain/member/error/MemberNotFoundException.java new file mode 100644 index 0000000..b8fefaa --- /dev/null +++ b/src/main/java/com/project/musinsastocknotificationbot/domain/member/error/MemberNotFoundException.java @@ -0,0 +1,6 @@ +package com.project.musinsastocknotificationbot.domain.member.error; + +public class MemberNotFoundException extends RuntimeException { + + +} diff --git a/src/main/java/com/project/musinsastocknotificationbot/domain/member/service/MemberService.java b/src/main/java/com/project/musinsastocknotificationbot/domain/member/service/MemberService.java new file mode 100644 index 0000000..2435405 --- /dev/null +++ b/src/main/java/com/project/musinsastocknotificationbot/domain/member/service/MemberService.java @@ -0,0 +1,21 @@ +package com.project.musinsastocknotificationbot.domain.member.service; + +import com.project.musinsastocknotificationbot.domain.member.entity.Member; +import com.project.musinsastocknotificationbot.domain.member.error.MemberNotFoundException; +import com.project.musinsastocknotificationbot.domain.member.repository.MemberRepository; +import org.springframework.stereotype.Service; + +@Service +public class MemberService { + + private final MemberRepository memberRepository; + + public MemberService(MemberRepository memberRepository) { + this.memberRepository = memberRepository; + } + + public Member findById(Long memberId) { + return memberRepository.findById(memberId).orElseThrow(() -> new MemberNotFoundException()); + } + +} diff --git a/src/main/java/com/project/musinsastocknotificationbot/infrastructure/message/email/service/EmailService.java b/src/main/java/com/project/musinsastocknotificationbot/infrastructure/message/email/service/EmailService.java index 562c7b8..9aa47b6 100644 --- a/src/main/java/com/project/musinsastocknotificationbot/infrastructure/message/email/service/EmailService.java +++ b/src/main/java/com/project/musinsastocknotificationbot/infrastructure/message/email/service/EmailService.java @@ -3,6 +3,7 @@ import com.project.musinsastocknotificationbot.infrastructure.message.service.MessageService; import jakarta.mail.MessagingException; import jakarta.mail.internet.MimeMessage; +import jakarta.mail.internet.MimeMessage.RecipientType; import java.io.UnsupportedEncodingException; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Profile; @@ -25,7 +26,7 @@ public EmailService(JavaMailSender javaMailSender, @Value("${spring.mail.usernam @Override public void sendMessage(String message) { - + // TODO document why this method is empty } //메일 양식 작성 @@ -39,7 +40,7 @@ public MimeMessage createEmailForm(String email) { MimeMessage message = javaMailSender.createMimeMessage(); try { - message.addRecipients(MimeMessage.RecipientType.TO, toEmail); //보낼 이메일 설정 + message.addRecipients(RecipientType.TO, toEmail); //보낼 이메일 설정 message.setSubject(title); message.setText(content);//제목 설정 message.setFrom(setFrom); //보내는 이메일 @@ -47,7 +48,6 @@ public MimeMessage createEmailForm(String email) { throw new RuntimeException(e); } - return message; } diff --git a/src/main/java/com/project/musinsastocknotificationbot/infrastructure/message/telegramBot/domain/TelegramBot.java b/src/main/java/com/project/musinsastocknotificationbot/infrastructure/message/telegramBot/domain/TelegramBot.java index 1f453cd..ff60fb9 100644 --- a/src/main/java/com/project/musinsastocknotificationbot/infrastructure/message/telegramBot/domain/TelegramBot.java +++ b/src/main/java/com/project/musinsastocknotificationbot/infrastructure/message/telegramBot/domain/TelegramBot.java @@ -2,12 +2,14 @@ import com.project.musinsastocknotificationbot.infrastructure.message.telegramBot.error.TelegramApiConnectionException; import com.project.musinsastocknotificationbot.infrastructure.message.telegramBot.service.TelegramMessageServiceImpl; +import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Component; import org.telegram.telegrambots.meta.TelegramBotsApi; import org.telegram.telegrambots.meta.exceptions.TelegramApiException; import org.telegram.telegrambots.updatesreceivers.DefaultBotSession; @Component +@Profile("telegram") public class TelegramBot { private final TelegramBotsApi telegramBotsApi; diff --git a/src/main/java/com/project/musinsastocknotificationbot/infrastructure/message/telegramBot/service/TelegramMessageServiceImpl.java b/src/main/java/com/project/musinsastocknotificationbot/infrastructure/message/telegramBot/service/TelegramMessageServiceImpl.java index 7c6d7eb..6fe600e 100644 --- a/src/main/java/com/project/musinsastocknotificationbot/infrastructure/message/telegramBot/service/TelegramMessageServiceImpl.java +++ b/src/main/java/com/project/musinsastocknotificationbot/infrastructure/message/telegramBot/service/TelegramMessageServiceImpl.java @@ -6,6 +6,7 @@ import com.project.musinsastocknotificationbot.domain.product.service.ProductService; import com.project.musinsastocknotificationbot.infrastructure.message.telegramBot.error.TelegramApiConnectionException; import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Service; import org.telegram.telegrambots.bots.TelegramLongPollingBot; import org.telegram.telegrambots.meta.api.methods.send.SendMessage; @@ -13,6 +14,7 @@ import org.telegram.telegrambots.meta.exceptions.TelegramApiException; @Service +@Profile("telegram") public class TelegramMessageServiceImpl extends TelegramLongPollingBot implements MessageService { private final String telegramToken; diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 2aea94c..149c219 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -5,7 +5,7 @@ spring: profiles: active: - - telegram + - email group: email: - email @@ -29,7 +29,7 @@ spring: properties: hibernate: format_sql: true - show_sql: true + show_sql: false database: h2 show-sql: true @@ -37,21 +37,21 @@ spring: oauth2: client: registration: - kakao: - client-id: ${oauth2_kakao_client-id} - redirect-uri: http://localhost:8080/api/login/oauth2/code/kakao - client-authentication-method: POST - client-secret: ${client-secret} + naver: + client-id: _6pnTKoF4IO5akersCes + client-secret: rvwy4a43G5 + redirect-uri: http://localhost:8080/login/oauth2/code/naver + authorization-grant-type: authorization_code scope: - - account_email - client_name: kakao + - email + client-name: Naver provider: - kakao: - authorization-uri: https://kauth.kakao.com/oauth/authorize - token-uri: https://kauth.kakao.com/oauth/token - user-info-uri: https://kapi.kakao.com/v2/user/me - user-name-attribute: id + naver: + authorization-uri: https://nid.naver.com/oauth2.0/authorize + token-uri: https://nid.naver.com/oauth2.0/token + user-info-uri: https://openapi.naver.com/v1/nid/me + user-name-attribute: response mail: host: smtp.naver.com @@ -64,9 +64,12 @@ spring: mail.smtp.ssl.trust: smtp.naver.com mail.smtp.starttls.enable: true +jwt: + header: ${jwt_token_header} + secret: ${jwt_token_key} + token-validity: ${jwt_token_validity_time} + secret: telegramToken: ${telegramToken} chat_id: ${chat_id} - bot_name: ${bot_name} - oauth2_kakao_client-id: ${oauth2_kakao_client-id} - client-secret: ${client-secret} \ No newline at end of file + bot_name: ${bot_name} \ No newline at end of file