diff --git a/build.gradle b/build.gradle index 6888a3b..2529c16 100644 --- a/build.gradle +++ b/build.gradle @@ -1,15 +1,7 @@ -buildscript { - ext { - queryDslVersion = "5.0.0" - } -} - plugins { id 'java' - id 'org.springframework.boot' version '2.7.12' - id 'io.spring.dependency-management' version '1.0.15.RELEASE' - //querydsl 추가 - id "com.ewerk.gradle.plugins.querydsl" version "1.0.10" + id 'org.springframework.boot' version '3.2.0' + id 'io.spring.dependency-management' version '1.1.4' } jar { @@ -18,10 +10,7 @@ jar { group = 'com.umc' version = '0.0.1-SNAPSHOT' - -java { - sourceCompatibility = '17' -} +sourceCompatibility = '17' configurations { compileOnly { @@ -44,6 +33,7 @@ dependencies { //메일 전송 라이브러리 implementation group: 'org.springframework.boot', name: 'spring-boot-starter-mail', version: '2.5.5' + implementation group: 'com.sun.mail', name: 'jakarta.mail', version: '2.0.1' //타임리프 라이브러리® implementation group: 'org.springframework.boot', name: 'spring-boot-starter-thymeleaf', version: '2.7.12' @@ -54,7 +44,8 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-security' //JSON Parse 라이브러리 - implementation 'com.google.code.gson:gson:2.8.9' + implementation 'com.fasterxml.jackson.core:jackson-databind' + implementation 'com.google.code.gson:gson' // validation implementation 'org.springframework.boot:spring-boot-starter-validation' @@ -65,11 +56,17 @@ dependencies { //AWS S3 implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE' - //querydsl 추가 - implementation "com.querydsl:querydsl-jpa:${queryDslVersion}" // querydsl 라이브러리 - annotationProcessor "com.querydsl:querydsl-apt:${queryDslVersion}" // Querydsl 관련 코드 생성 기능 제공 - + //JWT + implementation 'io.jsonwebtoken:jjwt-api:0.11.5' + runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5' + runtimeOnly 'io.jsonwebtoken:jjwt-orgjson:0.11.5' + implementation 'org.json:json:20210307' + //querydsl 추가 + implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta' + annotationProcessor "com.querydsl:querydsl-apt:5.0.0:jakarta" + annotationProcessor "jakarta.annotation:jakarta.annotation-api" + annotationProcessor "jakarta.persistence:jakarta.persistence-api" } tasks.named('test') { @@ -77,21 +74,16 @@ tasks.named('test') { } -//querydsl 추가 시작 (위에 plugin 추가 부분과 맞물림) def querydslDir = "$buildDir/generated/querydsl" -querydsl { - jpa = true - querydslSourcesDir = querydslDir -} -sourceSets { // IDE의 소스 폴더에 자동으로 넣어준다. - main.java.srcDir querydslDir +sourceSets { + main.java.srcDirs += [ querydslDir ] } -configurations { - querydsl.extendsFrom compileClasspath // 컴파일이 될때 같이 수행 +tasks.withType(JavaCompile) { + options.annotationProcessorGeneratedSourcesDirectory = file(querydslDir) } -compileQuerydsl { - options.annotationProcessorPath = configurations.querydsl // Q파일을 생성해준다. +clean.doLast { + file(querydslDir).deleteDir() } \ No newline at end of file diff --git a/src/main/java/com/umc/refit/Util.java b/src/main/java/com/umc/refit/Util.java new file mode 100644 index 0000000..cd24064 --- /dev/null +++ b/src/main/java/com/umc/refit/Util.java @@ -0,0 +1,29 @@ +package com.umc.refit; + +import java.util.Random; + +public class Util { + + /**Random 문자열*/ + private static final String CHARACTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()"; + + public static String generateRandomString(int length) { + Random random = new Random(); + StringBuilder stringBuilder = new StringBuilder(length); + + for (int i = 0; i < length; i++) { + int randomIndex = random.nextInt(CHARACTERS.length()); + stringBuilder.append(CHARACTERS.charAt(randomIndex)); + } + + return stringBuilder.toString(); + } + + /**시간*/ + public static final int ONE_HOUR = 60 * 60 * 1000; + public static final int ONE_DAY = 24 * 60 * 60 * 1000; + public static final int ONE_WEEK = 7 * 24 * 60 * 60 * 1000; + + /**PATH*/ + public static final String KAKAO_API = "https://kapi.kakao.com/v2/user/me"; +} diff --git a/src/main/java/com/umc/refit/domain/dto/clothe/RegisterClotheRequestDto.java b/src/main/java/com/umc/refit/domain/dto/clothe/RegisterClotheRequestDto.java index b0a4f3e..0cf7fbe 100644 --- a/src/main/java/com/umc/refit/domain/dto/clothe/RegisterClotheRequestDto.java +++ b/src/main/java/com/umc/refit/domain/dto/clothe/RegisterClotheRequestDto.java @@ -6,7 +6,7 @@ import lombok.*; import org.hibernate.validator.constraints.Range; -import javax.validation.constraints.NotNull; +import jakarta.validation.constraints.*; @Getter @Setter diff --git a/src/main/java/com/umc/refit/domain/dto/clothe/UpdateClotheGoalRequestDto.java b/src/main/java/com/umc/refit/domain/dto/clothe/UpdateClotheGoalRequestDto.java index d0320b9..d7b7980 100644 --- a/src/main/java/com/umc/refit/domain/dto/clothe/UpdateClotheGoalRequestDto.java +++ b/src/main/java/com/umc/refit/domain/dto/clothe/UpdateClotheGoalRequestDto.java @@ -2,9 +2,7 @@ import lombok.*; -import javax.validation.constraints.Max; -import javax.validation.constraints.Min; -import javax.validation.constraints.NotNull; +import jakarta.validation.constraints.*; @Getter @Setter diff --git a/src/main/java/com/umc/refit/domain/dto/clothe/UpdateClotheRequestDto.java b/src/main/java/com/umc/refit/domain/dto/clothe/UpdateClotheRequestDto.java index b7c8f38..d7df853 100644 --- a/src/main/java/com/umc/refit/domain/dto/clothe/UpdateClotheRequestDto.java +++ b/src/main/java/com/umc/refit/domain/dto/clothe/UpdateClotheRequestDto.java @@ -2,9 +2,7 @@ import lombok.*; -import javax.validation.constraints.Max; -import javax.validation.constraints.Min; -import javax.validation.constraints.NotNull; +import jakarta.validation.constraints.*; @Getter @Setter diff --git a/src/main/java/com/umc/refit/domain/dto/community/ReportMemDto.java b/src/main/java/com/umc/refit/domain/dto/community/ReportMemDto.java index 4679c37..9498036 100644 --- a/src/main/java/com/umc/refit/domain/dto/community/ReportMemDto.java +++ b/src/main/java/com/umc/refit/domain/dto/community/ReportMemDto.java @@ -4,10 +4,6 @@ import lombok.Getter; import lombok.Setter; -import javax.validation.constraints.Max; -import javax.validation.constraints.Min; -import javax.validation.constraints.NotNull; - @Getter @Setter public class ReportMemDto { diff --git a/src/main/java/com/umc/refit/domain/dto/member/LoginDto.java b/src/main/java/com/umc/refit/domain/dto/member/LoginDto.java new file mode 100644 index 0000000..c64236c --- /dev/null +++ b/src/main/java/com/umc/refit/domain/dto/member/LoginDto.java @@ -0,0 +1,11 @@ +package com.umc.refit.domain.dto.member; + +import lombok.Getter; +import lombok.Setter; + +@Getter @Setter +public class LoginDto { + private String loginId; + private String password; + private String fcm; +} \ No newline at end of file diff --git a/src/main/java/com/umc/refit/domain/dto/member/ResLoginDto.java b/src/main/java/com/umc/refit/domain/dto/member/RefreshTokenDto.java similarity index 86% rename from src/main/java/com/umc/refit/domain/dto/member/ResLoginDto.java rename to src/main/java/com/umc/refit/domain/dto/member/RefreshTokenDto.java index d424434..fdb2f74 100644 --- a/src/main/java/com/umc/refit/domain/dto/member/ResLoginDto.java +++ b/src/main/java/com/umc/refit/domain/dto/member/RefreshTokenDto.java @@ -6,7 +6,7 @@ @Getter @Setter @AllArgsConstructor -public class ResLoginDto { +public class RefreshTokenDto { private String refreshToken; } diff --git a/src/main/java/com/umc/refit/domain/dto/member/TokenDto.java b/src/main/java/com/umc/refit/domain/dto/member/TokenDto.java new file mode 100644 index 0000000..3ccc83d --- /dev/null +++ b/src/main/java/com/umc/refit/domain/dto/member/TokenDto.java @@ -0,0 +1,13 @@ +package com.umc.refit.domain.dto.member; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; + +@Getter @Setter +@AllArgsConstructor +public class TokenDto { + + private String accessToken; + private String refreshToken; +} diff --git a/src/main/java/com/umc/refit/domain/dto/mypage/UpdateMyInfoRequestDto.java b/src/main/java/com/umc/refit/domain/dto/mypage/UpdateMyInfoRequestDto.java index 04797bc..3428ef6 100644 --- a/src/main/java/com/umc/refit/domain/dto/mypage/UpdateMyInfoRequestDto.java +++ b/src/main/java/com/umc/refit/domain/dto/mypage/UpdateMyInfoRequestDto.java @@ -1,8 +1,7 @@ package com.umc.refit.domain.dto.mypage; import lombok.*; - -import javax.validation.constraints.Size; +import jakarta.validation.constraints.*; @Getter @Setter diff --git a/src/main/java/com/umc/refit/domain/entity/BaseTimeEntity.java b/src/main/java/com/umc/refit/domain/entity/BaseTimeEntity.java index 0104fd7..421e6f9 100644 --- a/src/main/java/com/umc/refit/domain/entity/BaseTimeEntity.java +++ b/src/main/java/com/umc/refit/domain/entity/BaseTimeEntity.java @@ -1,13 +1,13 @@ package com.umc.refit.domain.entity; +import jakarta.persistence.Column; +import jakarta.persistence.EntityListeners; +import jakarta.persistence.MappedSuperclass; import lombok.Getter; import org.springframework.data.annotation.CreatedDate; import org.springframework.data.annotation.LastModifiedDate; import org.springframework.data.jpa.domain.support.AuditingEntityListener; -import javax.persistence.Column; -import javax.persistence.EntityListeners; -import javax.persistence.MappedSuperclass; import java.time.LocalDateTime; @Getter diff --git a/src/main/java/com/umc/refit/domain/entity/Block.java b/src/main/java/com/umc/refit/domain/entity/Block.java index 522c98f..d780925 100644 --- a/src/main/java/com/umc/refit/domain/entity/Block.java +++ b/src/main/java/com/umc/refit/domain/entity/Block.java @@ -5,7 +5,7 @@ import lombok.Getter; import lombok.NoArgsConstructor; -import javax.persistence.*; +import jakarta.persistence.*; @Entity @Getter diff --git a/src/main/java/com/umc/refit/domain/entity/Clothe.java b/src/main/java/com/umc/refit/domain/entity/Clothe.java index bd07037..05fcf53 100644 --- a/src/main/java/com/umc/refit/domain/entity/Clothe.java +++ b/src/main/java/com/umc/refit/domain/entity/Clothe.java @@ -1,13 +1,13 @@ package com.umc.refit.domain.entity; import com.umc.refit.domain.dto.clothe.*; +import jakarta.persistence.*; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.extern.slf4j.Slf4j; -import javax.persistence.*; import java.time.LocalDate; @Slf4j diff --git a/src/main/java/com/umc/refit/domain/entity/Member.java b/src/main/java/com/umc/refit/domain/entity/Member.java index 9e69c99..452ace8 100644 --- a/src/main/java/com/umc/refit/domain/entity/Member.java +++ b/src/main/java/com/umc/refit/domain/entity/Member.java @@ -9,7 +9,7 @@ import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; -import javax.persistence.*; +import jakarta.persistence.*; import java.time.LocalDate; import java.util.ArrayList; import java.util.Collection; @@ -43,10 +43,10 @@ public Member(String name) { this.name = name; } - public Member(String email, String password, String name, String fcm) { + public Member(String email, String name, String fcm) { this.email = email; this.loginId = email; - this.password = password; + this.password = email; this.name = name; this.birth = LocalDate.now().toString().replace('-', '/'); this.gender = 0; diff --git a/src/main/java/com/umc/refit/domain/entity/PostImage.java b/src/main/java/com/umc/refit/domain/entity/PostImage.java index 63b25dc..2420703 100644 --- a/src/main/java/com/umc/refit/domain/entity/PostImage.java +++ b/src/main/java/com/umc/refit/domain/entity/PostImage.java @@ -5,7 +5,7 @@ import lombok.Getter; import lombok.NoArgsConstructor; -import javax.persistence.*; +import jakarta.persistence.*; @Entity @Getter diff --git a/src/main/java/com/umc/refit/domain/entity/Posts.java b/src/main/java/com/umc/refit/domain/entity/Posts.java index 2b655de..d8e3521 100644 --- a/src/main/java/com/umc/refit/domain/entity/Posts.java +++ b/src/main/java/com/umc/refit/domain/entity/Posts.java @@ -5,7 +5,7 @@ import lombok.Getter; import lombok.NoArgsConstructor; -import javax.persistence.*; +import jakarta.persistence.*; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/com/umc/refit/domain/entity/Question.java b/src/main/java/com/umc/refit/domain/entity/Question.java index 70c882f..358aabf 100644 --- a/src/main/java/com/umc/refit/domain/entity/Question.java +++ b/src/main/java/com/umc/refit/domain/entity/Question.java @@ -6,7 +6,7 @@ import lombok.NoArgsConstructor; import lombok.extern.slf4j.Slf4j; -import javax.persistence.*; +import jakarta.persistence.*; @Slf4j @Getter diff --git a/src/main/java/com/umc/refit/domain/entity/ReportMem.java b/src/main/java/com/umc/refit/domain/entity/ReportMem.java index 6f082cf..a37632c 100644 --- a/src/main/java/com/umc/refit/domain/entity/ReportMem.java +++ b/src/main/java/com/umc/refit/domain/entity/ReportMem.java @@ -5,7 +5,7 @@ import lombok.Getter; import lombok.NoArgsConstructor; -import javax.persistence.*; +import jakarta.persistence.*; @Entity @Getter diff --git a/src/main/java/com/umc/refit/domain/entity/Scrap.java b/src/main/java/com/umc/refit/domain/entity/Scrap.java index cce0ffe..408a7c1 100644 --- a/src/main/java/com/umc/refit/domain/entity/Scrap.java +++ b/src/main/java/com/umc/refit/domain/entity/Scrap.java @@ -5,7 +5,7 @@ import lombok.Getter; import lombok.NoArgsConstructor; -import javax.persistence.*; +import jakarta.persistence.*; @Entity @Getter diff --git a/src/main/java/com/umc/refit/exception/ExceptionType.java b/src/main/java/com/umc/refit/exception/ExceptionType.java index f46cc49..83837f5 100644 --- a/src/main/java/com/umc/refit/exception/ExceptionType.java +++ b/src/main/java/com/umc/refit/exception/ExceptionType.java @@ -40,8 +40,10 @@ public enum ExceptionType { //로그인 예외 LOGIN_FAILED(BAD_REQUEST, 10103, "존재하지 않는 계정입니다."), - LOGIN_FAILED_ALL(BAD_REQUEST, 10104, "알 수 없는 이유로 로그인 할 수 없습니다."), + LOGIN_FAILED_UNKNOWN(BAD_REQUEST, 10104, "알 수 없는 이유로 로그인 할 수 없습니다."), KAKAO_MEMBER_EXIST(BAD_REQUEST, 10105, "카카오 로그인 계정이 존재합니다."), + GOOGLE_MEMBER_EXIST(BAD_REQUEST, 10505, "구글 로그인 계정이 존재합니다."), + NAVER_MEMBER_EXIST(BAD_REQUEST, 10605, "네이버 로그인 계정이 존재합니다."), BASIC_MEMBER_EXIST(BAD_REQUEST, 10106, "일반 로그인 계정이 존재합니다."), diff --git a/src/main/java/com/umc/refit/exception/handler/ClotheExceptionHandler.java b/src/main/java/com/umc/refit/exception/handler/ClotheExceptionHandler.java index f6235d1..ab1a111 100644 --- a/src/main/java/com/umc/refit/exception/handler/ClotheExceptionHandler.java +++ b/src/main/java/com/umc/refit/exception/handler/ClotheExceptionHandler.java @@ -8,8 +8,7 @@ import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestControllerAdvice; - -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; @Slf4j @RestControllerAdvice(basePackageClasses = ClotheController.class) diff --git a/src/main/java/com/umc/refit/exception/handler/CommunityExceptionHandler.java b/src/main/java/com/umc/refit/exception/handler/CommunityExceptionHandler.java index 414a043..e5bbdc8 100644 --- a/src/main/java/com/umc/refit/exception/handler/CommunityExceptionHandler.java +++ b/src/main/java/com/umc/refit/exception/handler/CommunityExceptionHandler.java @@ -10,7 +10,7 @@ import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestControllerAdvice; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; @Slf4j @RestControllerAdvice diff --git a/src/main/java/com/umc/refit/exception/handler/MemberExceptionHandler.java b/src/main/java/com/umc/refit/exception/handler/MemberExceptionHandler.java index d663dd0..3582804 100644 --- a/src/main/java/com/umc/refit/exception/handler/MemberExceptionHandler.java +++ b/src/main/java/com/umc/refit/exception/handler/MemberExceptionHandler.java @@ -9,7 +9,7 @@ import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestControllerAdvice; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; @Slf4j @RestControllerAdvice diff --git a/src/main/java/com/umc/refit/exception/handler/MyInfoExceptionHandler.java b/src/main/java/com/umc/refit/exception/handler/MyInfoExceptionHandler.java index e223cce..895fa19 100644 --- a/src/main/java/com/umc/refit/exception/handler/MyInfoExceptionHandler.java +++ b/src/main/java/com/umc/refit/exception/handler/MyInfoExceptionHandler.java @@ -8,7 +8,7 @@ import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestControllerAdvice; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; @Slf4j @RestControllerAdvice diff --git a/src/main/java/com/umc/refit/web/config/AppConfig.java b/src/main/java/com/umc/refit/web/config/AppConfig.java index 34a8d57..a513149 100644 --- a/src/main/java/com/umc/refit/web/config/AppConfig.java +++ b/src/main/java/com/umc/refit/web/config/AppConfig.java @@ -1,9 +1,16 @@ package com.umc.refit.web.config; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.jpa.repository.config.EnableJpaAuditing; +import org.springframework.web.client.RestTemplate; @EnableJpaAuditing @Configuration public class AppConfig { + + @Bean + public RestTemplate restTemplate() { + return new RestTemplate(); + } } diff --git a/src/main/java/com/umc/refit/web/config/S3Config.java b/src/main/java/com/umc/refit/web/config/S3Config.java new file mode 100644 index 0000000..45b64e3 --- /dev/null +++ b/src/main/java/com/umc/refit/web/config/S3Config.java @@ -0,0 +1,33 @@ +package com.umc.refit.web.config; + +import com.amazonaws.auth.AWSCredentials; +import com.amazonaws.auth.AWSStaticCredentialsProvider; +import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.AmazonS3ClientBuilder; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class S3Config { + @Value("${cloud.aws.credentials.access-key}") + private String accessKey; + + @Value("${cloud.aws.credentials.secret-key}") + private String secretKey; + + @Value("${cloud.aws.region.static}") + private String region; + + @Bean + public AmazonS3 amazonS3Client() { + AWSCredentials credentials = new BasicAWSCredentials(accessKey, secretKey); + + return AmazonS3ClientBuilder + .standard() + .withCredentials(new AWSStaticCredentialsProvider(credentials)) + .withRegion(region) + .build(); + } +} \ No newline at end of file diff --git a/src/main/java/com/umc/refit/web/config/SignatureConfig.java b/src/main/java/com/umc/refit/web/config/SignatureConfig.java index 3abe5c2..be96a06 100644 --- a/src/main/java/com/umc/refit/web/config/SignatureConfig.java +++ b/src/main/java/com/umc/refit/web/config/SignatureConfig.java @@ -1,34 +1,24 @@ package com.umc.refit.web.config; -import com.nimbusds.jose.JOSEException; -import com.nimbusds.jose.JWSAlgorithm; -import com.nimbusds.jose.jwk.RSAKey; -import com.nimbusds.jose.jwk.gen.RSAKeyGenerator; -import com.umc.refit.web.signature.RSASecuritySigner; +import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.security.Keys; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; +import java.security.Key; + @Configuration public class SignatureConfig { @Bean - public RSASecuritySigner rsaSecuritySigner() { - return new RSASecuritySigner(); - } - - /*RSA 키 생성*/ - @Bean - public RSAKey rsaKey512() throws JOSEException { - return new RSAKeyGenerator(2048) - .keyID("rsaKey") - .algorithm(JWSAlgorithm.RS512) - .generate(); + public PasswordEncoder passwordEncoder(){ + return new BCryptPasswordEncoder(); } @Bean - public PasswordEncoder passwordEncoder(){ - return new BCryptPasswordEncoder(); + public Key jwtSigningKey() { + return Keys.secretKeyFor(SignatureAlgorithm.HS256); } } \ No newline at end of file diff --git a/src/main/java/com/umc/refit/web/config/OAuth2ResourceServer.java b/src/main/java/com/umc/refit/web/config/SpringSecurityConfig.java similarity index 51% rename from src/main/java/com/umc/refit/web/config/OAuth2ResourceServer.java rename to src/main/java/com/umc/refit/web/config/SpringSecurityConfig.java index 723f0b3..ff8a35f 100644 --- a/src/main/java/com/umc/refit/web/config/OAuth2ResourceServer.java +++ b/src/main/java/com/umc/refit/web/config/SpringSecurityConfig.java @@ -1,6 +1,5 @@ package com.umc.refit.web.config; -import com.nimbusds.jose.jwk.RSAKey; import com.umc.refit.web.filter.authentication.CustomUserDetailsService; import com.umc.refit.web.filter.authentication.JwtAuthenticationFilter; import com.umc.refit.web.filter.authentication.JwtKakaoAuthenticationFilter; @@ -9,73 +8,68 @@ import com.umc.refit.web.filter.exception.CustomAuthenticationFailureHandler; import com.umc.refit.web.service.MemberService; import com.umc.refit.web.service.RefreshTokenService; -import com.umc.refit.web.signature.RSASecuritySigner; +import com.umc.refit.web.signature.JWTSigner; import lombok.RequiredArgsConstructor; -import org.springframework.beans.factory.annotation.Autowired; 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.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.web.client.RestTemplate; + +import java.security.Key; @EnableWebSecurity +@Configuration @RequiredArgsConstructor -public class OAuth2ResourceServer { +public class SpringSecurityConfig { - private final RSASecuritySigner rsaSecuritySigner; - private final RSAKey rsaKey; private final CustomUserDetailsService userDetailsService; private final CustomAuthenticationFailureHandler authFailureHandler; + private final CustomAuthenticationEntryPoint authenticationEntryPoint; private final MemberService memberService; private final RefreshTokenService refreshTokenService; + private final JWTSigner jwtSigner; + private final Key key; + private final RestTemplate restTemplate; + + private String[] permitAllUrlPatterns() { + return new String[] { + "/auth/logout", "/auth/join", "/auth/email", "/auth/find/id", + "/auth/reset/password", "/static/**", "/*.html", "/oauth2/fcm", + "/oauth2/image", "/auth/join/name", + }; + } @Bean SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { - //세션을 사용하지 않음 - http.csrf().disable(); - http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); - - //인증을 거치지 않을 URL 처리 및 인증, 인가 예외 EntryPoint 등록 - http.authorizeRequests((requests) -> + http.authorizeHttpRequests((requests) -> + requests.requestMatchers(permitAllUrlPatterns()).permitAll() + .anyRequest().authenticated()) + .exceptionHandling(handler -> handler.authenticationEntryPoint(authenticationEntryPoint)); - requests.antMatchers("/auth/logout" //로그아웃 - , "/auth/join" //회원 가입 - , "/auth/email" //이메일 찾기 - , "/auth/find/id" //아이디 찾기 - , "/auth/reset/password" //패스워드 찾기 - , "/static/**" //카카오 주소 api - , "/*.html" //카카오 주소 api - , "/oauth2/fcm" - , "/oauth2/image" - , "/auth/join/name" -// , "/**" //임시로 모든 인증 처리 제외 - ).permitAll() - .anyRequest().authenticated()) + http.csrf(csrf -> csrf.disable()); + http.sessionManagement(sessionManagement -> + sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS) + ); - .exceptionHandling().authenticationEntryPoint(new CustomAuthenticationEntryPoint()); - - //사용자 정보 로드해서 객체 생성 http.userDetailsService(userDetailsService); - //일반 로그인 URL 설정 JwtAuthenticationFilter jwtAuthenticationFilter = - - new JwtAuthenticationFilter(http, rsaSecuritySigner, rsaKey, memberService, refreshTokenService); - + new JwtAuthenticationFilter(http, jwtSigner, memberService, refreshTokenService); jwtAuthenticationFilter.setAuthenticationFailureHandler(authFailureHandler); jwtAuthenticationFilter.setFilterProcessesUrl("/auth/login"); - //카카오 로그인 URL 설정 JwtKakaoAuthenticationFilter jwtKakaoAuthenticationFilter = - new JwtKakaoAuthenticationFilter(http, rsaSecuritySigner, rsaKey, memberService, refreshTokenService); + new JwtKakaoAuthenticationFilter(http, jwtSigner, memberService, refreshTokenService, restTemplate); jwtKakaoAuthenticationFilter.setAuthenticationFailureHandler(authFailureHandler); jwtKakaoAuthenticationFilter.setFilterProcessesUrl("/auth/kakao"); - //인가 필터 등록 필터 http.addFilter(jwtAuthenticationFilter).addFilter(jwtKakaoAuthenticationFilter) - .addFilterBefore(new JwtAuthorizationRsaFilter(rsaKey), UsernamePasswordAuthenticationFilter.class); + .addFilterBefore(new JwtAuthorizationRsaFilter(key), UsernamePasswordAuthenticationFilter.class); return http.build(); } diff --git a/src/main/java/com/umc/refit/web/config/ThymeleafConfig.java b/src/main/java/com/umc/refit/web/config/ThymeleafConfig.java new file mode 100644 index 0000000..d6206ec --- /dev/null +++ b/src/main/java/com/umc/refit/web/config/ThymeleafConfig.java @@ -0,0 +1,14 @@ +package com.umc.refit.web.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.thymeleaf.spring5.SpringTemplateEngine; + +@Configuration +public class ThymeleafConfig { + @Bean + public SpringTemplateEngine templateEngine() { + SpringTemplateEngine templateEngine = new SpringTemplateEngine(); + return templateEngine; + } +} diff --git a/src/main/java/com/umc/refit/web/controller/BlockController.java b/src/main/java/com/umc/refit/web/controller/BlockController.java index 8cc87dc..28cde61 100644 --- a/src/main/java/com/umc/refit/web/controller/BlockController.java +++ b/src/main/java/com/umc/refit/web/controller/BlockController.java @@ -11,7 +11,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; @RestController @RequestMapping("/refit/block") diff --git a/src/main/java/com/umc/refit/web/controller/ClotheController.java b/src/main/java/com/umc/refit/web/controller/ClotheController.java index 155ad82..60aa54b 100644 --- a/src/main/java/com/umc/refit/web/controller/ClotheController.java +++ b/src/main/java/com/umc/refit/web/controller/ClotheController.java @@ -12,8 +12,8 @@ import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; -import javax.servlet.http.HttpServletRequest; -import javax.validation.Valid; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.Valid; import java.util.List; @Slf4j diff --git a/src/main/java/com/umc/refit/web/controller/CommunityController.java b/src/main/java/com/umc/refit/web/controller/CommunityController.java index 6ff8996..92c95be 100644 --- a/src/main/java/com/umc/refit/web/controller/CommunityController.java +++ b/src/main/java/com/umc/refit/web/controller/CommunityController.java @@ -12,7 +12,7 @@ import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import java.io.IOException; import java.util.List; diff --git a/src/main/java/com/umc/refit/web/controller/MemberController.java b/src/main/java/com/umc/refit/web/controller/MemberController.java index e612cf0..fba1b8b 100644 --- a/src/main/java/com/umc/refit/web/controller/MemberController.java +++ b/src/main/java/com/umc/refit/web/controller/MemberController.java @@ -1,9 +1,6 @@ package com.umc.refit.web.controller; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import com.nimbusds.jose.JOSEException; -import com.nimbusds.jose.jwk.JWK; import com.umc.refit.domain.dto.member.*; import com.umc.refit.domain.entity.Member; import com.umc.refit.exception.ExceptionType; @@ -13,18 +10,15 @@ import com.umc.refit.web.service.EmailService; import com.umc.refit.web.service.MemberService; -import com.umc.refit.web.service.RefreshTokenService; -import com.umc.refit.web.signature.SecuritySigner; - +import com.umc.refit.web.service.ValidateService; +import jakarta.mail.MessagingException; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.ResponseEntity; import org.springframework.security.core.Authentication; -import org.springframework.security.core.userdetails.User; import org.springframework.web.bind.annotation.*; -import javax.mail.MessagingException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.Optional; @@ -37,65 +31,34 @@ public class MemberController { private final EmailService emailService; private final MemberService memberService; - private final RefreshTokenService refreshTokenService; - - //토큰 재발급 - private final SecuritySigner securitySigner; - private final JWK jwk; + private final ValidateService validateService; @Value("${member.image}") private String imageUrl; - /*이메일 인증 API*/ @PostMapping("/email") public ResEmailDto email(@RequestBody EmailDto emailDto) throws MessagingException { - String email = emailDto.getEmail(); - emailCheck(email); - + validateService.emailCheck(email); String auth = emailService.sendEmail(emailDto.getEmail()); - return new ResEmailDto(auth); } @PostMapping("/join/name") public void checkName(@RequestBody NameDto nameDto) { String name = nameDto.getName(); - nameCheck(name); + validateService.nameCheck(name); } - /*회원 가입 API*/ @PostMapping("/join") public void join(@RequestBody JoinDto joinDto) { - - String loginId = joinDto.getLoginId(); - String password = joinDto.getPassword(); - String email = joinDto.getEmail(); - String name = joinDto.getName(); - String birth = joinDto.getBirth(); - Integer gender = joinDto.getGender(); - - /*예외 체크*/ - loginIdCheck(loginId); - passwordCheck(password); - emailCheck(email); - nameCheck(name); - birthCheck(birth); - genderCheck(gender); - - /*예외 처리가 끝나면 회원 저장*/ + validateService.validateJoin(joinDto); memberService.save(new Member(joinDto, imageUrl)); } - /*이메일 인증 API*/ @PostMapping("/reset/password") - public void reset(@RequestBody PasswordResetDto passwordResetDto) throws MessagingException { - - String email = passwordResetDto.getEmail(); - String name = passwordResetDto.getName(); - String loginId = passwordResetDto.getLoginId(); - - Optional member = memberService.findMemberForPasswordRest(name, email, loginId); + public void reset(@RequestBody PasswordResetDto dto) throws MessagingException { + Optional member = memberService.findMemberForPasswordRest(dto.getName(), dto.getEmail(), dto.getLoginId()); if (member.isEmpty()) { throw new MemberException(PASSWORD_RESET_FAIL, PASSWORD_RESET_FAIL.getCode(), PASSWORD_RESET_FAIL.getErrorMessage()); @@ -105,48 +68,40 @@ public void reset(@RequestBody PasswordResetDto passwordResetDto) throws Messagi throw new MemberException(PASSWORD_RESET_FAIL, PASSWORD_RESET_FAIL.getCode(), PASSWORD_RESET_FAIL.getErrorMessage()); } - String password = emailService.resetEmail(email, name); + String password = emailService.resetEmail(dto.getEmail(), dto.getName()); Member getMember = member.get(); getMember.setPassword(password); memberService.save(getMember); } - /*아이디 찾기 API*/ @PostMapping("/find/id") public ResIdFindDto findId(@RequestBody IdFindDto idFindDto) { String email = idFindDto.getEmail(); String name = idFindDto.getName(); - /*예외 처리가 끝나면 회원 조회*/ Optional member = memberService.findMemberForFindId(name, email); - /*회원이 조회되지 않으면 예외*/ if (member.isEmpty()) { throw new MemberException(MEMBER_IS_NOT_EXIST, MEMBER_IS_NOT_EXIST.getCode(), MEMBER_IS_NOT_EXIST.getErrorMessage()); } - /*뒤 세글자 ***로 변환*/ String lastThreeReplaced = "***"; String exceptLastThree = member.get().getLoginId().substring(0, member.get().getLoginId().length() - 3); - return new ResIdFindDto(exceptLastThree + lastThreeReplaced); } - /*일반 로그인 API*/ @PostMapping("/login") public ResponseEntity login() { return ResponseEntity.ok().build(); } - /*카카오 로그인 API*/ @PostMapping("/kakao") public ResponseEntity kakao_login() { return ResponseEntity.ok().build(); } - /*로그아웃 API*/ @PostMapping("/logout") public ResponseEntity logout(Authentication authentication, HttpServletRequest request) { @@ -155,130 +110,35 @@ public ResponseEntity logout(Authentication authentication, HttpServletReq throw new TokenException(exception, exception.getCode(), exception.getErrorMessage()); } - String loginId = authentication.getName(); - refreshTokenService.deleteRefreshToken(loginId); + String email = authentication.getName(); + memberService.deleteRefreshToken(email); return ResponseEntity.ok().build(); } - /*엑세스 토큰 체크 API*/ @GetMapping("/token/check") public ResponseEntity token_check() { return ResponseEntity.ok().build(); } - /*토큰 재발급 API*/ @PostMapping("/token/refresh") public ResponseEntity token_reissue(Authentication authentication, HttpServletRequest request - , HttpServletResponse response) throws JOSEException, IOException { + , HttpServletResponse response) throws IOException { if (authentication == null) { ExceptionType exception = (ExceptionType) request.getAttribute("exception"); throw new TokenException(exception, exception.getCode(), exception.getErrorMessage()); } - //토큰 재발급 코드 - User user = (User) authentication.getPrincipal(); - - String accessToken = securitySigner.getJwtToken(user, jwk, 216000000); - String refreshToken = securitySigner.getJwtToken(user, jwk, 216000000); + TokenDto tokenResponse = memberService.refreshAuthenticationToken(authentication); - //리프레쉬 토큰 저장 - refreshTokenService.saveRefreshToken(user.getUsername(), refreshToken); - - //엑세스 토큰 헤더를 통해 전달 - response.addHeader("Authorization", "Bearer " + accessToken); //발행받은 토큰을 response 헤더에 담아 응답 - - //리프레쉬 토큰 바디에 담아 전달 - ResLoginDto resEmailDto = new ResLoginDto(refreshToken); + response.addHeader("Authorization", "Bearer " + tokenResponse.getAccessToken()); response.setContentType("application/json"); + + RefreshTokenDto responseDto = new RefreshTokenDto(tokenResponse.getRefreshToken()); ObjectMapper objectMapper = new ObjectMapper(); - String jsonString = objectMapper.writeValueAsString(resEmailDto); + String jsonString = objectMapper.writeValueAsString(responseDto); response.getWriter().write(jsonString); - return ResponseEntity.ok().build(); } - - /*로그인 아이디 체크 메서드*/ - private void loginIdCheck(String loginId) { - //예외 코드 10011: 아이디가 비어있을 경우 - if (loginId.strip().equals("")) { - throw new MemberException(ID_EMPTY, ID_EMPTY.getCode(), ID_EMPTY.getErrorMessage()); - } - - //예외 코드 10012: 아이디가 형식에 맞지 않은 경우 - if (!MemberValidator.isLoginValid(loginId)) { - throw new MemberException(ID_INVALID, ID_INVALID.getCode(), ID_INVALID.getErrorMessage()); - } - - //예외 코드 10013: 아이디가 이미 존재하는 경우 - if (memberService.findMemberByLoginId(loginId).isPresent()) { - throw new MemberException(ID_ALREADY_EXIST, ID_ALREADY_EXIST.getCode(), ID_ALREADY_EXIST.getErrorMessage()); - } - } - - /*비밀번호 체크 메서드*/ - private void passwordCheck(String password) { - //예외 코드 10014: 비밀번호는 필수 정보입니다. - if (password.strip().equals("")) { - throw new MemberException(PASSWORD_EMPTY, PASSWORD_EMPTY.getCode(), PASSWORD_EMPTY.getErrorMessage()); - } - - //예외 코드 10015: "8-16자의 영문 대소문자, 숫자, 특수문자 ((!), (_) , (-))를 포합해야합니다. - if (!MemberValidator.isPasswordValid(password)) { - throw new MemberException(PASSWORD_INVALID, PASSWORD_INVALID.getCode(), PASSWORD_INVALID.getErrorMessage()); - } - } - - /*이메일 체크 메서드*/ - private void emailCheck(String email) { - //예외 코드 10016: 이메일이 비어있을 경우 - if (email.strip().equals("")) { - throw new MemberException(EMAIL_EMPTY, EMAIL_EMPTY.getCode(), EMAIL_EMPTY.getErrorMessage()); - } - - //예외 코드 10017: 이미 존재하는 회원일 경우 - if (memberService.findMemberByEmail(email).isPresent()) { - throw new MemberException(EMAIL_ALREADY_EXIST, EMAIL_ALREADY_EXIST.getCode(), EMAIL_ALREADY_EXIST.getErrorMessage()); - } - - //예외 코드 10018: 이메일 형식에 맞지 않을 경우 - if (!MemberValidator.isEmailValid(email)) { - throw new MemberException(EMAIL_INVALID, EMAIL_INVALID.getCode(), EMAIL_INVALID.getErrorMessage()); - } - } - - /*닉네임 체크 메서드*/ - private void nameCheck(String name) { - //예외 코드 10019: 이름이 비어있을 경우 - if (name.strip().equals("")) { - throw new MemberException(NAME_EMPTY, NAME_EMPTY.getCode(), NAME_EMPTY.getErrorMessage()); - } - - //예외 코드 10020: 이미 존재하는 이름일 경우 - if (memberService.findMemberByName(name).isPresent()) { - throw new MemberException(NAME_ALREADY_EXIST, NAME_ALREADY_EXIST.getCode(), NAME_ALREADY_EXIST.getErrorMessage()); - } - } - - /*생일 체크 메서드*/ - private void birthCheck(String birth) { - //예외 코드 10021: 생일이 비어있을 경우 - if (birth.strip().equals("")) { - throw new MemberException(BIRTH_EMPTY, BIRTH_EMPTY.getCode(), BIRTH_EMPTY.getErrorMessage()); - } - - //예외 코드 10022: 생일이 형식에 맞지 않는 경우 - if (!MemberValidator.isBirthValid(birth)) { - throw new MemberException(BIRTH_ALREADY_EXIST, BIRTH_ALREADY_EXIST.getCode(), BIRTH_ALREADY_EXIST.getErrorMessage()); - } - } - - /*성별 체크 메서드*/ - private void genderCheck(Integer gender) { - //예외 코드 10021: 생일이 비어있을 경우 - if (gender == null) { - throw new MemberException(GENDER_EMPTY, GENDER_EMPTY.getCode(), GENDER_EMPTY.getErrorMessage()); - } - } } diff --git a/src/main/java/com/umc/refit/web/controller/MypageController.java b/src/main/java/com/umc/refit/web/controller/MypageController.java index b58fb5c..48b0af3 100644 --- a/src/main/java/com/umc/refit/web/controller/MypageController.java +++ b/src/main/java/com/umc/refit/web/controller/MypageController.java @@ -20,8 +20,8 @@ import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; -import javax.servlet.http.HttpServletRequest; -import javax.validation.Valid; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.Valid; import java.util.List; import java.util.Optional; diff --git a/src/main/java/com/umc/refit/web/controller/OauthController.java b/src/main/java/com/umc/refit/web/controller/OauthController.java index eed2591..c73187f 100644 --- a/src/main/java/com/umc/refit/web/controller/OauthController.java +++ b/src/main/java/com/umc/refit/web/controller/OauthController.java @@ -16,7 +16,7 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.util.Optional; diff --git a/src/main/java/com/umc/refit/web/controller/ReportMemController.java b/src/main/java/com/umc/refit/web/controller/ReportMemController.java index bed954c..ea6a0f2 100644 --- a/src/main/java/com/umc/refit/web/controller/ReportMemController.java +++ b/src/main/java/com/umc/refit/web/controller/ReportMemController.java @@ -9,8 +9,8 @@ import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.*; -import javax.servlet.http.HttpServletRequest; -import javax.validation.Valid; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.Valid; @RestController @RequestMapping("/refit/report") diff --git a/src/main/java/com/umc/refit/web/controller/TradeController.java b/src/main/java/com/umc/refit/web/controller/TradeController.java index 4f3bd82..e4ba620 100644 --- a/src/main/java/com/umc/refit/web/controller/TradeController.java +++ b/src/main/java/com/umc/refit/web/controller/TradeController.java @@ -9,7 +9,7 @@ import org.springframework.security.core.Authentication; import org.springframework.web.bind.annotation.*; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; @RestController @RequestMapping("/refit/trade") diff --git a/src/main/java/com/umc/refit/web/filter/authentication/CustomUserDetailsService.java b/src/main/java/com/umc/refit/web/filter/authentication/CustomUserDetailsService.java index bad264b..111e162 100644 --- a/src/main/java/com/umc/refit/web/filter/authentication/CustomUserDetailsService.java +++ b/src/main/java/com/umc/refit/web/filter/authentication/CustomUserDetailsService.java @@ -9,6 +9,8 @@ import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; +import static com.umc.refit.exception.ExceptionType.MEMBER_IS_NOT_EXIST; + @Service @RequiredArgsConstructor public class CustomUserDetailsService implements UserDetailsService { @@ -16,17 +18,16 @@ public class CustomUserDetailsService implements UserDetailsService { private final MemberRepository memberRepository; @Override - public UserDetails loadUserByUsername(String loginId) { - return memberRepository.findByLoginId(loginId) + public UserDetails loadUserByUsername(String email) { + return memberRepository.findByEmail(email) .map(this::createUserDetails) .orElseThrow(() -> - new UsernameNotFoundException("사용자를 찾을 수 없습니다")); + new UsernameNotFoundException(MEMBER_IS_NOT_EXIST.getErrorMessage())); } - //Member 데이터가 존재한다면 UserDetails 객체로 만들어서 리턴 private UserDetails createUserDetails(Member member) { return User.builder() - .username(member.getLoginId()) + .username(member.getEmail()) .password(member.getPassword()) .roles(member.getRoles().toArray(new String[0])) .build(); diff --git a/src/main/java/com/umc/refit/web/filter/authentication/JwtAuthenticationFilter.java b/src/main/java/com/umc/refit/web/filter/authentication/JwtAuthenticationFilter.java index 08ee4d6..7ce33bf 100644 --- a/src/main/java/com/umc/refit/web/filter/authentication/JwtAuthenticationFilter.java +++ b/src/main/java/com/umc/refit/web/filter/authentication/JwtAuthenticationFilter.java @@ -1,14 +1,16 @@ package com.umc.refit.web.filter.authentication; import com.fasterxml.jackson.databind.ObjectMapper; -import com.nimbusds.jose.JOSEException; -import com.nimbusds.jose.jwk.JWK; -import com.umc.refit.domain.dto.member.ResLoginDto; +import com.umc.refit.domain.dto.member.LoginDto; +import com.umc.refit.domain.dto.member.RefreshTokenDto; import com.umc.refit.domain.entity.Member; import com.umc.refit.exception.member.LoginException; import com.umc.refit.web.service.MemberService; import com.umc.refit.web.service.RefreshTokenService; -import com.umc.refit.web.signature.SecuritySigner; +import com.umc.refit.web.signature.JWTSigner; +import jakarta.servlet.FilterChain; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; @@ -18,78 +20,82 @@ import org.springframework.security.core.userdetails.User; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.Optional; -import static com.umc.refit.exception.ExceptionType.KAKAO_MEMBER_EXIST; +import static com.umc.refit.Util.ONE_HOUR; +import static com.umc.refit.Util.ONE_WEEK; +import static com.umc.refit.exception.ExceptionType.*; @RequiredArgsConstructor public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter { private final HttpSecurity httpSecurity; - private final SecuritySigner securitySigner; - private final JWK jwk; + private final JWTSigner securitySigner; private final MemberService memberService; private final RefreshTokenService refreshTokenService; - /*일반 로그인 인증 시작 메서드*/ @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { + try { + LoginDto loginDto = new ObjectMapper().readValue(request.getInputStream(), LoginDto.class); + + String loginId = loginDto.getLoginId(); + String password = loginDto.getPassword(); + String fcm = loginDto.getFcm(); + + Optional findMember = memberService.findMemberByLoginId(loginId); + + findMember.ifPresent(member -> { + validateSocialMember(member); + member.setFcm(fcm); + memberService.updateFcm(member); + }); + + AuthenticationManager authenticationManager = httpSecurity.getSharedObject(AuthenticationManager.class); + UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(findMember.get().getEmail(), password); + Authentication authentication = authenticationManager.authenticate(authenticationToken); + return authentication; + } catch (IOException e) { + throw new LoginException(LOGIN_FAILED_UNKNOWN, + LOGIN_FAILED_UNKNOWN.getCode(), LOGIN_FAILED_UNKNOWN.getErrorMessage()); + } + } - String loginId = request.getParameter("loginId"); - String password = request.getParameter("password"); - String fcm = request.getParameter("fcm"); - - Optional findMember = memberService.findMemberByLoginId(loginId); - - //이미 카카오 계정이 있는 경우, 예외 발생 - if (findMember.isPresent()) { - if (!(findMember.get().getSocialType() == null)) { - throw new LoginException(KAKAO_MEMBER_EXIST, - KAKAO_MEMBER_EXIST.getCode(), KAKAO_MEMBER_EXIST.getErrorMessage()); - } + private void validateSocialMember(Member member) { + if ("KAKAO".equals(member.getSocialType())) { + throw new LoginException(KAKAO_MEMBER_EXIST, + KAKAO_MEMBER_EXIST.getCode(), KAKAO_MEMBER_EXIST.getErrorMessage()); + } - //fcm 토큰 저장 - Member member = findMember.get(); - member.setFcm(fcm); - memberService.updateFcm(member); + if ("GOOGLE".equals(member.getSocialType())) { + throw new LoginException(GOOGLE_MEMBER_EXIST, + GOOGLE_MEMBER_EXIST.getCode(), GOOGLE_MEMBER_EXIST.getErrorMessage()); } - AuthenticationManager authenticationManager = httpSecurity.getSharedObject(AuthenticationManager.class); - UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginId, password); - Authentication authentication = authenticationManager.authenticate(authenticationToken); - return authentication; + if ("NAVER".equals(member.getSocialType())) { + throw new LoginException(NAVER_MEMBER_EXIST, + NAVER_MEMBER_EXIST.getCode(), NAVER_MEMBER_EXIST.getErrorMessage()); + } } - /*일반 로그인 인증 성공시 토큰 발행하는 메소드*/ @Override - protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws ServletException, IOException { + protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, + FilterChain chain, Authentication authResult) throws IOException { User user = (User) authResult.getPrincipal(); - try { - //엑세스 토큰 및 리프레쉬 토큰 발행 - String accessToken = securitySigner.getJwtToken(user, jwk, 216000000); - String refreshToken = securitySigner.getJwtToken(user, jwk, 216000000); - //리프레쉬 토큰 저장 - refreshTokenService.saveRefreshToken(user.getUsername(), refreshToken); + String accessToken = securitySigner.getJwtToken(user, ONE_HOUR); + String refreshToken = securitySigner.getJwtToken(user, ONE_WEEK); - //엑세스 토큰 헤더를 통해 전달 - response.addHeader("Authorization", "Bearer " + accessToken); //발행받은 토큰을 response 헤더에 담아 응답 + refreshTokenService.saveRefreshToken(user.getUsername(), refreshToken); - //리프레쉬 토큰 바디에 담아 전달 - ResLoginDto resEmailDto = new ResLoginDto(refreshToken); - response.setContentType("application/json"); - ObjectMapper objectMapper = new ObjectMapper(); - String jsonString = objectMapper.writeValueAsString(resEmailDto); - response.getWriter().write(jsonString); + response.addHeader("Authorization", "Bearer " + accessToken); - } catch (JOSEException e) { - e.printStackTrace(); - } + RefreshTokenDto resEmailDto = new RefreshTokenDto(refreshToken); + response.setContentType("application/json"); + ObjectMapper objectMapper = new ObjectMapper(); + String jsonString = objectMapper.writeValueAsString(resEmailDto); + response.getWriter().write(jsonString); } } diff --git a/src/main/java/com/umc/refit/web/filter/authentication/JwtKakaoAuthenticationFilter.java b/src/main/java/com/umc/refit/web/filter/authentication/JwtKakaoAuthenticationFilter.java index 0b70e16..0c5539d 100644 --- a/src/main/java/com/umc/refit/web/filter/authentication/JwtKakaoAuthenticationFilter.java +++ b/src/main/java/com/umc/refit/web/filter/authentication/JwtKakaoAuthenticationFilter.java @@ -3,163 +3,124 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.google.gson.JsonElement; import com.google.gson.JsonParser; -import com.nimbusds.jose.JOSEException; -import com.nimbusds.jose.jwk.JWK; -import com.umc.refit.domain.dto.member.ResLoginDto; +import com.umc.refit.domain.dto.member.RefreshTokenDto; import com.umc.refit.domain.entity.Member; import com.umc.refit.exception.member.LoginException; import com.umc.refit.web.service.MemberService; import com.umc.refit.web.service.RefreshTokenService; -import com.umc.refit.web.signature.SecuritySigner; +import com.umc.refit.web.signature.JWTSigner; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; -import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.userdetails.User; -import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.web.client.RestClientException; +import org.springframework.web.client.RestTemplate; -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.BufferedReader; import java.io.IOException; -import java.io.InputStreamReader; -import java.net.HttpURLConnection; -import java.net.URL; -import java.util.Optional; -import java.util.Random; - -import static com.umc.refit.exception.ExceptionType.BASIC_MEMBER_EXIST; +import static com.umc.refit.Util.*; +import static com.umc.refit.Util.KAKAO_API; +import static com.umc.refit.exception.ExceptionType.*; @RequiredArgsConstructor public class JwtKakaoAuthenticationFilter extends UsernamePasswordAuthenticationFilter { private final HttpSecurity httpSecurity; - private final SecuritySigner securitySigner; - private final JWK jwk; + private final JWTSigner securitySigner; private final MemberService memberService; private final RefreshTokenService refreshTokenService; + private final RestTemplate restTemplate; - //랜덤 문자열 - private String CHARACTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()"; - - /*카카오 로그인 인증 시작*/ @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { - String KAKAO_API_URL = "https://kapi.kakao.com/v2/user/me"; - String DEFAULT_PASSWORD = "KAKAO_LOGIN"; String accessToken = request.getHeader("Authorization"); String fcm = request.getParameter("fcm"); - String EMAIL = getEmailFromKakao(accessToken, KAKAO_API_URL); - - Optional findMember = memberService.findMemberByEmail(EMAIL); - if (findMember.isPresent()) { //멤버가 존재할 경우 - if (findMember.get().getSocialType() == (null)) { //일반 로그인일 경우 - throw new LoginException(BASIC_MEMBER_EXIST, - BASIC_MEMBER_EXIST.getCode(), BASIC_MEMBER_EXIST.getErrorMessage()); - } - - //fcm 토큰 저장 - Member member = findMember.get(); - member.setFcm(fcm); - memberService.updateFcm(member); - } else { - - /*카카오 로그인 시 유일한 멤버 닉네임 생성*/ - String name; - while (true) { - String randomString = generateRandomString(4); - name = "환경지킴이" + randomString; - - Optional member = memberService.findMemberByName(name); - if (member.isEmpty()) { - break; - } - } - - Member member = new Member(EMAIL, DEFAULT_PASSWORD, name, fcm); - memberService.kakaoSave(member); + String userEmail = getEmailFromKakao(accessToken); + findOrCreateMember(userEmail, fcm); + + return authenticateUser(userEmail); + } + + private Member findOrCreateMember(String userEmail, String fcm) { + return memberService.findMemberByEmail(userEmail) + .map(member -> updateMemberFcm(member, fcm)) + .orElseGet(() -> createNewMember(userEmail, fcm)); + } + + private Member updateMemberFcm(Member member, String fcm) { + if (!("KAKAO".equals(member.getSocialType()))) { + throw new LoginException(BASIC_MEMBER_EXIST, + BASIC_MEMBER_EXIST.getCode(), BASIC_MEMBER_EXIST.getErrorMessage()); } + member.setFcm(fcm); + memberService.updateFcm(member); + return member; + } + + private Member createNewMember(String userEmail, String fcm) { + String name; + do { + String randomString = generateRandomString(4); + name = "환경지킴이" + randomString; + } while (memberService.findMemberByName(name).isPresent()); + Member member = new Member(userEmail, name, fcm); + memberService.kakaoSave(member); + return member; + } + + private Authentication authenticateUser(String userEmail) { AuthenticationManager authenticationManager = httpSecurity.getSharedObject(AuthenticationManager.class); - UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(EMAIL, DEFAULT_PASSWORD); - Authentication authentication = authenticationManager.authenticate(authenticationToken); - return authentication; + UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userEmail, userEmail); + return authenticationManager.authenticate(authenticationToken); } - /*카카오 로그인 인증 성공시 토큰 발행하는 메소드*/ @Override protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws ServletException, IOException { User user = (User) authResult.getPrincipal(); - try { - //엑세스 토큰 및 리프레쉬 토큰 발행 - String accessToken = securitySigner.getJwtToken(user, jwk, 216000000); - String refreshToken = securitySigner.getJwtToken(user, jwk, 216000000); - //엑세스 토큰 헤더를 통해 전달 - response.addHeader("Authorization", "Bearer " + accessToken); //발행받은 토큰을 response 헤더에 담아 응답 + String accessToken = securitySigner.getJwtToken(user, ONE_HOUR); + String refreshToken = securitySigner.getJwtToken(user, ONE_WEEK); - //리프레쉬 토큰 저장 - refreshTokenService.saveRefreshToken(user.getUsername(), refreshToken); + response.addHeader("Authorization", "Bearer " + accessToken); - //리프레쉬 토큰 바디에 담아 전달 - ResLoginDto resEmailDto = new ResLoginDto(refreshToken); - response.setContentType("application/json"); - ObjectMapper objectMapper = new ObjectMapper(); - String jsonString = objectMapper.writeValueAsString(resEmailDto); - response.getWriter().write(jsonString); + refreshTokenService.saveRefreshToken(user.getUsername(), refreshToken); - } catch (JOSEException e) { - e.printStackTrace(); - } + RefreshTokenDto resEmailDto = new RefreshTokenDto(refreshToken); + response.setContentType("application/json"); + ObjectMapper objectMapper = new ObjectMapper(); + String jsonString = objectMapper.writeValueAsString(resEmailDto); + response.getWriter().write(jsonString); } - /*카카오 인가 서버에 이메일 정보 요청*/ - private String getEmailFromKakao(String accessToken, String KAKAO_API_URL) { - String email = ""; + private String getEmailFromKakao(String accessToken) { try { - URL url = new URL(KAKAO_API_URL); - HttpURLConnection conn = (HttpURLConnection) url.openConnection(); - - conn.setRequestMethod("POST"); - conn.setDoOutput(true); - conn.setRequestProperty("Authorization", "Bearer " + accessToken); - - BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream())); - StringBuilder result = new StringBuilder(); - - String line; - while ((line = br.readLine()) != null) { - result.append(line); - } - - JsonElement element = JsonParser.parseString(result.toString()); - email = element.getAsJsonObject().get("kakao_account").getAsJsonObject().get("email").getAsString(); - br.close(); - } catch (IOException exception) { - exception.printStackTrace(); + HttpHeaders headers = new HttpHeaders(); + headers.set("Authorization", "Bearer " + accessToken); + HttpEntity entity = new HttpEntity<>(headers); + + ResponseEntity response = restTemplate.exchange( + KAKAO_API, HttpMethod.POST, entity, String.class); + + JsonElement element = JsonParser.parseString(response.getBody()); + return element.getAsJsonObject().get("kakao_account").getAsJsonObject().get("email").getAsString(); + } catch (RestClientException e) { + throw new LoginException(LOGIN_FAILED_UNKNOWN, + LOGIN_FAILED_UNKNOWN.getCode(), LOGIN_FAILED_UNKNOWN.getErrorMessage()); } - return email; - } - - /*랜덤 문자열 생성 메서드*/ - public String generateRandomString(int length) { - Random random = new Random(); - StringBuilder stringBuilder = new StringBuilder(length); - - for (int i = 0; i < length; i++) { - int randomIndex = random.nextInt(CHARACTERS.length()); - stringBuilder.append(CHARACTERS.charAt(randomIndex)); - } - - return stringBuilder.toString(); } } diff --git a/src/main/java/com/umc/refit/web/filter/authorization/JwtAuthorizationRsaFilter.java b/src/main/java/com/umc/refit/web/filter/authorization/JwtAuthorizationRsaFilter.java index 2186a6c..f6ffa60 100644 --- a/src/main/java/com/umc/refit/web/filter/authorization/JwtAuthorizationRsaFilter.java +++ b/src/main/java/com/umc/refit/web/filter/authorization/JwtAuthorizationRsaFilter.java @@ -1,9 +1,13 @@ package com.umc.refit.web.filter.authorization; -import com.nimbusds.jose.crypto.RSASSAVerifier; -import com.nimbusds.jose.jwk.RSAKey; -import com.nimbusds.jwt.SignedJWT; import com.umc.refit.exception.ExceptionType; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; @@ -12,109 +16,98 @@ import org.springframework.util.AntPathMatcher; import org.springframework.web.filter.OncePerRequestFilter; -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import java.io.IOException; +import java.security.Key; import java.util.Date; import java.util.List; import java.util.UUID; import static com.umc.refit.exception.ExceptionType.*; -/*Bearer 토큰을 RSA 알고리즘에 의해 검증하며 검증 성공시 인증 및 인가를 처리하는 필터*/ +@RequiredArgsConstructor public class JwtAuthorizationRsaFilter extends OncePerRequestFilter { - private RSAKey jwk; + private final Key key; - public JwtAuthorizationRsaFilter(RSAKey rsaKey) { - this.jwk = rsaKey; - } - - /*인가 처리를 거치지 않는 URL 설정 필터*/ @Override protected boolean shouldNotFilter(HttpServletRequest request) { String path = request.getServletPath(); request.getMethod(); AntPathMatcher pathMatcher = new AntPathMatcher(); - return (pathMatcher.match("/auth/join", path) //일반 회원 가입 - || pathMatcher.match("/auth/kakao", path) //카카오 로그인 - || pathMatcher.match("/auth/email", path) //이메일 인증 - || pathMatcher.match("/auth/find/id", path) //아이디 찾기 - || pathMatcher.match("/auth/reset/password", path) //패스워드 찾기 - || pathMatcher.match("/auth/login", path) //일반 로그인 + return (pathMatcher.match("/auth/join", path) + || pathMatcher.match("/auth/kakao", path) + || pathMatcher.match("/auth/email", path) + || pathMatcher.match("/auth/find/id", path) + || pathMatcher.match("/auth/reset/password", path) + || pathMatcher.match("/auth/login", path) || pathMatcher.match("/*.html", path) || pathMatcher.match("/oauth2/fcm", path) || pathMatcher.match("/oauth2/image", path) || pathMatcher.match("/auth/join/name", path) -// || pathMatcher.match("/**", path) //API 테스트를 위해 모든 로직에 대해 인가 제외 ); } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { + try { + if (tokenResolve(request)) { + setException(request, TOKEN_NOT_EXIST, chain, response); + return; + } - ExceptionType errorType = null; + String token = getToken(request); + if (token == null) { + setException(request, TOKEN_INVALID, chain, response); + return; + } - /*토큰 헤더 검증*/ - if (tokenResolve(request, response, chain)){ - errorType = TOKEN_NOT_EXIST; - } else { + Claims claims = Jwts.parserBuilder() + .setSigningKey(key) + .build() + .parseClaimsJws(token) + .getBody(); - //Bearer를 제거한 토큰 값만 추출(header + payload + signature) - String token = getToken(request); - SignedJWT signedJWT; - try { - - //header와 payload와 signature 값이 속성으로 매핑됨 - signedJWT = SignedJWT.parse(token); - RSASSAVerifier jwsVerifier = new RSASSAVerifier(jwk.toRSAPublicKey()); - - /*토큰 만료기간 검증*/ - Date expirationTime = signedJWT.getJWTClaimsSet().getExpirationTime(); - Date now = new Date(); - if (now.after(expirationTime)) { - errorType = TOKEN_EXPIRED; - } else { - - boolean verify = signedJWT.verify(jwsVerifier); - - if (verify) { - String username = signedJWT.getJWTClaimsSet().getClaim("id").toString(); - List authority = (List) signedJWT.getJWTClaimsSet().getClaim("role"); - - //사용자 정보를 만들어서 인증 객체 생성 후 Security Context에 보관 - if (username != null) { - UserDetails user = User.builder().username(username) - .password(UUID.randomUUID().toString()) - .authorities(authority.get(0)) - .build(); - Authentication authentication = new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities()); - SecurityContextHolder.getContext().setAuthentication(authentication); - } - } else { - errorType = TOKEN_INVALID; - } - } - } catch (Exception e) { - errorType = TOKEN_INVALID; + if (isTokenExpired(claims)) { + setException(request, TOKEN_EXPIRED, chain, response); + return; } + + createAuthentication(claims); + } catch (Exception e) { + setException(request, TOKEN_INVALID, chain, response); + return; } - /*토큰 예외 처리*/ - if (errorType != null) { - request.setAttribute("exception", errorType); + + chain.doFilter(request, response); + } + + private void setException(HttpServletRequest request, ExceptionType errorType, FilterChain chain, HttpServletResponse response) throws IOException, ServletException { + request.setAttribute("exception", errorType); + chain.doFilter(request, response); + } + + private boolean isTokenExpired(Claims claims) { + return new Date().after(claims.getExpiration()); + } + + private void createAuthentication(Claims claims) { + String username = claims.getSubject(); + List authority = (List) claims.get("role"); + if (username != null && !authority.isEmpty()) { + UserDetails user = User.builder().username(username) + .password(UUID.randomUUID().toString()) + .authorities(authority.get(0)) + .build(); + Authentication authentication = new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities()); + SecurityContextHolder.getContext().setAuthentication(authentication); } - chain.doFilter(request, response); //다음 필터로 넘어감 } - /*Authorization 헤더로 넘어온 엑세스 토큰 값 추출*/ protected String getToken(HttpServletRequest request) { return request.getHeader("Authorization").replace("Bearer ", ""); } - /*헤더 유효성 검사*/ - protected boolean tokenResolve(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { + protected boolean tokenResolve(HttpServletRequest request) { String header = request.getHeader("Authorization"); return header == null || !header.startsWith("Bearer "); } diff --git a/src/main/java/com/umc/refit/web/filter/exception/CustomAuthenticationEntryPoint.java b/src/main/java/com/umc/refit/web/filter/exception/CustomAuthenticationEntryPoint.java index 4712835..153df13 100644 --- a/src/main/java/com/umc/refit/web/filter/exception/CustomAuthenticationEntryPoint.java +++ b/src/main/java/com/umc/refit/web/filter/exception/CustomAuthenticationEntryPoint.java @@ -1,15 +1,16 @@ package com.umc.refit.web.filter.exception; -import com.nimbusds.jose.shaded.json.JSONObject; +import com.fasterxml.jackson.databind.ObjectMapper; import com.umc.refit.exception.ExceptionType; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.stereotype.Component; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import java.io.IOException; +import java.util.HashMap; @Slf4j @Component @@ -25,9 +26,12 @@ private void setResponse(HttpServletResponse response, ExceptionType errorType) response.setContentType("application/json;charset=UTF-8"); response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); - JSONObject responseJson = new JSONObject(); - responseJson.put("errorMessage", errorType.getErrorMessage()); - responseJson.put("code", errorType.getCode()); - response.getWriter().print(responseJson); + ObjectMapper mapper = new ObjectMapper(); + HashMap errorResponse = new HashMap<>(); + errorResponse.put("errorMessage", errorType.getErrorMessage()); + errorResponse.put("code", errorType.getCode()); + + String jsonResponse = mapper.writeValueAsString(errorResponse); + response.getWriter().print(jsonResponse); } } diff --git a/src/main/java/com/umc/refit/web/filter/exception/CustomAuthenticationFailureHandler.java b/src/main/java/com/umc/refit/web/filter/exception/CustomAuthenticationFailureHandler.java index f00a513..a71509b 100644 --- a/src/main/java/com/umc/refit/web/filter/exception/CustomAuthenticationFailureHandler.java +++ b/src/main/java/com/umc/refit/web/filter/exception/CustomAuthenticationFailureHandler.java @@ -1,7 +1,9 @@ package com.umc.refit.web.filter.exception; -import com.nimbusds.jose.shaded.json.JSONObject; +import com.fasterxml.jackson.databind.ObjectMapper; import com.umc.refit.exception.member.LoginException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import org.springframework.http.HttpStatus; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.core.AuthenticationException; @@ -9,12 +11,10 @@ import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.stereotype.Component; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import java.io.IOException; +import java.util.HashMap; -import static com.umc.refit.exception.ExceptionType.LOGIN_FAILED; -import static com.umc.refit.exception.ExceptionType.LOGIN_FAILED_ALL; +import static com.umc.refit.exception.ExceptionType.*; @Component public class CustomAuthenticationFailureHandler implements AuthenticationFailureHandler { @@ -22,27 +22,29 @@ public class CustomAuthenticationFailureHandler implements AuthenticationFailure @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException { + if (exception instanceof UsernameNotFoundException || exception instanceof BadCredentialsException) { + sendErrorResponse(response, LOGIN_FAILED.getErrorMessage(), LOGIN_FAILED.getCode()); + return; + } + + if (exception instanceof LoginException) { + sendErrorResponse(response, ((LoginException) exception).getErrorMessage(), ((LoginException) exception).getCode()); + return; + } + sendErrorResponse(response, LOGIN_FAILED_UNKNOWN.getErrorMessage(), LOGIN_FAILED_UNKNOWN.getCode()); + } + + private void sendErrorResponse(HttpServletResponse response, String errorMessage, int code) throws IOException { response.setStatus(HttpStatus.BAD_REQUEST.value()); response.setContentType("application/json;charset=UTF-8"); - String errorMessage; - int code; - - if ((exception instanceof UsernameNotFoundException) || (exception instanceof BadCredentialsException)){ - errorMessage = LOGIN_FAILED.getErrorMessage(); - code = LOGIN_FAILED.getCode(); - } else if (exception instanceof LoginException) { - errorMessage = ((LoginException) exception).getErrorMessage(); - code = ((LoginException) exception).getCode(); - } else { - errorMessage = LOGIN_FAILED_ALL.getErrorMessage(); - code = LOGIN_FAILED_ALL.getCode(); - } + ObjectMapper mapper = new ObjectMapper(); + HashMap errorResponse = new HashMap<>(); + errorResponse.put("errorMessage", errorMessage); + errorResponse.put("code", code); - JSONObject responseJson = new JSONObject(); - responseJson.put("errorMessage", errorMessage); - responseJson.put("code", code); - response.getWriter().print(responseJson); + String jsonResponse = mapper.writeValueAsString(errorResponse); + response.getWriter().print(jsonResponse); } } \ No newline at end of file diff --git a/src/main/java/com/umc/refit/web/repository/CommunityRepositoryImpl.java b/src/main/java/com/umc/refit/web/repository/CommunityRepositoryImpl.java index a3aa1ed..38ce3be 100644 --- a/src/main/java/com/umc/refit/web/repository/CommunityRepositoryImpl.java +++ b/src/main/java/com/umc/refit/web/repository/CommunityRepositoryImpl.java @@ -5,9 +5,8 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Repository; -import javax.persistence.EntityManager; +import jakarta.persistence.EntityManager; import java.util.List; - import static com.umc.refit.domain.entity.QPosts.posts; @Repository diff --git a/src/main/java/com/umc/refit/web/service/BlockService.java b/src/main/java/com/umc/refit/web/service/BlockService.java index e1780f1..6a5e453 100644 --- a/src/main/java/com/umc/refit/web/service/BlockService.java +++ b/src/main/java/com/umc/refit/web/service/BlockService.java @@ -9,7 +9,7 @@ import org.springframework.security.core.Authentication; import org.springframework.stereotype.Service; -import javax.transaction.Transactional; +import jakarta.transaction.Transactional; import java.util.List; import java.util.stream.Collectors; diff --git a/src/main/java/com/umc/refit/web/service/CmImgService.java b/src/main/java/com/umc/refit/web/service/CmImgService.java index e24823d..d6a1cc3 100644 --- a/src/main/java/com/umc/refit/web/service/CmImgService.java +++ b/src/main/java/com/umc/refit/web/service/CmImgService.java @@ -8,7 +8,7 @@ import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; -import javax.transaction.Transactional; +import jakarta.transaction.Transactional; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/com/umc/refit/web/service/CommunityService.java b/src/main/java/com/umc/refit/web/service/CommunityService.java index d32f896..7a4e3d6 100644 --- a/src/main/java/com/umc/refit/web/service/CommunityService.java +++ b/src/main/java/com/umc/refit/web/service/CommunityService.java @@ -14,7 +14,7 @@ import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; -import javax.transaction.Transactional; +import jakarta.transaction.Transactional; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; diff --git a/src/main/java/com/umc/refit/web/service/EmailService.java b/src/main/java/com/umc/refit/web/service/EmailService.java index 1d0e407..8c755dc 100644 --- a/src/main/java/com/umc/refit/web/service/EmailService.java +++ b/src/main/java/com/umc/refit/web/service/EmailService.java @@ -2,15 +2,13 @@ import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; -import org.springframework.mail.SimpleMailMessage; import org.springframework.mail.javamail.JavaMailSender; -import org.springframework.mail.javamail.MimeMessageHelper; import org.springframework.stereotype.Service; import org.thymeleaf.context.Context; import org.thymeleaf.spring5.SpringTemplateEngine; -import javax.mail.MessagingException; -import javax.mail.internet.MimeMessage; +import jakarta.mail.MessagingException; +import jakarta.mail.internet.MimeMessage; import java.security.SecureRandom; import java.util.Random; @@ -18,7 +16,6 @@ @RequiredArgsConstructor public class EmailService { - private final JavaMailSender mailSender; private final SpringTemplateEngine templateEngine; @@ -59,7 +56,7 @@ public MimeMessage joinEmailForm(String email) throws MessagingException { String title = "[RE-FIT] 회원가입 인증번호 안내 이메일 입니다."; MimeMessage message = mailSender.createMimeMessage(); - message.addRecipients(MimeMessage.RecipientType.TO, email); //보낼 이메일 설정 + message.addRecipients(MimeMessage.RecipientType.TO, email); message.setSubject(title); message.setFrom(from); diff --git a/src/main/java/com/umc/refit/web/service/MemberService.java b/src/main/java/com/umc/refit/web/service/MemberService.java index e8982a3..2890a7a 100644 --- a/src/main/java/com/umc/refit/web/service/MemberService.java +++ b/src/main/java/com/umc/refit/web/service/MemberService.java @@ -1,20 +1,28 @@ package com.umc.refit.web.service; +import com.umc.refit.domain.dto.member.TokenDto; import com.umc.refit.domain.entity.Member; import com.umc.refit.web.repository.MemberRepository; +import com.umc.refit.web.signature.JWTSigner; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.userdetails.User; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import java.util.Optional; +import static com.umc.refit.Util.*; + @Service @RequiredArgsConstructor public class MemberService { private final MemberRepository memberRepository; private final PasswordEncoder passwordEncoder; + private final RefreshTokenService refreshTokenService; + private final JWTSigner securitySigner; @Value("${member.image}") private String imageUrl; @@ -70,4 +78,19 @@ public void updateMemberPassword(Member member, String newPassword) { public boolean isPasswordMatch(String requestCurrentPassword, String encryptPassword) { return passwordEncoder.matches(requestCurrentPassword, encryptPassword); } + + public void deleteRefreshToken(String token) { + refreshTokenService.deleteRefreshToken(token); + return; + } + + public TokenDto refreshAuthenticationToken(Authentication authentication) { + User user = (User) authentication.getPrincipal(); + String accessToken = securitySigner.getJwtToken(user, ONE_HOUR); + String refreshToken = securitySigner.getJwtToken(user, ONE_WEEK); + + refreshTokenService.saveRefreshToken(user.getUsername(), refreshToken); + + return new TokenDto(accessToken, refreshToken); + } } diff --git a/src/main/java/com/umc/refit/web/service/ReportMemService.java b/src/main/java/com/umc/refit/web/service/ReportMemService.java index 588a678..b47692c 100644 --- a/src/main/java/com/umc/refit/web/service/ReportMemService.java +++ b/src/main/java/com/umc/refit/web/service/ReportMemService.java @@ -9,7 +9,7 @@ import org.springframework.security.core.Authentication; import org.springframework.stereotype.Service; -import javax.transaction.Transactional; +import jakarta.transaction.Transactional; import static com.umc.refit.exception.ExceptionType.*; diff --git a/src/main/java/com/umc/refit/web/service/S3UploadService.java b/src/main/java/com/umc/refit/web/service/S3UploadService.java index f66e65c..e39ab2a 100644 --- a/src/main/java/com/umc/refit/web/service/S3UploadService.java +++ b/src/main/java/com/umc/refit/web/service/S3UploadService.java @@ -12,7 +12,7 @@ import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; -import javax.transaction.Transactional; +import jakarta.transaction.Transactional; import java.io.IOException; import java.util.UUID; @@ -23,10 +23,8 @@ @Transactional public class S3UploadService { - private final AmazonS3Client amazonS3Client; private final AmazonS3 s3; - /*멀티파트파일을 지정한 s3 버킷의 폴더에 저장 * ImageDto 객체에 key name, url 넣어서 반환 * */ @@ -43,7 +41,7 @@ public ImageDto uploadFile(MultipartFile multipartFile, String bucketName, Strin PutObjectRequest request = new PutObjectRequest(bucketName, keyName, multipartFile.getInputStream(), metadata).withCannedAcl(CannedAccessControlList.PublicRead); s3.putObject(request); - String uploadImageUrl = amazonS3Client.getUrl(bucketName, keyName).toString(); + String uploadImageUrl = s3.getUrl(bucketName, keyName).toString(); ImageDto imageDto = new ImageDto(keyName, uploadImageUrl); return imageDto; diff --git a/src/main/java/com/umc/refit/web/service/ScrapService.java b/src/main/java/com/umc/refit/web/service/ScrapService.java index aae6a08..c7de070 100644 --- a/src/main/java/com/umc/refit/web/service/ScrapService.java +++ b/src/main/java/com/umc/refit/web/service/ScrapService.java @@ -11,7 +11,7 @@ import org.springframework.security.core.Authentication; import org.springframework.stereotype.Service; -import javax.transaction.Transactional; +import jakarta.transaction.Transactional; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; diff --git a/src/main/java/com/umc/refit/web/service/ValidateService.java b/src/main/java/com/umc/refit/web/service/ValidateService.java new file mode 100644 index 0000000..d85aeea --- /dev/null +++ b/src/main/java/com/umc/refit/web/service/ValidateService.java @@ -0,0 +1,90 @@ +package com.umc.refit.web.service; + +import com.umc.refit.domain.dto.member.JoinDto; +import com.umc.refit.exception.member.MemberException; +import com.umc.refit.exception.validator.MemberValidator; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import static com.umc.refit.exception.ExceptionType.*; +import static com.umc.refit.exception.ExceptionType.GENDER_EMPTY; + +@Service +@RequiredArgsConstructor +public class ValidateService { + + private final MemberService memberService; + + public void validateJoin(JoinDto joinDto) { + loginIdCheck(joinDto.getLoginId()); + passwordCheck(joinDto.getPassword()); + emailCheck(joinDto.getEmail()); + nameCheck(joinDto.getName()); + birthCheck(joinDto.getBirth()); + genderCheck(joinDto.getGender()); + } + + public void loginIdCheck(String loginId) { + if (loginId.strip().equals("")) { + throw new MemberException(ID_EMPTY, ID_EMPTY.getCode(), ID_EMPTY.getErrorMessage()); + } + + if (!MemberValidator.isLoginValid(loginId)) { + throw new MemberException(ID_INVALID, ID_INVALID.getCode(), ID_INVALID.getErrorMessage()); + } + + if (memberService.findMemberByLoginId(loginId).isPresent()) { + throw new MemberException(ID_ALREADY_EXIST, ID_ALREADY_EXIST.getCode(), ID_ALREADY_EXIST.getErrorMessage()); + } + } + + public void passwordCheck(String password) { + if (password.strip().equals("")) { + throw new MemberException(PASSWORD_EMPTY, PASSWORD_EMPTY.getCode(), PASSWORD_EMPTY.getErrorMessage()); + } + + if (!MemberValidator.isPasswordValid(password)) { + throw new MemberException(PASSWORD_INVALID, PASSWORD_INVALID.getCode(), PASSWORD_INVALID.getErrorMessage()); + } + } + + public void emailCheck(String email) { + if (email.strip().equals("")) { + throw new MemberException(EMAIL_EMPTY, EMAIL_EMPTY.getCode(), EMAIL_EMPTY.getErrorMessage()); + } + + if (memberService.findMemberByEmail(email).isPresent()) { + throw new MemberException(EMAIL_ALREADY_EXIST, EMAIL_ALREADY_EXIST.getCode(), EMAIL_ALREADY_EXIST.getErrorMessage()); + } + + if (!MemberValidator.isEmailValid(email)) { + throw new MemberException(EMAIL_INVALID, EMAIL_INVALID.getCode(), EMAIL_INVALID.getErrorMessage()); + } + } + + public void nameCheck(String name) { + if (name.strip().equals("")) { + throw new MemberException(NAME_EMPTY, NAME_EMPTY.getCode(), NAME_EMPTY.getErrorMessage()); + } + + if (memberService.findMemberByName(name).isPresent()) { + throw new MemberException(NAME_ALREADY_EXIST, NAME_ALREADY_EXIST.getCode(), NAME_ALREADY_EXIST.getErrorMessage()); + } + } + + public void birthCheck(String birth) { + if (birth.strip().equals("")) { + throw new MemberException(BIRTH_EMPTY, BIRTH_EMPTY.getCode(), BIRTH_EMPTY.getErrorMessage()); + } + + if (!MemberValidator.isBirthValid(birth)) { + throw new MemberException(BIRTH_ALREADY_EXIST, BIRTH_ALREADY_EXIST.getCode(), BIRTH_ALREADY_EXIST.getErrorMessage()); + } + } + + public void genderCheck(Integer gender) { + if (gender == null) { + throw new MemberException(GENDER_EMPTY, GENDER_EMPTY.getCode(), GENDER_EMPTY.getErrorMessage()); + } + } +} diff --git a/src/main/java/com/umc/refit/web/signature/JWTSigner.java b/src/main/java/com/umc/refit/web/signature/JWTSigner.java new file mode 100644 index 0000000..1fd8515 --- /dev/null +++ b/src/main/java/com/umc/refit/web/signature/JWTSigner.java @@ -0,0 +1,36 @@ +package com.umc.refit.web.signature; + +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.security.Keys; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +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.Date; +import java.util.List; +import java.util.stream.Collectors; + +@Component +@RequiredArgsConstructor +public class JWTSigner { + + @Value("${token.issuer}") + private String issuer; + private final Key key; + + public String getJwtToken(User user, Integer time) { + List authority = user.getAuthorities().stream().map(auth -> auth.getAuthority()).collect(Collectors.toList()); + + return Jwts.builder() + .setSubject(user.getUsername()) + .setIssuer(issuer) + .setExpiration(new Date(new Date().getTime() + time)) + .claim("role", authority) + .signWith(key, SignatureAlgorithm.HS256) + .compact(); + } +} diff --git a/src/main/java/com/umc/refit/web/signature/RSASecuritySigner.java b/src/main/java/com/umc/refit/web/signature/RSASecuritySigner.java deleted file mode 100644 index f0f6262..0000000 --- a/src/main/java/com/umc/refit/web/signature/RSASecuritySigner.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.umc.refit.web.signature; - -import com.nimbusds.jose.JOSEException; -import com.nimbusds.jose.crypto.RSASSASigner; -import com.nimbusds.jose.jwk.JWK; -import com.nimbusds.jose.jwk.RSAKey; -import org.springframework.security.core.userdetails.UserDetails; - -import java.math.BigInteger; - -/*RSA 방식의 토큰을 서명하고 발행하는 클래스*/ -public class RSASecuritySigner extends SecuritySigner { - - //개인키로 서명히고 부모에게 전달 - public String getJwtToken(UserDetails user, JWK jwk, Integer time) throws JOSEException { - - RSASSASigner jwsSigner = new RSASSASigner(((RSAKey)jwk).toRSAPrivateKey()); - - return getJwtTokenInternal(jwsSigner, user, jwk, time); - } -} diff --git a/src/main/java/com/umc/refit/web/signature/SecuritySigner.java b/src/main/java/com/umc/refit/web/signature/SecuritySigner.java deleted file mode 100644 index 0bc3b13..0000000 --- a/src/main/java/com/umc/refit/web/signature/SecuritySigner.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.umc.refit.web.signature; - -import com.nimbusds.jose.JOSEException; -import com.nimbusds.jose.JWSAlgorithm; -import com.nimbusds.jose.JWSHeader; -import com.nimbusds.jose.JWSSigner; -import com.nimbusds.jose.jwk.JWK; -import com.nimbusds.jwt.JWTClaimsSet; -import com.nimbusds.jwt.SignedJWT; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.security.core.userdetails.UserDetails; - -import java.math.BigInteger; -import java.util.Date; -import java.util.List; -import java.util.stream.Collectors; - -public abstract class SecuritySigner { - - @Value("${token.issuer}") - private String issuer; - - /*토큰 발행 메서드*/ - public String getJwtTokenInternal(JWSSigner jwsSigner, UserDetails user, JWK jwk, Integer time) throws JOSEException { - - JWSHeader header= new JWSHeader.Builder((JWSAlgorithm) jwk.getAlgorithm()).keyID(jwk.getKeyID()).build(); - - List authority = user.getAuthorities().stream().map(auth -> auth.getAuthority()).collect(Collectors.toList()); - JWTClaimsSet jwtClaimsSet = new JWTClaimsSet.Builder() - .subject(user.getUsername()) - .issuer(issuer) - .claim("id", user.getUsername()) - .claim("role", authority) - .expirationTime(new Date(new Date().getTime() + time)) - .build(); - - //최종 서명 객체가 SignedJWT 객체 - SignedJWT signedJWT = new SignedJWT(header, jwtClaimsSet); - signedJWT.sign(jwsSigner); - String jwtToken = signedJWT.serialize(); - - //서명에 성공하면 JWT 토큰 발행 - return jwtToken; - } - - //UserDetails가 UserDetailsService 에서 반환된 User 객체 - public abstract String getJwtToken(UserDetails user, JWK jwk, Integer time) throws JOSEException; -} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 3049bb0..ab9cb59 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -21,7 +21,7 @@ spring: properties: hibernate: - dialect: org.hibernate.dialect.MySQL5Dialect + dialect: org.hibernate.dialect.MySQL8Dialect format_sql: true default_batch_fetch_size: 100