From 4719fd6a5ec7e66a4ec8b116788492680c463706 Mon Sep 17 00:00:00 2001 From: kssumin <201566@jnu.ac.kr> Date: Sun, 1 Oct 2023 22:58:10 +0900 Subject: [PATCH 01/18] =?UTF-8?q?chore(#19)=20:=20mysql=20local=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=EB=AA=85=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/application-local-mysql.yml | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 src/main/resources/application-local-mysql.yml diff --git a/src/main/resources/application-local-mysql.yml b/src/main/resources/application-local-mysql.yml new file mode 100644 index 0000000..200f829 --- /dev/null +++ b/src/main/resources/application-local-mysql.yml @@ -0,0 +1,20 @@ +spring: + config: + activate: + on-profile: local-mysql + datasource: + url: jdbc:mysql://localhost:13306/golajuma?allowPublicKeyRetrieval=true&rewriteBatchedStatements=true + username: root + password: root + driver-class-name: com.mysql.cj.jdbc.Driver + jpa: + hibernate: + ddl-auto: update + properties: + hibernate: + format_sql: true + url: + +logging: + level: + sql: debug \ No newline at end of file From fd4d007e8be9eff615591938010ec10c59704143 Mon Sep 17 00:00:00 2001 From: kssumin <201566@jnu.ac.kr> Date: Sun, 1 Oct 2023 22:59:07 +0900 Subject: [PATCH 02/18] =?UTF-8?q?feat=20:(#19)=20=EB=A1=9C=EC=BB=AC=20?= =?UTF-8?q?=ED=99=98=EA=B2=BD=20redis=20=EA=B5=AC=EC=B6=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 6 ++++++ resources/local-develop-environment/docker-compose.yml | 8 +++++++- src/main/resources/application-local-redis.yml | 7 +++++++ 3 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 src/main/resources/application-local-redis.yml diff --git a/build.gradle b/build.gradle index c40ed21..6d53761 100644 --- a/build.gradle +++ b/build.gradle @@ -51,6 +51,7 @@ dependencies { // database implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'mysql:mysql-connector-java' + implementation 'org.springframework.boot:spring-boot-starter-data-redis' // test testImplementation 'org.springframework.boot:spring-boot-starter-test' @@ -58,6 +59,11 @@ dependencies { // infra implementation 'org.springframework.boot:spring-boot-starter-security' + + // jwt + implementation "io.jsonwebtoken:jjwt-api:${jsonwebtokenVersion}" + implementation "io.jsonwebtoken:jjwt-impl:${jsonwebtokenVersion}" + implementation "io.jsonwebtoken:jjwt-jackson:${jsonwebtokenVersion}" } test { diff --git a/resources/local-develop-environment/docker-compose.yml b/resources/local-develop-environment/docker-compose.yml index 5f1b5ce..806adfc 100644 --- a/resources/local-develop-environment/docker-compose.yml +++ b/resources/local-develop-environment/docker-compose.yml @@ -21,4 +21,10 @@ services: environment: - ADMINER_DEFAULT_SERVER=golajuma-mysql8 - ADMINER_DESIGN=nette - - ADMINER_PLUGINS=tables-filter tinymce \ No newline at end of file + - ADMINER_PLUGINS=tables-filter tinymce + + redis-docker: + container_name: golajuma-redis + image: redis:latest + ports: + - "16379:6379" diff --git a/src/main/resources/application-local-redis.yml b/src/main/resources/application-local-redis.yml new file mode 100644 index 0000000..36a3021 --- /dev/null +++ b/src/main/resources/application-local-redis.yml @@ -0,0 +1,7 @@ +spring: + redis: + host: localhost + port: 16379 + +redis: + timeToLive : 60 \ No newline at end of file From 40607cde73daf3f3dad47d73379f82ed4eac28be Mon Sep 17 00:00:00 2001 From: kssumin <201566@jnu.ac.kr> Date: Sun, 1 Oct 2023 22:59:32 +0900 Subject: [PATCH 03/18] =?UTF-8?q?fix=20:(#19)=20local=20yml=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application-local.yml | 20 -------------------- src/main/resources/application.yml | 7 ++++++- 2 files changed, 6 insertions(+), 21 deletions(-) delete mode 100644 src/main/resources/application-local.yml diff --git a/src/main/resources/application-local.yml b/src/main/resources/application-local.yml deleted file mode 100644 index 10ffada..0000000 --- a/src/main/resources/application-local.yml +++ /dev/null @@ -1,20 +0,0 @@ -spring: - config: - activate: - on-profile: local - datasource: - url: jdbc:mysql://localhost:13306/golajuma?allowPublicKeyRetrieval=true&rewriteBatchedStatements=true - username: root - password: root - driver-class-name: com.mysql.cj.jdbc.Driver - jpa: - hibernate: - ddl-auto: update - properties: - hibernate: - format_sql: true - url: - -logging: - level: - sql: debug \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 7455971..cc51800 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,3 +1,8 @@ spring: profiles: - default: local + group: + local: + - local-mysql + - local-security + - local-redis + active: local From 12b827cd097b2154186ac8b9f37693f61470d2ca Mon Sep 17 00:00:00 2001 From: kssumin <201566@jnu.ac.kr> Date: Sun, 1 Oct 2023 23:00:04 +0900 Subject: [PATCH 04/18] =?UTF-8?q?feat=20:=20(#19)=20security=20local=20yml?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application-local-security.yml | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 src/main/resources/application-local-security.yml diff --git a/src/main/resources/application-local-security.yml b/src/main/resources/application-local-security.yml new file mode 100644 index 0000000..25a2d14 --- /dev/null +++ b/src/main/resources/application-local-security.yml @@ -0,0 +1,9 @@ +security: + jwt: + token: + access: + secretKey: asccesssecretkeygolajumasecrekey + validTime: 1800000 + refresh: + secretKey: refreshsecretkeygolajumasecrekey + validTime: 604800000 \ No newline at end of file From e4cfd6becd2ce1faaacfd41b8348914a0d6ab6e8 Mon Sep 17 00:00:00 2001 From: kssumin <201566@jnu.ac.kr> Date: Sun, 1 Oct 2023 23:00:35 +0900 Subject: [PATCH 05/18] =?UTF-8?q?feat=20:(#19)=20login=20request=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../web/dto/request/LoginUserRequest.java | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 src/main/java/com/kakao/golajuma/auth/web/dto/request/LoginUserRequest.java diff --git a/src/main/java/com/kakao/golajuma/auth/web/dto/request/LoginUserRequest.java b/src/main/java/com/kakao/golajuma/auth/web/dto/request/LoginUserRequest.java new file mode 100644 index 0000000..1a8231d --- /dev/null +++ b/src/main/java/com/kakao/golajuma/auth/web/dto/request/LoginUserRequest.java @@ -0,0 +1,24 @@ +package com.kakao.golajuma.auth.web.dto.request; + +import com.kakao.golajuma.auth.web.supplier.EmailSupplier; +import com.kakao.golajuma.common.marker.AbstractRequestDto; +import javax.validation.constraints.Email; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@AllArgsConstructor +@NoArgsConstructor +@Builder(toBuilder = true) +public class LoginUserRequest implements AbstractRequestDto, EmailSupplier { + + @NotNull @Email private String email; + + @NotNull + @Size(min = 8) + private String password; +} From d598ad4846e9d1ed207bdec94c20f8188a33d001 Mon Sep 17 00:00:00 2001 From: kssumin <201566@jnu.ac.kr> Date: Sun, 1 Oct 2023 23:01:12 +0900 Subject: [PATCH 06/18] =?UTF-8?q?feat=20:(#19)=20header=EB=A5=BC=20?= =?UTF-8?q?=EA=B0=80=EC=A7=80=EB=8F=84=EB=A1=9D=20response=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kakao/golajuma/common/support/respnose/ApiResponse.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/java/com/kakao/golajuma/common/support/respnose/ApiResponse.java b/src/main/java/com/kakao/golajuma/common/support/respnose/ApiResponse.java index fe4710d..3912bc9 100644 --- a/src/main/java/com/kakao/golajuma/common/support/respnose/ApiResponse.java +++ b/src/main/java/com/kakao/golajuma/common/support/respnose/ApiResponse.java @@ -3,6 +3,7 @@ import lombok.Getter; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.util.MultiValueMap; @Getter public class ApiResponse extends ResponseEntity { @@ -14,4 +15,8 @@ public ApiResponse(final HttpStatus status) { public ApiResponse(final B body, final HttpStatus status) { super(body, status); } + + public ApiResponse(final B body, MultiValueMap headers, HttpStatus status) { + super(body, headers, status); + } } From fd2cfb59e8b91ee9294b11ff779274a3d9ef859f Mon Sep 17 00:00:00 2001 From: kssumin <201566@jnu.ac.kr> Date: Sun, 1 Oct 2023 23:01:34 +0900 Subject: [PATCH 07/18] =?UTF-8?q?feat=20:(#19)=20header=20=EA=B0=80?= =?UTF-8?q?=EC=A7=80=EB=8A=94=20response=20generator=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../support/respnose/ApiResponseGenerator.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/main/java/com/kakao/golajuma/common/support/respnose/ApiResponseGenerator.java b/src/main/java/com/kakao/golajuma/common/support/respnose/ApiResponseGenerator.java index ee5d454..000224e 100644 --- a/src/main/java/com/kakao/golajuma/common/support/respnose/ApiResponseGenerator.java +++ b/src/main/java/com/kakao/golajuma/common/support/respnose/ApiResponseGenerator.java @@ -3,6 +3,8 @@ import lombok.experimental.UtilityClass; import org.springframework.data.domain.Page; import org.springframework.http.HttpStatus; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; @UtilityClass public class ApiResponseGenerator { @@ -18,6 +20,14 @@ public static ApiResponse> success( new ApiResponseBody.SuccessBody<>(data, code.getMessage(), code.getCode()), status); } + public static ApiResponse> success( + final D data, final HttpStatus status, MessageCode code, String cookieValue) { + return new ApiResponse<>( + new ApiResponseBody.SuccessBody<>(data, code.getMessage(), code.getCode()), + setCookie(cookieValue), + status); + } + public static ApiResponse>> success( final Page data, final HttpStatus status, final MessageCode code) { return new ApiResponse<>( @@ -41,4 +51,11 @@ public static ApiResponse fail( final String code, final String message, final HttpStatus status) { return new ApiResponse<>(new ApiResponseBody.FailureBody(code, message), status); } + + private MultiValueMap setCookie(String cookieValue) { + MultiValueMap map = new LinkedMultiValueMap<>(); + map.add("Set-Cookie", cookieValue); + + return map; + } } From bb02b96af5435445808e24f5b42a799a8791a431 Mon Sep 17 00:00:00 2001 From: kssumin <201566@jnu.ac.kr> Date: Sun, 1 Oct 2023 23:02:15 +0900 Subject: [PATCH 08/18] =?UTF-8?q?feat=20:(#19)=20redis=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kakao/golajuma/config/RedisConfig.java | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 src/main/java/com/kakao/golajuma/config/RedisConfig.java diff --git a/src/main/java/com/kakao/golajuma/config/RedisConfig.java b/src/main/java/com/kakao/golajuma/config/RedisConfig.java new file mode 100644 index 0000000..6e144d1 --- /dev/null +++ b/src/main/java/com/kakao/golajuma/config/RedisConfig.java @@ -0,0 +1,45 @@ +package com.kakao.golajuma.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +@Configuration +public class RedisConfig { + + private final String host; + private final int port; + + public RedisConfig( + @Value("${spring.redis.host}") final String host, + @Value("${spring.redis.port}") final int port) { + this.host = host; + this.port = port; + } + + @Bean + public RedisConnectionFactory redisConnectionFactory() { + return new LettuceConnectionFactory(host, port); + } + + @Bean + public RedisTemplate redisTemplate() { + RedisTemplate redisTemplate = new RedisTemplate<>(); + redisTemplate.setConnectionFactory(redisConnectionFactory()); + + redisTemplate.setKeySerializer(new GenericJackson2JsonRedisSerializer()); + redisTemplate.setValueSerializer(new StringRedisSerializer()); + + redisTemplate.setHashKeySerializer(new GenericJackson2JsonRedisSerializer()); + redisTemplate.setHashValueSerializer(new StringRedisSerializer()); + + redisTemplate.setDefaultSerializer(new StringRedisSerializer()); + + return redisTemplate; + } +} From ebc0c78253b912814d580981eb4bd80fe9740e1e Mon Sep 17 00:00:00 2001 From: kssumin <201566@jnu.ac.kr> Date: Sun, 1 Oct 2023 23:02:30 +0900 Subject: [PATCH 09/18] =?UTF-8?q?feat=20:(#19)=20security=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kakao/golajuma/config/SecurityConfig.java | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 src/main/java/com/kakao/golajuma/config/SecurityConfig.java diff --git a/src/main/java/com/kakao/golajuma/config/SecurityConfig.java b/src/main/java/com/kakao/golajuma/config/SecurityConfig.java new file mode 100644 index 0000000..8a24b94 --- /dev/null +++ b/src/main/java/com/kakao/golajuma/config/SecurityConfig.java @@ -0,0 +1,43 @@ +package com.kakao.golajuma.config; + +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.crypto.factory.PasswordEncoderFactories; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.SecurityFilterChain; + +@Configuration +@RequiredArgsConstructor +@EnableWebSecurity +public class SecurityConfig { + private final AuthenticationConfiguration authenticationConfiguration; + + @Bean + public PasswordEncoder passwordEncoder() { + return PasswordEncoderFactories.createDelegatingPasswordEncoder(); + } + + @Bean + public AuthenticationManager authenticationManager() throws Exception { + return authenticationConfiguration.getAuthenticationManager(); + } + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + http.csrf().disable(); + http.formLogin().disable(); + http.httpBasic().disable(); + http.cors(); + http.authorizeRequests().antMatchers(HttpMethod.POST, "/users/auth/**").permitAll(); + + http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); + return http.build(); + } +} From cc48e52bd1632d210979542d70e9bae91c904399 Mon Sep 17 00:00:00 2001 From: kssumin <201566@jnu.ac.kr> Date: Sun, 1 Oct 2023 23:02:55 +0900 Subject: [PATCH 10/18] =?UTF-8?q?feat=20:(#19)=20not=20found=20exception?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/domain/exception/NotFoundException.java | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 src/main/java/com/kakao/golajuma/auth/domain/exception/NotFoundException.java diff --git a/src/main/java/com/kakao/golajuma/auth/domain/exception/NotFoundException.java b/src/main/java/com/kakao/golajuma/auth/domain/exception/NotFoundException.java new file mode 100644 index 0000000..a6c47ad --- /dev/null +++ b/src/main/java/com/kakao/golajuma/auth/domain/exception/NotFoundException.java @@ -0,0 +1,11 @@ +package com.kakao.golajuma.auth.domain.exception; + +import com.kakao.golajuma.common.exception.BusinessException; +import org.springframework.http.HttpStatus; + +public class NotFoundException extends BusinessException { + + public NotFoundException(String message) { + super(message, HttpStatus.NOT_FOUND); + } +} From b4212255c5fe8ad782be9e43364edfb71ad01c91 Mon Sep 17 00:00:00 2001 From: kssumin <201566@jnu.ac.kr> Date: Sun, 1 Oct 2023 23:03:13 +0900 Subject: [PATCH 11/18] =?UTF-8?q?feat=20:(#19)=20=EC=9C=A0=ED=9A=A8?= =?UTF-8?q?=ED=95=98=EC=A7=80=20=EC=95=8A=EB=8A=94=20=ED=86=A0=ED=81=B0?= =?UTF-8?q?=EC=97=90=20=EC=82=AC=EC=9A=A9=ED=95=A0=20exception=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../golajuma/auth/domain/exception/NotValidToken.java | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 src/main/java/com/kakao/golajuma/auth/domain/exception/NotValidToken.java diff --git a/src/main/java/com/kakao/golajuma/auth/domain/exception/NotValidToken.java b/src/main/java/com/kakao/golajuma/auth/domain/exception/NotValidToken.java new file mode 100644 index 0000000..a7c5103 --- /dev/null +++ b/src/main/java/com/kakao/golajuma/auth/domain/exception/NotValidToken.java @@ -0,0 +1,11 @@ +package com.kakao.golajuma.auth.domain.exception; + +import com.kakao.golajuma.common.exception.BusinessException; +import org.springframework.http.HttpStatus; + +public class NotValidToken extends BusinessException { + + public NotValidToken(String message) { + super(message, HttpStatus.BAD_REQUEST); + } +} From 73e0a81f47d546ca616d3079e5c933be4d5b0579 Mon Sep 17 00:00:00 2001 From: kssumin <201566@jnu.ac.kr> Date: Sun, 1 Oct 2023 23:04:18 +0900 Subject: [PATCH 12/18] =?UTF-8?q?feat=20:=20(#19)=20token=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/domain/token/TokenProvider.java | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 src/main/java/com/kakao/golajuma/auth/domain/token/TokenProvider.java diff --git a/src/main/java/com/kakao/golajuma/auth/domain/token/TokenProvider.java b/src/main/java/com/kakao/golajuma/auth/domain/token/TokenProvider.java new file mode 100644 index 0000000..905de54 --- /dev/null +++ b/src/main/java/com/kakao/golajuma/auth/domain/token/TokenProvider.java @@ -0,0 +1,54 @@ +package com.kakao.golajuma.auth.domain.token; + +import io.jsonwebtoken.Header; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.security.Keys; +import java.nio.charset.StandardCharsets; +import java.util.Date; +import javax.crypto.SecretKey; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +@Component +public class TokenProvider { + + private static final String USER_ID_CLAIM_KEY = "memberId"; + private static final String USER_ROLE_CLAIM_KEY = "memberRole"; + private final SecretKey accessSecretKey; + + private final long accessValidTime; + private final SecretKey refreshSecretKey; + + private final long refreshValidTime; + + public TokenProvider( + @Value("${security.jwt.token.access.secretKey}") String accessSecretKey, + @Value("${security.jwt.token.access.validTime}") long accessValidTime, + @Value("${security.jwt.token.refresh.secretKey}") String refreshSecretKey, + @Value("${security.jwt.token.refresh.validTime}") long refreshValidTime) { + this.accessSecretKey = Keys.hmacShaKeyFor(accessSecretKey.getBytes(StandardCharsets.UTF_8)); + this.accessValidTime = accessValidTime; + this.refreshSecretKey = Keys.hmacShaKeyFor(refreshSecretKey.getBytes(StandardCharsets.UTF_8)); + this.refreshValidTime = refreshValidTime; + } + + private String createToken(final Long userId, final SecretKey secretKey, final long validTime) { + final Date now = new Date(); + + return Jwts.builder() + .setHeaderParam(Header.TYPE, Header.JWT_TYPE) + .claim(USER_ID_CLAIM_KEY, userId) + .setIssuedAt(now) + .setExpiration(new Date(now.getTime() + validTime)) + .signWith(secretKey) + .compact(); + } + + public String createAccessToken(final Long userId) { + return createToken(userId, accessSecretKey, accessValidTime); + } + + public String createRefreshToken(final Long userId) { + return createToken(userId, refreshSecretKey, refreshValidTime); + } +} From 741679e3df97a2984f2ab29e12b4e7473a2c60b7 Mon Sep 17 00:00:00 2001 From: kssumin <201566@jnu.ac.kr> Date: Sun, 1 Oct 2023 23:05:51 +0900 Subject: [PATCH 13/18] =?UTF-8?q?feat=20:=20(#19)=20token=20decode=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/domain/token/TokenResolver.java | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 src/main/java/com/kakao/golajuma/auth/domain/token/TokenResolver.java diff --git a/src/main/java/com/kakao/golajuma/auth/domain/token/TokenResolver.java b/src/main/java/com/kakao/golajuma/auth/domain/token/TokenResolver.java new file mode 100644 index 0000000..f30c278 --- /dev/null +++ b/src/main/java/com/kakao/golajuma/auth/domain/token/TokenResolver.java @@ -0,0 +1,43 @@ +package com.kakao.golajuma.auth.domain.token; + +import com.kakao.golajuma.auth.domain.exception.NotValidToken; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.security.Keys; +import java.nio.charset.StandardCharsets; +import java.util.Date; +import javax.crypto.SecretKey; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +@Component +public class TokenResolver { + + private static final String USER_ID_CLAIM_KEY = "memberId"; + private static final String USER_ROLE_CLAIM_KEY = "memberRole"; + private final SecretKey accessSecretKey; + private final SecretKey refreshSecretKey; + + public TokenResolver( + @Value("${security.jwt.token.access.secretKey}") String accessSecretKey, + @Value("${security.jwt.token.refresh.secretKey}") String refreshSecretKey) { + this.accessSecretKey = Keys.hmacShaKeyFor(accessSecretKey.getBytes(StandardCharsets.UTF_8)); + this.refreshSecretKey = Keys.hmacShaKeyFor(refreshSecretKey.getBytes(StandardCharsets.UTF_8)); + } + + private Claims getClaims(final String token) { + try { + return Jwts.parserBuilder() + .setSigningKey(accessSecretKey) + .build() + .parseClaimsJws(token) + .getBody(); + } catch (Exception e) { + throw new NotValidToken("유효하지 않은 토큰입니다."); + } + } + + public Date getExpiredDate(final String token) { + return getClaims(token).getExpiration(); + } +} From 1ccef819480c426b6b4375a5de8b841947bc1023 Mon Sep 17 00:00:00 2001 From: kssumin <201566@jnu.ac.kr> Date: Sun, 1 Oct 2023 23:08:08 +0900 Subject: [PATCH 14/18] =?UTF-8?q?feat=20:=20(#19)=20redis=EB=A5=BC=20?= =?UTF-8?q?=EC=9D=B4=EC=9A=A9=ED=95=9C=20repository=EC=97=90=20refresh=20t?= =?UTF-8?q?oken=20=EC=A0=80=EC=9E=A5=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/domain/model/RefreshToken.java | 17 ++++++++ .../repository/RefreshTokenRepository.java | 42 +++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 src/main/java/com/kakao/golajuma/auth/domain/model/RefreshToken.java create mode 100644 src/main/java/com/kakao/golajuma/auth/infra/repository/RefreshTokenRepository.java diff --git a/src/main/java/com/kakao/golajuma/auth/domain/model/RefreshToken.java b/src/main/java/com/kakao/golajuma/auth/domain/model/RefreshToken.java new file mode 100644 index 0000000..0e829bb --- /dev/null +++ b/src/main/java/com/kakao/golajuma/auth/domain/model/RefreshToken.java @@ -0,0 +1,17 @@ +package com.kakao.golajuma.auth.domain.model; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.ToString; + +@Getter +@ToString +@AllArgsConstructor +@NoArgsConstructor +@Builder(toBuilder = true) +public class RefreshToken { + private String refreshToken; + private Long userId; +} diff --git a/src/main/java/com/kakao/golajuma/auth/infra/repository/RefreshTokenRepository.java b/src/main/java/com/kakao/golajuma/auth/infra/repository/RefreshTokenRepository.java new file mode 100644 index 0000000..f4bda1d --- /dev/null +++ b/src/main/java/com/kakao/golajuma/auth/infra/repository/RefreshTokenRepository.java @@ -0,0 +1,42 @@ +package com.kakao.golajuma.auth.infra.repository; + +import com.kakao.golajuma.auth.domain.model.RefreshToken; +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.TimeUnit; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.ValueOperations; +import org.springframework.stereotype.Repository; + +@Repository +@Slf4j +public class RefreshTokenRepository { + + private RedisTemplate redisTemplate; + + @Value("${redis.timeToLive}") + private long ttl; + + public RefreshTokenRepository(final RedisTemplate redisTemplate) { + this.redisTemplate = redisTemplate; + } + + public void save(final RefreshToken refreshToken) { + ValueOperations valueOperations = redisTemplate.opsForValue(); + valueOperations.set(refreshToken.getUserId(), refreshToken.getRefreshToken()); + redisTemplate.expire(refreshToken.getUserId(), ttl, TimeUnit.SECONDS); + } + + public Optional findById(final Long userId) { + ValueOperations valueOperations = redisTemplate.opsForValue(); + String refreshToken = valueOperations.get(userId); + + if (Objects.isNull(refreshToken)) { + return Optional.empty(); + } + + return Optional.of(new RefreshToken(refreshToken, userId)); + } +} From c6d0edaf28ff37849dab4a60fae0a45fa10f846e Mon Sep 17 00:00:00 2001 From: kssumin <201566@jnu.ac.kr> Date: Sun, 1 Oct 2023 23:08:28 +0900 Subject: [PATCH 15/18] =?UTF-8?q?feat=20:=20(#19)=20token=20=EC=A0=80?= =?UTF-8?q?=EC=9E=A5=20service=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/domain/service/TokenService.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 src/main/java/com/kakao/golajuma/auth/domain/service/TokenService.java diff --git a/src/main/java/com/kakao/golajuma/auth/domain/service/TokenService.java b/src/main/java/com/kakao/golajuma/auth/domain/service/TokenService.java new file mode 100644 index 0000000..fc9b463 --- /dev/null +++ b/src/main/java/com/kakao/golajuma/auth/domain/service/TokenService.java @@ -0,0 +1,19 @@ +package com.kakao.golajuma.auth.domain.service; + +import com.kakao.golajuma.auth.domain.model.RefreshToken; +import com.kakao.golajuma.auth.infra.repository.RefreshTokenRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class TokenService { + private final RefreshTokenRepository refreshTokenRepository; + + @Transactional + public void execute(Long userId, String token) { + RefreshToken refreshToken = RefreshToken.builder().refreshToken(token).userId(userId).build(); + refreshTokenRepository.save(refreshToken); + } +} From 28323b6c49468bddf033ee9feb6eea62dbb79774 Mon Sep 17 00:00:00 2001 From: kssumin <201566@jnu.ac.kr> Date: Sun, 1 Oct 2023 23:10:26 +0900 Subject: [PATCH 16/18] =?UTF-8?q?feat=20:=20(#19)=20=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8=20=EC=8B=9C=20response=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../web/dto/converter/TokenConverter.java | 19 ++++++++++++++++++ .../auth/web/dto/response/TokenResponse.java | 20 +++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 src/main/java/com/kakao/golajuma/auth/web/dto/converter/TokenConverter.java create mode 100644 src/main/java/com/kakao/golajuma/auth/web/dto/response/TokenResponse.java diff --git a/src/main/java/com/kakao/golajuma/auth/web/dto/converter/TokenConverter.java b/src/main/java/com/kakao/golajuma/auth/web/dto/converter/TokenConverter.java new file mode 100644 index 0000000..e5a3fc0 --- /dev/null +++ b/src/main/java/com/kakao/golajuma/auth/web/dto/converter/TokenConverter.java @@ -0,0 +1,19 @@ +package com.kakao.golajuma.auth.web.dto.converter; + +import com.kakao.golajuma.auth.web.dto.response.TokenResponse; +import java.util.Date; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class TokenConverter { + + public TokenResponse from(String accessToken, Date expiredTime, String refreshToken) { + return TokenResponse.builder() + .accessToken(accessToken) + .expiredTime(expiredTime) + .refreshToken(refreshToken) + .build(); + } +} diff --git a/src/main/java/com/kakao/golajuma/auth/web/dto/response/TokenResponse.java b/src/main/java/com/kakao/golajuma/auth/web/dto/response/TokenResponse.java new file mode 100644 index 0000000..60d631b --- /dev/null +++ b/src/main/java/com/kakao/golajuma/auth/web/dto/response/TokenResponse.java @@ -0,0 +1,20 @@ +package com.kakao.golajuma.auth.web.dto.response; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.kakao.golajuma.common.marker.AbstractResponseDto; +import java.util.Date; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@AllArgsConstructor +@NoArgsConstructor +@Builder(toBuilder = true) +public class TokenResponse implements AbstractResponseDto { + + private String accessToken; + private Date expiredTime; + @JsonIgnore private String refreshToken; +} From f2b689d7e1e329d2c9feb2981e4f3ff71b6a262f Mon Sep 17 00:00:00 2001 From: kssumin <201566@jnu.ac.kr> Date: Sun, 1 Oct 2023 23:11:31 +0900 Subject: [PATCH 17/18] =?UTF-8?q?feat=20:=20(#19)=20=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8=20service=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/domain/service/LoginUserService.java | 55 +++++++++++++++++++ .../auth/infra/repository/UserRepository.java | 3 + 2 files changed, 58 insertions(+) create mode 100644 src/main/java/com/kakao/golajuma/auth/domain/service/LoginUserService.java diff --git a/src/main/java/com/kakao/golajuma/auth/domain/service/LoginUserService.java b/src/main/java/com/kakao/golajuma/auth/domain/service/LoginUserService.java new file mode 100644 index 0000000..30822c6 --- /dev/null +++ b/src/main/java/com/kakao/golajuma/auth/domain/service/LoginUserService.java @@ -0,0 +1,55 @@ +package com.kakao.golajuma.auth.domain.service; + +import com.kakao.golajuma.auth.domain.exception.NotFoundException; +import com.kakao.golajuma.auth.domain.token.TokenProvider; +import com.kakao.golajuma.auth.domain.token.TokenResolver; +import com.kakao.golajuma.auth.infra.entity.UserEntity; +import com.kakao.golajuma.auth.infra.repository.UserRepository; +import com.kakao.golajuma.auth.web.dto.converter.TokenConverter; +import com.kakao.golajuma.auth.web.dto.request.LoginUserRequest; +import com.kakao.golajuma.auth.web.dto.response.TokenResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class LoginUserService { + + private final TokenProvider tokenProvider; + private final UserRepository userRepository; + private final PasswordEncoder passwordEncoder; + private final TokenConverter tokenConverter; + private final TokenResolver tokenResolver; + private final TokenService tokenService; + + @Transactional + public TokenResponse execute(final LoginUserRequest request) { + UserEntity userEntity = + userRepository + .findByEmail(request.getEmail()) + .orElseThrow(() -> new NotFoundException("존재하지 않는 이메일입니다.")); + + validPassword(request.getPassword(), userEntity); + + String accessToken = tokenProvider.createAccessToken(userEntity.getId()); + String refreshToken = tokenProvider.createRefreshToken(userEntity.getId()); + + tokenService.execute(userEntity.getId(), refreshToken); + + return tokenConverter.from( + accessToken, tokenResolver.getExpiredDate(accessToken), refreshToken); + } + + private void validPassword(final String requestPassword, final UserEntity userEntity) { + if (!matchPassword(requestPassword, userEntity.getPassword())) { + throw new NotFoundException("존재하지 않는 비밀번호입니다"); + } + } + + private boolean matchPassword(final String requestPassword, final String password) { + return passwordEncoder.matches(requestPassword, password); + } +} diff --git a/src/main/java/com/kakao/golajuma/auth/infra/repository/UserRepository.java b/src/main/java/com/kakao/golajuma/auth/infra/repository/UserRepository.java index 41102ab..354fae5 100644 --- a/src/main/java/com/kakao/golajuma/auth/infra/repository/UserRepository.java +++ b/src/main/java/com/kakao/golajuma/auth/infra/repository/UserRepository.java @@ -1,10 +1,13 @@ package com.kakao.golajuma.auth.infra.repository; import com.kakao.golajuma.auth.infra.entity.UserEntity; +import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; public interface UserRepository extends JpaRepository { boolean existsByEmail(String email); boolean existsByNickname(String nickname); + + Optional findByEmail(String email); } From d28a122e8e86a4090743b781a8697eae14e7d66f Mon Sep 17 00:00:00 2001 From: kssumin <201566@jnu.ac.kr> Date: Sun, 1 Oct 2023 23:11:42 +0900 Subject: [PATCH 18/18] =?UTF-8?q?feat=20:=20(#19)=20=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8=20controller=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/web/controller/LoginController.java | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 src/main/java/com/kakao/golajuma/auth/web/controller/LoginController.java diff --git a/src/main/java/com/kakao/golajuma/auth/web/controller/LoginController.java b/src/main/java/com/kakao/golajuma/auth/web/controller/LoginController.java new file mode 100644 index 0000000..3e4c500 --- /dev/null +++ b/src/main/java/com/kakao/golajuma/auth/web/controller/LoginController.java @@ -0,0 +1,45 @@ +package com.kakao.golajuma.auth.web.controller; + +import com.kakao.golajuma.auth.domain.service.LoginUserService; +import com.kakao.golajuma.auth.web.dto.request.LoginUserRequest; +import com.kakao.golajuma.auth.web.dto.response.TokenResponse; +import com.kakao.golajuma.common.support.respnose.ApiResponse; +import com.kakao.golajuma.common.support.respnose.ApiResponseBody.SuccessBody; +import com.kakao.golajuma.common.support.respnose.ApiResponseGenerator; +import com.kakao.golajuma.common.support.respnose.MessageCode; +import javax.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseCookie; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/auth") +public class LoginController { + private static final String REFRESH_TOKEN = "refreshToken"; + private static final int REFRESH_TOKEN_EXPIRATION = 7 * 24 * 60 * 60; + private final LoginUserService loginUserUseCase; + + @PostMapping("/login") + public ApiResponse> signIn( + @RequestBody @Valid LoginUserRequest request) { + final TokenResponse tokenResponse = loginUserUseCase.execute(request); + final ResponseCookie cookie = putTokenInCookie(tokenResponse); + return ApiResponseGenerator.success( + tokenResponse, HttpStatus.OK, MessageCode.CREATE, cookie.toString()); + } + + private ResponseCookie putTokenInCookie(final TokenResponse tokenResponse) { + return ResponseCookie.from(REFRESH_TOKEN, tokenResponse.getRefreshToken()) + .maxAge(REFRESH_TOKEN_EXPIRATION) + .path("/") + .sameSite("None") + .secure(true) + .httpOnly(true) + .build(); + } +}