From 1c3845caa378089eb2d46dcb55db386aae8e476e Mon Sep 17 00:00:00 2001 From: ta2ye0n Date: Tue, 8 Oct 2024 10:24:54 +0900 Subject: [PATCH 01/29] update :: profile nullable true --- src/main/java/com/gcms/v3/domain/user/domain/entity/User.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/gcms/v3/domain/user/domain/entity/User.java b/src/main/java/com/gcms/v3/domain/user/domain/entity/User.java index 8c008c3..60f7c68 100644 --- a/src/main/java/com/gcms/v3/domain/user/domain/entity/User.java +++ b/src/main/java/com/gcms/v3/domain/user/domain/entity/User.java @@ -29,6 +29,6 @@ public class User { @Column(columnDefinition = "VARCHAR(20)", nullable = false) private String email; - @Column(columnDefinition = "TEXT", nullable = false) + @Column(columnDefinition = "TEXT") private String profileImg; } From e6a3f194a7b3e62e32791f88f855e587297b3a74 Mon Sep 17 00:00:00 2001 From: ta2ye0n Date: Tue, 8 Oct 2024 10:25:13 +0900 Subject: [PATCH 02/29] =?UTF-8?q?add=20::=20Oauth2,=20jwt=20=EC=9D=98?= =?UTF-8?q?=EC=A1=B4=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/build.gradle b/build.gradle index b1d86a6..2fb58af 100644 --- a/build.gradle +++ b/build.gradle @@ -35,6 +35,12 @@ dependencies { testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'org.springframework.security:spring-security-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + + implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' + + implementation 'io.jsonwebtoken:jjwt-api:0.11.5' + implementation 'io.jsonwebtoken:jjwt-impl:0.11.5' + implementation 'io.jsonwebtoken:jjwt-jackson:0.11.5' } tasks.named('test') { From 81171ab05f5b731aac25056be86e2c4be0e5f5a4 Mon Sep 17 00:00:00 2001 From: ta2ye0n Date: Tue, 8 Oct 2024 10:25:20 +0900 Subject: [PATCH 03/29] =?UTF-8?q?add=20::=20Oauth2,=20jwt=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application.yml | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index b1c6e01..f4e822e 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -12,8 +12,26 @@ spring: hibernate: format_sql: true show-sql: true + security: + oauth2: + client: + registration: + google: + client-id: ${CLIENT_ID} + client-secret: ${CLIENT_SECRET} + redirect-uri: ${REDIRECT_URI} + scope: + - email + - profile + provider: + google: + token-uri: ${TOKEN-URI} + user-info-uri: ${USER_INFO_URI} data: redis: - host: ${REDIS_HOST} - port: ${REDIS_PORT} \ No newline at end of file + host: 127.0.0.1 + port: 6379 + +jwt: + secret: ${JWT_SECRET} \ No newline at end of file From 91f94ff66cbfef13b58b03398b29652aa95b5b29 Mon Sep 17 00:00:00 2001 From: ta2ye0n Date: Tue, 8 Oct 2024 10:26:27 +0900 Subject: [PATCH 04/29] add :: exception --- .../gcms/v3/global/error/BasicException.java | 10 ++++++++ .../com/gcms/v3/global/error/ErrorCode.java | 21 ++++++++++++++++ .../gcms/v3/global/error/ErrorResponse.java | 25 +++++++++++++++++++ .../global/error/GlobalExceptionHandler.java | 14 +++++++++++ 4 files changed, 70 insertions(+) create mode 100644 src/main/java/com/gcms/v3/global/error/BasicException.java create mode 100644 src/main/java/com/gcms/v3/global/error/ErrorCode.java create mode 100644 src/main/java/com/gcms/v3/global/error/ErrorResponse.java create mode 100644 src/main/java/com/gcms/v3/global/error/GlobalExceptionHandler.java diff --git a/src/main/java/com/gcms/v3/global/error/BasicException.java b/src/main/java/com/gcms/v3/global/error/BasicException.java new file mode 100644 index 0000000..889f468 --- /dev/null +++ b/src/main/java/com/gcms/v3/global/error/BasicException.java @@ -0,0 +1,10 @@ +package com.gcms.v3.global.error; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +@Getter +public class BasicException extends RuntimeException{ + ErrorCode errorCode; +} diff --git a/src/main/java/com/gcms/v3/global/error/ErrorCode.java b/src/main/java/com/gcms/v3/global/error/ErrorCode.java new file mode 100644 index 0000000..fb027af --- /dev/null +++ b/src/main/java/com/gcms/v3/global/error/ErrorCode.java @@ -0,0 +1,21 @@ +package com.gcms.v3.global.error; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + + +@AllArgsConstructor +@Getter +public enum ErrorCode { + + INVALID_AUTH_TOKEN(HttpStatus.UNAUTHORIZED, "권한 정보가 없는 토큰입니다."), + EXPIRED_TOKEN(HttpStatus.UNAUTHORIZED, "만료된 토큰입니다."), + INVALID_TOKEN_TYPE(HttpStatus.UNAUTHORIZED, "유효하지 않은 토큰 타입입니다."), + USER_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 유저를 찾을 수 없습니다."), + + INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "서버 내부 에러"); + + private final HttpStatus httpStatus; + private final String message; +} diff --git a/src/main/java/com/gcms/v3/global/error/ErrorResponse.java b/src/main/java/com/gcms/v3/global/error/ErrorResponse.java new file mode 100644 index 0000000..6302d9f --- /dev/null +++ b/src/main/java/com/gcms/v3/global/error/ErrorResponse.java @@ -0,0 +1,25 @@ +package com.gcms.v3.global.error; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +@Getter +@Builder +@AllArgsConstructor +public class ErrorResponse { + private HttpStatus status; + private String message; + + public static ResponseEntity toResponseEntity(ErrorCode e){ + return ResponseEntity + .status(e.getHttpStatus()) + .body(ErrorResponse.builder() + .status(e.getHttpStatus()) + .message(e.getMessage()) + .build() + ); + } +} diff --git a/src/main/java/com/gcms/v3/global/error/GlobalExceptionHandler.java b/src/main/java/com/gcms/v3/global/error/GlobalExceptionHandler.java new file mode 100644 index 0000000..dd457d1 --- /dev/null +++ b/src/main/java/com/gcms/v3/global/error/GlobalExceptionHandler.java @@ -0,0 +1,14 @@ +package com.gcms.v3.global.error; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; + +@ControllerAdvice +public class GlobalExceptionHandler { + + @ExceptionHandler(BasicException.class) + protected ResponseEntity handleCustomException(BasicException e) { + return ErrorResponse.toResponseEntity(e.getErrorCode()); + } +} From 87fc2b28ff789595470db3eed4aa1ff64e36a6ba Mon Sep 17 00:00:00 2001 From: ta2ye0n Date: Tue, 8 Oct 2024 10:26:59 +0900 Subject: [PATCH 05/29] add :: token exceptions --- .../security/exception/ExpiredTokenException.java | 10 ++++++++++ .../security/exception/InvalidAuthTokenException.java | 10 ++++++++++ .../security/exception/InvalidTokenTypeException.java | 10 ++++++++++ 3 files changed, 30 insertions(+) create mode 100644 src/main/java/com/gcms/v3/global/security/exception/ExpiredTokenException.java create mode 100644 src/main/java/com/gcms/v3/global/security/exception/InvalidAuthTokenException.java create mode 100644 src/main/java/com/gcms/v3/global/security/exception/InvalidTokenTypeException.java diff --git a/src/main/java/com/gcms/v3/global/security/exception/ExpiredTokenException.java b/src/main/java/com/gcms/v3/global/security/exception/ExpiredTokenException.java new file mode 100644 index 0000000..9e002a1 --- /dev/null +++ b/src/main/java/com/gcms/v3/global/security/exception/ExpiredTokenException.java @@ -0,0 +1,10 @@ +package com.gcms.v3.global.security.exception; + +import com.gcms.v3.global.error.BasicException; +import com.gcms.v3.global.error.ErrorCode; + +public class ExpiredTokenException extends BasicException { + public ExpiredTokenException() { + super(ErrorCode.EXPIRED_TOKEN); + } +} diff --git a/src/main/java/com/gcms/v3/global/security/exception/InvalidAuthTokenException.java b/src/main/java/com/gcms/v3/global/security/exception/InvalidAuthTokenException.java new file mode 100644 index 0000000..56f1cc3 --- /dev/null +++ b/src/main/java/com/gcms/v3/global/security/exception/InvalidAuthTokenException.java @@ -0,0 +1,10 @@ +package com.gcms.v3.global.security.exception; + +import com.gcms.v3.global.error.BasicException; +import com.gcms.v3.global.error.ErrorCode; + +public class InvalidAuthTokenException extends BasicException { + public InvalidAuthTokenException() { + super(ErrorCode.INVALID_AUTH_TOKEN); + } +} diff --git a/src/main/java/com/gcms/v3/global/security/exception/InvalidTokenTypeException.java b/src/main/java/com/gcms/v3/global/security/exception/InvalidTokenTypeException.java new file mode 100644 index 0000000..b2e5a96 --- /dev/null +++ b/src/main/java/com/gcms/v3/global/security/exception/InvalidTokenTypeException.java @@ -0,0 +1,10 @@ +package com.gcms.v3.global.security.exception; + +import com.gcms.v3.global.error.BasicException; +import com.gcms.v3.global.error.ErrorCode; + +public class InvalidTokenTypeException extends BasicException { + public InvalidTokenTypeException() { + super(ErrorCode.INVALID_TOKEN_TYPE); + } +} From 91c8125fcaf95dcdbc7b582aa1fb90e6cbe5e8ea Mon Sep 17 00:00:00 2001 From: ta2ye0n Date: Tue, 8 Oct 2024 10:27:05 +0900 Subject: [PATCH 06/29] add :: UserNotFoundException --- .../domain/auth/exception/UserNotFoundException.java | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 src/main/java/com/gcms/v3/domain/auth/exception/UserNotFoundException.java diff --git a/src/main/java/com/gcms/v3/domain/auth/exception/UserNotFoundException.java b/src/main/java/com/gcms/v3/domain/auth/exception/UserNotFoundException.java new file mode 100644 index 0000000..bc6d3a3 --- /dev/null +++ b/src/main/java/com/gcms/v3/domain/auth/exception/UserNotFoundException.java @@ -0,0 +1,10 @@ +package com.gcms.v3.domain.auth.exception; + +import com.gcms.v3.global.error.BasicException; +import com.gcms.v3.global.error.ErrorCode; + +public class UserNotFoundException extends BasicException { + public UserNotFoundException() { + super(ErrorCode.USER_NOT_FOUND); + } +} From 90e8aff69175b31574ccb871fa051394c84a8139 Mon Sep 17 00:00:00 2001 From: ta2ye0n Date: Tue, 8 Oct 2024 10:29:20 +0900 Subject: [PATCH 07/29] add :: Oauth2 Service --- .../global/oauth2/GoogleOAuth2UserInfo.java | 31 ++++++++++ .../v3/global/oauth2/GoogleProperties.java | 26 +++++++++ .../gcms/v3/global/oauth2/OAuth2Service.java | 56 +++++++++++++++++++ .../v3/global/oauth2/config/ClientConfig.java | 13 +++++ 4 files changed, 126 insertions(+) create mode 100644 src/main/java/com/gcms/v3/global/oauth2/GoogleOAuth2UserInfo.java create mode 100644 src/main/java/com/gcms/v3/global/oauth2/GoogleProperties.java create mode 100644 src/main/java/com/gcms/v3/global/oauth2/OAuth2Service.java create mode 100644 src/main/java/com/gcms/v3/global/oauth2/config/ClientConfig.java diff --git a/src/main/java/com/gcms/v3/global/oauth2/GoogleOAuth2UserInfo.java b/src/main/java/com/gcms/v3/global/oauth2/GoogleOAuth2UserInfo.java new file mode 100644 index 0000000..e78dc8a --- /dev/null +++ b/src/main/java/com/gcms/v3/global/oauth2/GoogleOAuth2UserInfo.java @@ -0,0 +1,31 @@ +package com.gcms.v3.global.oauth2; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.Map; + +@Getter +@JsonIgnoreProperties(ignoreUnknown = true) +@NoArgsConstructor +public class GoogleOAuth2UserInfo { + + protected Map attributes; + + public GoogleOAuth2UserInfo(Map attributes) { + this.attributes = attributes; + } + + public String getId() { + return (String) attributes.get("sub"); + } + + public String getName() { + return (String) attributes.get("name"); + } + + public String getEmail() { + return (String) attributes.get("email"); + } +} diff --git a/src/main/java/com/gcms/v3/global/oauth2/GoogleProperties.java b/src/main/java/com/gcms/v3/global/oauth2/GoogleProperties.java new file mode 100644 index 0000000..aa9199d --- /dev/null +++ b/src/main/java/com/gcms/v3/global/oauth2/GoogleProperties.java @@ -0,0 +1,26 @@ +package com.gcms.v3.global.oauth2; + +import lombok.Getter; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +@Component +@Getter +public class GoogleProperties { + @Value("${spring.security.oauth2.client.provider.google.token-uri}") + private String tokenUri; + + @Value("${spring.security.oauth2.client.registration.google.client-id}") + private String clientId; + + @Value("${spring.security.oauth2.client.registration.google.client-secret}") + private String clientSecret; + + @Value("${spring.security.oauth2.client.provider.google.user-info-uri}") + private String userInfoUri; + + @Value("${spring.security.oauth2.client.registration.google.redirect-uri}") + private String redirectUri; + + public static final String GRANT_TYPE = "authorization_code"; +} diff --git a/src/main/java/com/gcms/v3/global/oauth2/OAuth2Service.java b/src/main/java/com/gcms/v3/global/oauth2/OAuth2Service.java new file mode 100644 index 0000000..64ca78f --- /dev/null +++ b/src/main/java/com/gcms/v3/global/oauth2/OAuth2Service.java @@ -0,0 +1,56 @@ +package com.gcms.v3.global.oauth2; + +import lombok.RequiredArgsConstructor; +import org.springframework.http.*; +import org.springframework.stereotype.Service; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestTemplate; + +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; +import java.util.Map; + +import static com.gcms.v3.global.oauth2.GoogleProperties.GRANT_TYPE; + +@Service +@RequiredArgsConstructor +public class OAuth2Service { + + private final GoogleProperties config; + private final RestTemplate restTemplate; + + public String requestAccessToken(String code) { + String decode = URLDecoder.decode(code, StandardCharsets.UTF_8); + + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED); + + MultiValueMap body = new LinkedMultiValueMap<>(); + body.add("client_id", config.getClientId()); + body.add("client_secret", config.getClientSecret()); + body.add("code", decode); + body.add("redirect_uri", config.getRedirectUri()); + body.add("grant_type", GRANT_TYPE); + + HttpEntity request = new HttpEntity<>(body, httpHeaders); + + ResponseEntity response = restTemplate.postForEntity(config.getTokenUri(), request, Map.class); + return (String) response.getBody().get("access_token"); + } + + + public GoogleOAuth2UserInfo requestUserInfo(String accessToken) { + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.setContentType(MediaType.APPLICATION_JSON); + httpHeaders.setBearerAuth(accessToken); + + MultiValueMap body = new LinkedMultiValueMap<>(); + + HttpEntity request = new HttpEntity<>(body, httpHeaders); + + ResponseEntity response = restTemplate.exchange(config.getUserInfoUri(), HttpMethod.GET, request, Map.class); + + return new GoogleOAuth2UserInfo(response.getBody()); + } +} diff --git a/src/main/java/com/gcms/v3/global/oauth2/config/ClientConfig.java b/src/main/java/com/gcms/v3/global/oauth2/config/ClientConfig.java new file mode 100644 index 0000000..abb8d31 --- /dev/null +++ b/src/main/java/com/gcms/v3/global/oauth2/config/ClientConfig.java @@ -0,0 +1,13 @@ +package com.gcms.v3.global.oauth2.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.client.RestTemplate; + +@Configuration +public class ClientConfig { + @Bean + public RestTemplate restTemplate() { + return new RestTemplate(); + } +} From cacda952c5a1ad39c037576d06a1acb24a0035f5 Mon Sep 17 00:00:00 2001 From: ta2ye0n Date: Tue, 8 Oct 2024 11:14:06 +0900 Subject: [PATCH 08/29] add :: ExceptionFilter --- .../security/filter/ExceptionFilter.java | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 src/main/java/com/gcms/v3/global/security/filter/ExceptionFilter.java diff --git a/src/main/java/com/gcms/v3/global/security/filter/ExceptionFilter.java b/src/main/java/com/gcms/v3/global/security/filter/ExceptionFilter.java new file mode 100644 index 0000000..9947d16 --- /dev/null +++ b/src/main/java/com/gcms/v3/global/security/filter/ExceptionFilter.java @@ -0,0 +1,45 @@ +package com.gcms.v3.global.security.filter; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.gcms.v3.global.error.BasicException; +import com.gcms.v3.global.error.ErrorCode; +import com.gcms.v3.global.error.ErrorResponse; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.http.MediaType; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; + +@RequiredArgsConstructor +public class ExceptionFilter extends OncePerRequestFilter { + + private final ObjectMapper objectMapper; + + @Override + protected void doFilterInternal(HttpServletRequest request, + HttpServletResponse response, + FilterChain filterChain) throws ServletException, IOException { + try { + filterChain.doFilter(request, response); + } catch (BasicException e) { + sendError(response, e.getErrorCode()); + } catch (Exception e) { + sendError(response, ErrorCode.INTERNAL_SERVER_ERROR); + } + + } + + private void sendError(HttpServletResponse response, ErrorCode errorCode) throws IOException { + ErrorResponse errorResponse = new ErrorResponse(errorCode.getHttpStatus(), errorCode.getMessage()); + String responseString = objectMapper.writeValueAsString(errorResponse); + response.setCharacterEncoding("UTF-8"); + response.setStatus(errorCode.getHttpStatus().value()); + response.setContentType(MediaType.APPLICATION_JSON_VALUE); + response.getWriter().write(responseString); + } +} From a2ab858ee299d9883037f9abf260a2fa74468348 Mon Sep 17 00:00:00 2001 From: ta2ye0n Date: Tue, 8 Oct 2024 11:14:22 +0900 Subject: [PATCH 09/29] add :: JwtFilter --- .../v3/global/security/filter/JwtFilter.java | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 src/main/java/com/gcms/v3/global/security/filter/JwtFilter.java diff --git a/src/main/java/com/gcms/v3/global/security/filter/JwtFilter.java b/src/main/java/com/gcms/v3/global/security/filter/JwtFilter.java new file mode 100644 index 0000000..554f672 --- /dev/null +++ b/src/main/java/com/gcms/v3/global/security/filter/JwtFilter.java @@ -0,0 +1,31 @@ +package com.gcms.v3.global.security.filter; + +import com.gcms.v3.global.security.jwt.JwtTokenProvider; +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.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; + +@RequiredArgsConstructor +public class JwtFilter extends OncePerRequestFilter { + + private final JwtTokenProvider jwtTokenProvider; + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + String token = jwtTokenProvider.resolveToken(request); + + if (token != null && jwtTokenProvider.validateToken(token)) { + Authentication authentication = jwtTokenProvider.getAuthentication(token); + SecurityContextHolder.getContext().setAuthentication(authentication); + } + + filterChain.doFilter(request, response); + } +} From 1c37c82a9e176cfb81802cdab3abb0cfcc3f8baa Mon Sep 17 00:00:00 2001 From: ta2ye0n Date: Tue, 8 Oct 2024 11:14:28 +0900 Subject: [PATCH 10/29] add :: JwtAccessDeniedHandler --- .../handler/JwtAccessDeniedHandler.java | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 src/main/java/com/gcms/v3/global/security/handler/JwtAccessDeniedHandler.java diff --git a/src/main/java/com/gcms/v3/global/security/handler/JwtAccessDeniedHandler.java b/src/main/java/com/gcms/v3/global/security/handler/JwtAccessDeniedHandler.java new file mode 100644 index 0000000..24356f5 --- /dev/null +++ b/src/main/java/com/gcms/v3/global/security/handler/JwtAccessDeniedHandler.java @@ -0,0 +1,21 @@ +package com.gcms.v3.global.security.handler; + +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.web.access.AccessDeniedHandler; +import org.springframework.stereotype.Component; + +import java.io.IOException; + +@Component +public class JwtAccessDeniedHandler implements AccessDeniedHandler { + + @Override + public void handle(HttpServletRequest request, + HttpServletResponse response, + AccessDeniedException accessDeniedException) throws IOException, ServletException { + response.sendError(HttpServletResponse.SC_FORBIDDEN); + } +} From 92ef096815e951856c53c1de2f248e59da84270a Mon Sep 17 00:00:00 2001 From: ta2ye0n Date: Tue, 8 Oct 2024 11:14:34 +0900 Subject: [PATCH 11/29] add :: JwtAuthenticationEntryPoint --- .../handler/JwtAuthenticationEntryPoint.java | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 src/main/java/com/gcms/v3/global/security/handler/JwtAuthenticationEntryPoint.java diff --git a/src/main/java/com/gcms/v3/global/security/handler/JwtAuthenticationEntryPoint.java b/src/main/java/com/gcms/v3/global/security/handler/JwtAuthenticationEntryPoint.java new file mode 100644 index 0000000..dbacbbd --- /dev/null +++ b/src/main/java/com/gcms/v3/global/security/handler/JwtAuthenticationEntryPoint.java @@ -0,0 +1,21 @@ +package com.gcms.v3.global.security.handler; + +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.stereotype.Component; + +import java.io.IOException; + +@Component +public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint { + + @Override + public void commence(HttpServletRequest request, + HttpServletResponse response, + AuthenticationException authException) throws IOException, ServletException { + response.sendError(HttpServletResponse.SC_UNAUTHORIZED); + } +} From dd11ed4261fb542fca42d1f1f17e19a9e992495f Mon Sep 17 00:00:00 2001 From: ta2ye0n Date: Tue, 8 Oct 2024 11:14:44 +0900 Subject: [PATCH 12/29] add :: UserRepository --- .../domain/user/domain/repository/UserRepository.java | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 src/main/java/com/gcms/v3/domain/user/domain/repository/UserRepository.java diff --git a/src/main/java/com/gcms/v3/domain/user/domain/repository/UserRepository.java b/src/main/java/com/gcms/v3/domain/user/domain/repository/UserRepository.java new file mode 100644 index 0000000..3619a11 --- /dev/null +++ b/src/main/java/com/gcms/v3/domain/user/domain/repository/UserRepository.java @@ -0,0 +1,10 @@ +package com.gcms.v3.domain.user.domain.repository; + +import com.gcms.v3.domain.user.domain.entity.User; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface UserRepository extends JpaRepository { + Optional findByEmail(String email); +} From b534bf82de9c2a1d42e64c18c09e20aea07fbabb Mon Sep 17 00:00:00 2001 From: ta2ye0n Date: Tue, 8 Oct 2024 11:14:50 +0900 Subject: [PATCH 13/29] add :: UserRoleRepository --- .../user/domain/repository/UserRoleRepository.java | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 src/main/java/com/gcms/v3/domain/user/domain/repository/UserRoleRepository.java diff --git a/src/main/java/com/gcms/v3/domain/user/domain/repository/UserRoleRepository.java b/src/main/java/com/gcms/v3/domain/user/domain/repository/UserRoleRepository.java new file mode 100644 index 0000000..cf16d5e --- /dev/null +++ b/src/main/java/com/gcms/v3/domain/user/domain/repository/UserRoleRepository.java @@ -0,0 +1,11 @@ +package com.gcms.v3.domain.user.domain.repository; + +import com.gcms.v3.domain.user.domain.entity.User; +import com.gcms.v3.domain.user.domain.entity.UserRole; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface UserRoleRepository extends JpaRepository { + List findByUser(User user); +} From 82159c39a39b425cf661cfc02ad4b0f8732abfa5 Mon Sep 17 00:00:00 2001 From: ta2ye0n Date: Tue, 8 Oct 2024 11:15:03 +0900 Subject: [PATCH 14/29] add :: JwtTokenProvider --- .../global/security/jwt/JwtTokenProvider.java | 122 ++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 src/main/java/com/gcms/v3/global/security/jwt/JwtTokenProvider.java diff --git a/src/main/java/com/gcms/v3/global/security/jwt/JwtTokenProvider.java b/src/main/java/com/gcms/v3/global/security/jwt/JwtTokenProvider.java new file mode 100644 index 0000000..77bb23a --- /dev/null +++ b/src/main/java/com/gcms/v3/global/security/jwt/JwtTokenProvider.java @@ -0,0 +1,122 @@ +package com.gcms.v3.global.security.jwt; + +import com.gcms.v3.domain.auth.presentation.data.response.TokenInfoResponseDto; +import com.gcms.v3.global.security.exception.InvalidAuthTokenException; +import com.gcms.v3.global.security.auth.AuthDetailsService; +import io.jsonwebtoken.*; +import io.jsonwebtoken.io.Decoders; +import io.jsonwebtoken.security.Keys; +import jakarta.annotation.PostConstruct; +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +import java.security.Key; +import java.time.LocalDateTime; +import java.util.Date; + +@Component +@RequiredArgsConstructor +public class JwtTokenProvider { + + @Value("${jwt.secret}") + private String secretKey; + private static final String AUTHORITIES = "auth"; + private static final String GRANT_TYPE = "Bearer"; + private static final String TOKEN_PREFIX = "Bearer "; + private static final long ACCESS_TOKEN_TIME = 1000 * 60 * 30L; + private static final long REFRESH_TOKEN_TIME = 1000L * 60 * 60 * 24 * 7; + private static Key key; + private final AuthDetailsService authDetailsService; + + @PostConstruct + public void init() { + byte[] keyBytes = Decoders.BASE64.decode(secretKey); + key = Keys.hmacShaKeyFor(keyBytes); + } + + public TokenInfoResponseDto generateToken(String email) { + return TokenInfoResponseDto.builder() + .accessToken(generateAccessToken(email)) + .refreshToken(generateRefreshToken(email)) + .accessTokenExpiresIn(LocalDateTime.now().plusSeconds(ACCESS_TOKEN_TIME)) + .refreshTokenExpiresIn(LocalDateTime.now().plusSeconds(REFRESH_TOKEN_TIME)) + .build(); + } + + private String generateAccessToken(String email) { + long now = (new Date()).getTime(); + + Date accessTokenExpiresIn = new Date(now + ACCESS_TOKEN_TIME); + + return Jwts.builder() + .setSubject(email) + .setIssuedAt(new Date()) + .setHeaderParam("typ", GRANT_TYPE) + .claim(AUTHORITIES, "JWT") + .setExpiration(accessTokenExpiresIn) + .signWith(key, SignatureAlgorithm.HS256) + .compact(); + } + + private String generateRefreshToken(String email) { + long now = (new Date()).getTime(); + + Date refreshTokenExpiresIn = new Date(now + REFRESH_TOKEN_TIME); + + return Jwts.builder() + .setSubject(email) + .setHeaderParam("typ", "JWT") + .signWith(key, SignatureAlgorithm.HS256) + .claim(AUTHORITIES, "JWT") + .setIssuedAt(new Date()) + .setExpiration(refreshTokenExpiresIn) + .compact(); + } + + public Authentication getAuthentication(String token) { + Claims claims = parseClaims(token); + + if (claims.get(AUTHORITIES) == null) { + throw new InvalidAuthTokenException(); + } + + UserDetails userDetails = authDetailsService.loadUserByUsername(claims.getSubject()); + return new UsernamePasswordAuthenticationToken(userDetails, "", userDetails.getAuthorities()); + } + + private Claims parseClaims(String assessToken) { + try { + return Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(assessToken).getBody(); + } catch (ExpiredJwtException e) { + return e.getClaims(); + } + } + + public String resolveToken(HttpServletRequest request) { + String bearerToken = request.getHeader("Authorization"); + if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(TOKEN_PREFIX)) { + return bearerToken.substring(7); + } + return null; + } + + public boolean validateToken(String token) { + try { + Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token); + return true; + } catch (SecurityException | MalformedJwtException e) { + throw new InvalidAuthTokenException(); + } catch (ExpiredJwtException e) { + throw new InvalidAuthTokenException(); + } catch (UnsupportedJwtException e) { + throw new InvalidAuthTokenException(); + } + } + +} From 521d117e9d0d1e446b9b4bc4dcdec8d05a973397 Mon Sep 17 00:00:00 2001 From: ta2ye0n Date: Tue, 8 Oct 2024 11:15:19 +0900 Subject: [PATCH 15/29] add :: AuthDetails, AuthDetailsService --- .../v3/global/security/auth/AuthDetails.java | 60 +++++++++++++++++++ .../security/auth/AuthDetailsService.java | 25 ++++++++ 2 files changed, 85 insertions(+) create mode 100644 src/main/java/com/gcms/v3/global/security/auth/AuthDetails.java create mode 100644 src/main/java/com/gcms/v3/global/security/auth/AuthDetailsService.java diff --git a/src/main/java/com/gcms/v3/global/security/auth/AuthDetails.java b/src/main/java/com/gcms/v3/global/security/auth/AuthDetails.java new file mode 100644 index 0000000..8f1c098 --- /dev/null +++ b/src/main/java/com/gcms/v3/global/security/auth/AuthDetails.java @@ -0,0 +1,60 @@ +package com.gcms.v3.global.security.auth; + +import com.gcms.v3.domain.user.domain.entity.User; +import com.gcms.v3.domain.user.domain.entity.UserRole; +import com.gcms.v3.domain.user.domain.repository.UserRoleRepository; +import lombok.AllArgsConstructor; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + +@AllArgsConstructor +@RequiredArgsConstructor +public class AuthDetails implements UserDetails { + + private User user; + private final UserRoleRepository userRoleRepository; + + @Override + public Collection getAuthorities() { + List userRoles = userRoleRepository.findByUser(user); + + return userRoles.stream() + .map(role -> new SimpleGrantedAuthority(role.getAuthority().name())) + .collect(Collectors.toList()); + } + + @Override + public String getPassword() { + return ""; + } + + @Override + public String getUsername() { + return user.getEmail(); + } + + @Override + public boolean isAccountNonExpired() { + return UserDetails.super.isAccountNonExpired(); + } + + @Override + public boolean isAccountNonLocked() { + return UserDetails.super.isAccountNonLocked(); + } + + @Override + public boolean isCredentialsNonExpired() { + return UserDetails.super.isCredentialsNonExpired(); + } + + @Override + public boolean isEnabled() { + return UserDetails.super.isEnabled(); + } +} diff --git a/src/main/java/com/gcms/v3/global/security/auth/AuthDetailsService.java b/src/main/java/com/gcms/v3/global/security/auth/AuthDetailsService.java new file mode 100644 index 0000000..b31f58f --- /dev/null +++ b/src/main/java/com/gcms/v3/global/security/auth/AuthDetailsService.java @@ -0,0 +1,25 @@ +package com.gcms.v3.global.security.auth; + +import com.gcms.v3.domain.auth.exception.UserNotFoundException; +import com.gcms.v3.domain.user.domain.repository.UserRepository; +import com.gcms.v3.domain.user.domain.repository.UserRoleRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class AuthDetailsService implements UserDetailsService { + + private final UserRepository userRepository; + private final UserRoleRepository userRoleRepository; + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + return userRepository.findByEmail(username) + .map(user -> new AuthDetails(user, userRoleRepository)) + .orElseThrow(UserNotFoundException::new); + } +} From 6d1c0c52a329dde43ce9caa41f672ce435ff88f3 Mon Sep 17 00:00:00 2001 From: ta2ye0n Date: Tue, 8 Oct 2024 11:15:29 +0900 Subject: [PATCH 16/29] add :: JwtSecurityConfig --- .../security/config/JwtSecurityConfig.java | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 src/main/java/com/gcms/v3/global/security/config/JwtSecurityConfig.java diff --git a/src/main/java/com/gcms/v3/global/security/config/JwtSecurityConfig.java b/src/main/java/com/gcms/v3/global/security/config/JwtSecurityConfig.java new file mode 100644 index 0000000..671e6df --- /dev/null +++ b/src/main/java/com/gcms/v3/global/security/config/JwtSecurityConfig.java @@ -0,0 +1,22 @@ +package com.gcms.v3.global.security.config; + +import com.gcms.v3.global.security.filter.JwtFilter; +import com.gcms.v3.global.security.jwt.JwtTokenProvider; +import lombok.RequiredArgsConstructor; +import org.springframework.security.config.annotation.SecurityConfigurerAdapter; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.web.DefaultSecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; + +@RequiredArgsConstructor +public class JwtSecurityConfig extends SecurityConfigurerAdapter { + + private final JwtTokenProvider jwtTokenProvider; + + @Override + public void configure(HttpSecurity http) { + http.addFilterBefore(new JwtFilter(jwtTokenProvider), + UsernamePasswordAuthenticationFilter.class); + } + +} From 2a67fecc880a60940027922bf3d1ec048ddf36a6 Mon Sep 17 00:00:00 2001 From: ta2ye0n Date: Tue, 8 Oct 2024 11:15:54 +0900 Subject: [PATCH 17/29] add :: SignInService --- .../v3/domain/auth/service/SignInService.java | 8 +++ .../auth/service/impl/SignInServiceImpl.java | 58 +++++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 src/main/java/com/gcms/v3/domain/auth/service/SignInService.java create mode 100644 src/main/java/com/gcms/v3/domain/auth/service/impl/SignInServiceImpl.java diff --git a/src/main/java/com/gcms/v3/domain/auth/service/SignInService.java b/src/main/java/com/gcms/v3/domain/auth/service/SignInService.java new file mode 100644 index 0000000..c2d328a --- /dev/null +++ b/src/main/java/com/gcms/v3/domain/auth/service/SignInService.java @@ -0,0 +1,8 @@ +package com.gcms.v3.domain.auth.service; + +import com.gcms.v3.domain.auth.presentation.data.request.SignInRequestDto; +import com.gcms.v3.domain.auth.presentation.data.response.TokenInfoResponseDto; + +public interface SignInService { + TokenInfoResponseDto execute(SignInRequestDto signInRequestDto); +} diff --git a/src/main/java/com/gcms/v3/domain/auth/service/impl/SignInServiceImpl.java b/src/main/java/com/gcms/v3/domain/auth/service/impl/SignInServiceImpl.java new file mode 100644 index 0000000..41e76b0 --- /dev/null +++ b/src/main/java/com/gcms/v3/domain/auth/service/impl/SignInServiceImpl.java @@ -0,0 +1,58 @@ +package com.gcms.v3.domain.auth.service.impl; + +import com.gcms.v3.domain.auth.presentation.data.request.SignInRequestDto; +import com.gcms.v3.domain.auth.presentation.data.response.TokenInfoResponseDto; +import com.gcms.v3.domain.auth.service.SignInService; +import com.gcms.v3.domain.user.domain.entity.User; +import com.gcms.v3.domain.user.domain.entity.UserRole; +import com.gcms.v3.domain.user.domain.enums.Authority; +import com.gcms.v3.domain.user.domain.repository.UserRepository; +import com.gcms.v3.domain.user.domain.repository.UserRoleRepository; +import com.gcms.v3.global.oauth2.GoogleOAuth2UserInfo; +import com.gcms.v3.global.oauth2.OAuth2Service; +import com.gcms.v3.global.security.jwt.JwtTokenProvider; +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@Transactional(rollbackOn = Exception.class) +@RequiredArgsConstructor +public class SignInServiceImpl implements SignInService { + + private final OAuth2Service oAuth2Service; + private final UserRepository userRepository; + private final JwtTokenProvider jwtTokenProvider; + private final UserRoleRepository userRoleRepository; + + public TokenInfoResponseDto execute(SignInRequestDto signInRequestDto) { + String accessToken = oAuth2Service.requestAccessToken(signInRequestDto.getCode()); + GoogleOAuth2UserInfo googleOAuth2UserInfo = oAuth2Service.requestUserInfo(accessToken); + + User user = userRepository.findByEmail(googleOAuth2UserInfo.getEmail()) + .orElseGet(() -> toEntity(googleOAuth2UserInfo)); + + return jwtTokenProvider.generateToken(user.getEmail()); + } + + private User toEntity(GoogleOAuth2UserInfo googleOAuth2UserInfo) { + User user = User.builder() + .name(googleOAuth2UserInfo.getName()) + .email(googleOAuth2UserInfo.getEmail()) + .build(); + + userRepository.save(user); + saveAuthority(user); + return user; + } + + private void saveAuthority(User user) { + UserRole userRole = UserRole.builder() + .user(user) + .authority(Authority.ROLE_STUDENT) + .build(); + + userRoleRepository.save(userRole); + } + +} From f71bc22dcc09f85ec8c8f5c57813ee5d5d5ab960 Mon Sep 17 00:00:00 2001 From: ta2ye0n Date: Tue, 8 Oct 2024 11:16:03 +0900 Subject: [PATCH 18/29] add :: AuthController --- .../auth/presentation/AuthController.java | 22 +++++++++++++++++++ .../data/request/SignInRequestDto.java | 8 +++++++ .../data/response/TokenInfoResponseDto.java | 15 +++++++++++++ 3 files changed, 45 insertions(+) create mode 100644 src/main/java/com/gcms/v3/domain/auth/presentation/AuthController.java create mode 100644 src/main/java/com/gcms/v3/domain/auth/presentation/data/request/SignInRequestDto.java create mode 100644 src/main/java/com/gcms/v3/domain/auth/presentation/data/response/TokenInfoResponseDto.java diff --git a/src/main/java/com/gcms/v3/domain/auth/presentation/AuthController.java b/src/main/java/com/gcms/v3/domain/auth/presentation/AuthController.java new file mode 100644 index 0000000..9923671 --- /dev/null +++ b/src/main/java/com/gcms/v3/domain/auth/presentation/AuthController.java @@ -0,0 +1,22 @@ +package com.gcms.v3.domain.auth.presentation; + +import com.gcms.v3.domain.auth.presentation.data.request.SignInRequestDto; +import com.gcms.v3.domain.auth.presentation.data.response.TokenInfoResponseDto; +import com.gcms.v3.domain.auth.service.SignInService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@RequiredArgsConstructor +@RestController +@RequestMapping("/v3/auth") +public class AuthController { + + private final SignInService signInService; + + @PostMapping + public ResponseEntity test(@RequestBody SignInRequestDto signInRequestDto) { + TokenInfoResponseDto res = signInService.execute(signInRequestDto); + return ResponseEntity.ok(res); + } +} diff --git a/src/main/java/com/gcms/v3/domain/auth/presentation/data/request/SignInRequestDto.java b/src/main/java/com/gcms/v3/domain/auth/presentation/data/request/SignInRequestDto.java new file mode 100644 index 0000000..41dd59a --- /dev/null +++ b/src/main/java/com/gcms/v3/domain/auth/presentation/data/request/SignInRequestDto.java @@ -0,0 +1,8 @@ +package com.gcms.v3.domain.auth.presentation.data.request; + +import lombok.Getter; + +@Getter +public class SignInRequestDto { + private String code; +} diff --git a/src/main/java/com/gcms/v3/domain/auth/presentation/data/response/TokenInfoResponseDto.java b/src/main/java/com/gcms/v3/domain/auth/presentation/data/response/TokenInfoResponseDto.java new file mode 100644 index 0000000..004eb24 --- /dev/null +++ b/src/main/java/com/gcms/v3/domain/auth/presentation/data/response/TokenInfoResponseDto.java @@ -0,0 +1,15 @@ +package com.gcms.v3.domain.auth.presentation.data.response; + +import lombok.Builder; +import lombok.Getter; + +import java.time.LocalDateTime; + +@Getter +@Builder +public class TokenInfoResponseDto { + private String accessToken; + private String refreshToken; + private LocalDateTime accessTokenExpiresIn; + private LocalDateTime refreshTokenExpiresIn; +} From 2164d2ed3bee201ccddcf409da67116650988d6f Mon Sep 17 00:00:00 2001 From: ta2ye0n Date: Tue, 8 Oct 2024 11:16:09 +0900 Subject: [PATCH 19/29] add :: SecurityConfig --- .../security/config/SecurityConfig.java | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 src/main/java/com/gcms/v3/global/security/config/SecurityConfig.java diff --git a/src/main/java/com/gcms/v3/global/security/config/SecurityConfig.java b/src/main/java/com/gcms/v3/global/security/config/SecurityConfig.java new file mode 100644 index 0000000..f4a717d --- /dev/null +++ b/src/main/java/com/gcms/v3/global/security/config/SecurityConfig.java @@ -0,0 +1,53 @@ +package com.gcms.v3.global.security.config; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.gcms.v3.global.security.filter.ExceptionFilter; +import com.gcms.v3.global.security.filter.JwtFilter; +import com.gcms.v3.global.security.handler.JwtAccessDeniedHandler; +import com.gcms.v3.global.security.handler.JwtAuthenticationEntryPoint; +import com.gcms.v3.global.security.jwt.JwtTokenProvider; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; + +@RequiredArgsConstructor +@Configuration +@EnableWebSecurity +public class SecurityConfig { + + private final JwtTokenProvider jwtTokenProvider; + private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint; + private final JwtAccessDeniedHandler jwtAccessDeniedHandler; + private final ObjectMapper objectMapper; + + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + http + .csrf(AbstractHttpConfigurer::disable) + .cors(AbstractHttpConfigurer::disable) + .formLogin(AbstractHttpConfigurer::disable) + + .sessionManagement(c -> c.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + + .exceptionHandling(exception-> + exception.authenticationEntryPoint(jwtAuthenticationEntryPoint) + .accessDeniedHandler(jwtAccessDeniedHandler)) + + .authorizeHttpRequests((authorizeRequests) -> + authorizeRequests + .requestMatchers(HttpMethod.POST, "/v3/auth").permitAll() + ) + + .addFilterBefore(new ExceptionFilter(objectMapper), UsernamePasswordAuthenticationFilter.class) + .addFilterBefore(new JwtFilter(jwtTokenProvider), UsernamePasswordAuthenticationFilter.class); + + return http.build(); + } +} From 66605e09d13476daa5f4978dc00d937db55670ff Mon Sep 17 00:00:00 2001 From: ta2ye0n Date: Thu, 10 Oct 2024 00:17:09 +0900 Subject: [PATCH 20/29] =?UTF-8?q?update=20::=20controller=EB=AA=85=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/gcms/v3/domain/auth/presentation/AuthController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/gcms/v3/domain/auth/presentation/AuthController.java b/src/main/java/com/gcms/v3/domain/auth/presentation/AuthController.java index 9923671..3196e4e 100644 --- a/src/main/java/com/gcms/v3/domain/auth/presentation/AuthController.java +++ b/src/main/java/com/gcms/v3/domain/auth/presentation/AuthController.java @@ -15,7 +15,7 @@ public class AuthController { private final SignInService signInService; @PostMapping - public ResponseEntity test(@RequestBody SignInRequestDto signInRequestDto) { + public ResponseEntity signIn (@RequestBody SignInRequestDto signInRequestDto) { TokenInfoResponseDto res = signInService.execute(signInRequestDto); return ResponseEntity.ok(res); } From 396b059866bfbb0cfeecc6211401136b03a2f33c Mon Sep 17 00:00:00 2001 From: ta2ye0n Date: Thu, 10 Oct 2024 00:17:24 +0900 Subject: [PATCH 21/29] add :: JwtProperties --- .../v3/global/security/jwt/JwtProperties.java | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 src/main/java/com/gcms/v3/global/security/jwt/JwtProperties.java diff --git a/src/main/java/com/gcms/v3/global/security/jwt/JwtProperties.java b/src/main/java/com/gcms/v3/global/security/jwt/JwtProperties.java new file mode 100644 index 0000000..1209f93 --- /dev/null +++ b/src/main/java/com/gcms/v3/global/security/jwt/JwtProperties.java @@ -0,0 +1,25 @@ +package com.gcms.v3.global.security.jwt; + +import lombok.Getter; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +@Getter +@Component +public class JwtProperties { + @Value("${jwt.secret.access-token}") + private String accessTokenKey; + + @Value("${jwt.secret.refresh-token}") + private String refreshTokenKey; + + public static final String AUTHORITIES = "auth"; + + public static final String GRANT_TYPE = "Bearer"; + + public static final String TOKEN_PREFIX = "Bearer "; + + public static final long ACCESS_TOKEN_TIME = 1000 * 60 * 30L; + + public static final long REFRESH_TOKEN_TIME = 1000L * 60 * 60 * 24 * 7; +} From 31346bdcc79880de855956005af19fee8fb79b0c Mon Sep 17 00:00:00 2001 From: ta2ye0n Date: Thu, 10 Oct 2024 00:18:27 +0900 Subject: [PATCH 22/29] =?UTF-8?q?update=20::=20Secret=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/security/jwt/JwtTokenProvider.java | 29 +++++++++---------- src/main/resources/application.yml | 4 ++- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/src/main/java/com/gcms/v3/global/security/jwt/JwtTokenProvider.java b/src/main/java/com/gcms/v3/global/security/jwt/JwtTokenProvider.java index 77bb23a..0fa3f11 100644 --- a/src/main/java/com/gcms/v3/global/security/jwt/JwtTokenProvider.java +++ b/src/main/java/com/gcms/v3/global/security/jwt/JwtTokenProvider.java @@ -9,7 +9,6 @@ import jakarta.annotation.PostConstruct; import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; -import org.springframework.beans.factory.annotation.Value; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.userdetails.UserDetails; @@ -20,24 +19,24 @@ import java.time.LocalDateTime; import java.util.Date; +import static com.gcms.v3.global.security.jwt.JwtProperties.*; + @Component @RequiredArgsConstructor public class JwtTokenProvider { - @Value("${jwt.secret}") - private String secretKey; - private static final String AUTHORITIES = "auth"; - private static final String GRANT_TYPE = "Bearer"; - private static final String TOKEN_PREFIX = "Bearer "; - private static final long ACCESS_TOKEN_TIME = 1000 * 60 * 30L; - private static final long REFRESH_TOKEN_TIME = 1000L * 60 * 60 * 24 * 7; - private static Key key; + private static Key accessTokenkey; + private static Key refreshtokenkey; private final AuthDetailsService authDetailsService; + private final JwtProperties jwtProperties; @PostConstruct public void init() { - byte[] keyBytes = Decoders.BASE64.decode(secretKey); - key = Keys.hmacShaKeyFor(keyBytes); + byte[] keyBytes = Decoders.BASE64.decode(jwtProperties.getAccessTokenKey()); + accessTokenkey = Keys.hmacShaKeyFor(keyBytes); + + byte[] refreshKeyBytes = Decoders.BASE64.decode(jwtProperties.getRefreshTokenKey()); + refreshtokenkey = Keys.hmacShaKeyFor(refreshKeyBytes); } public TokenInfoResponseDto generateToken(String email) { @@ -60,7 +59,7 @@ private String generateAccessToken(String email) { .setHeaderParam("typ", GRANT_TYPE) .claim(AUTHORITIES, "JWT") .setExpiration(accessTokenExpiresIn) - .signWith(key, SignatureAlgorithm.HS256) + .signWith(accessTokenkey, SignatureAlgorithm.HS256) .compact(); } @@ -72,7 +71,7 @@ private String generateRefreshToken(String email) { return Jwts.builder() .setSubject(email) .setHeaderParam("typ", "JWT") - .signWith(key, SignatureAlgorithm.HS256) + .signWith(refreshtokenkey, SignatureAlgorithm.HS256) .claim(AUTHORITIES, "JWT") .setIssuedAt(new Date()) .setExpiration(refreshTokenExpiresIn) @@ -92,7 +91,7 @@ public Authentication getAuthentication(String token) { private Claims parseClaims(String assessToken) { try { - return Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(assessToken).getBody(); + return Jwts.parserBuilder().setSigningKey(accessTokenkey).build().parseClaimsJws(assessToken).getBody(); } catch (ExpiredJwtException e) { return e.getClaims(); } @@ -108,7 +107,7 @@ public String resolveToken(HttpServletRequest request) { public boolean validateToken(String token) { try { - Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token); + Jwts.parserBuilder().setSigningKey(accessTokenkey).build().parseClaimsJws(token); return true; } catch (SecurityException | MalformedJwtException e) { throw new InvalidAuthTokenException(); diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index f4e822e..dbb1386 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -34,4 +34,6 @@ spring: port: 6379 jwt: - secret: ${JWT_SECRET} \ No newline at end of file + secret: + access-token: ${ACCESS_TOKEN_KEY} + refresh-token: ${REFRESH_TOKEN_KEY} \ No newline at end of file From c91aae52a17ca143a4da04a478f7e1514eed8ab4 Mon Sep 17 00:00:00 2001 From: ta2ye0n Date: Fri, 11 Oct 2024 10:47:59 +0900 Subject: [PATCH 23/29] =?UTF-8?q?update=20::=20redis=20=ED=99=98=EA=B2=BD?= =?UTF-8?q?=EB=B3=80=EC=88=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index dbb1386..0621016 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -30,8 +30,8 @@ spring: data: redis: - host: 127.0.0.1 - port: 6379 + host: ${REDIS_HOST} + port: ${REDIS_PORT} jwt: secret: From 5094a592c6932955f28d0ca4f2518e910e7c798d Mon Sep 17 00:00:00 2001 From: ta2ye0n Date: Fri, 11 Oct 2024 10:48:28 +0900 Subject: [PATCH 24/29] =?UTF-8?q?update=20::=20record=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/presentation/data/request/SignInRequestDto.java | 8 +++----- .../v3/domain/auth/service/impl/SignInServiceImpl.java | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/gcms/v3/domain/auth/presentation/data/request/SignInRequestDto.java b/src/main/java/com/gcms/v3/domain/auth/presentation/data/request/SignInRequestDto.java index 41dd59a..4230a41 100644 --- a/src/main/java/com/gcms/v3/domain/auth/presentation/data/request/SignInRequestDto.java +++ b/src/main/java/com/gcms/v3/domain/auth/presentation/data/request/SignInRequestDto.java @@ -1,8 +1,6 @@ package com.gcms.v3.domain.auth.presentation.data.request; -import lombok.Getter; - -@Getter -public class SignInRequestDto { - private String code; +public record SignInRequestDto ( + String code +){ } diff --git a/src/main/java/com/gcms/v3/domain/auth/service/impl/SignInServiceImpl.java b/src/main/java/com/gcms/v3/domain/auth/service/impl/SignInServiceImpl.java index 41e76b0..a3974e5 100644 --- a/src/main/java/com/gcms/v3/domain/auth/service/impl/SignInServiceImpl.java +++ b/src/main/java/com/gcms/v3/domain/auth/service/impl/SignInServiceImpl.java @@ -26,7 +26,7 @@ public class SignInServiceImpl implements SignInService { private final UserRoleRepository userRoleRepository; public TokenInfoResponseDto execute(SignInRequestDto signInRequestDto) { - String accessToken = oAuth2Service.requestAccessToken(signInRequestDto.getCode()); + String accessToken = oAuth2Service.requestAccessToken(signInRequestDto.code()); GoogleOAuth2UserInfo googleOAuth2UserInfo = oAuth2Service.requestUserInfo(accessToken); User user = userRepository.findByEmail(googleOAuth2UserInfo.getEmail()) From 44d4f1c0d203e146d46d2cd1f51c1e08ad4640bb Mon Sep 17 00:00:00 2001 From: ta2ye0n Date: Fri, 11 Oct 2024 10:48:55 +0900 Subject: [PATCH 25/29] =?UTF-8?q?add=20::=20configuration=20=EC=9D=98?= =?UTF-8?q?=EC=A1=B4=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 2 ++ 1 file changed, 2 insertions(+) diff --git a/build.gradle b/build.gradle index 2fb58af..b0a95c7 100644 --- a/build.gradle +++ b/build.gradle @@ -41,6 +41,8 @@ dependencies { implementation 'io.jsonwebtoken:jjwt-api:0.11.5' implementation 'io.jsonwebtoken:jjwt-impl:0.11.5' implementation 'io.jsonwebtoken:jjwt-jackson:0.11.5' + + annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor' } tasks.named('test') { From 438abb955207b6ed9e33f6f033ad8913f37ad712 Mon Sep 17 00:00:00 2001 From: ta2ye0n Date: Fri, 11 Oct 2024 10:49:28 +0900 Subject: [PATCH 26/29] update :: properties --- .../com/gcms/v3/GcmsServerV3Application.java | 2 ++ .../v3/global/security/jwt/JwtProperties.java | 27 +++++-------------- .../global/security/jwt/JwtTokenProvider.java | 16 ++++++++--- 3 files changed, 21 insertions(+), 24 deletions(-) diff --git a/src/main/java/com/gcms/v3/GcmsServerV3Application.java b/src/main/java/com/gcms/v3/GcmsServerV3Application.java index 278832a..70af294 100644 --- a/src/main/java/com/gcms/v3/GcmsServerV3Application.java +++ b/src/main/java/com/gcms/v3/GcmsServerV3Application.java @@ -2,8 +2,10 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.ConfigurationPropertiesScan; @SpringBootApplication +@ConfigurationPropertiesScan public class GcmsServerV3Application { public static void main(String[] args) { diff --git a/src/main/java/com/gcms/v3/global/security/jwt/JwtProperties.java b/src/main/java/com/gcms/v3/global/security/jwt/JwtProperties.java index 1209f93..9a8337c 100644 --- a/src/main/java/com/gcms/v3/global/security/jwt/JwtProperties.java +++ b/src/main/java/com/gcms/v3/global/security/jwt/JwtProperties.java @@ -1,25 +1,12 @@ package com.gcms.v3.global.security.jwt; -import lombok.Getter; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Component; +import org.springframework.boot.context.properties.ConfigurationProperties; -@Getter -@Component -public class JwtProperties { - @Value("${jwt.secret.access-token}") - private String accessTokenKey; - - @Value("${jwt.secret.refresh-token}") - private String refreshTokenKey; - - public static final String AUTHORITIES = "auth"; - - public static final String GRANT_TYPE = "Bearer"; - - public static final String TOKEN_PREFIX = "Bearer "; +@ConfigurationProperties(prefix = "jwt.secret") +public record JwtProperties ( + String accessToken, + String refreshToken +){ +} - public static final long ACCESS_TOKEN_TIME = 1000 * 60 * 30L; - public static final long REFRESH_TOKEN_TIME = 1000L * 60 * 60 * 24 * 7; -} diff --git a/src/main/java/com/gcms/v3/global/security/jwt/JwtTokenProvider.java b/src/main/java/com/gcms/v3/global/security/jwt/JwtTokenProvider.java index 0fa3f11..dd7e0ab 100644 --- a/src/main/java/com/gcms/v3/global/security/jwt/JwtTokenProvider.java +++ b/src/main/java/com/gcms/v3/global/security/jwt/JwtTokenProvider.java @@ -19,12 +19,20 @@ import java.time.LocalDateTime; import java.util.Date; -import static com.gcms.v3.global.security.jwt.JwtProperties.*; - @Component @RequiredArgsConstructor public class JwtTokenProvider { + private static final String AUTHORITIES = "auth"; + + private static final String GRANT_TYPE = "Bearer"; + + private static final String TOKEN_PREFIX = "Bearer "; + + private static final long ACCESS_TOKEN_TIME = 1000 * 60 * 30L; + + private static final long REFRESH_TOKEN_TIME = 1000L * 60 * 60 * 24 * 7; + private static Key accessTokenkey; private static Key refreshtokenkey; private final AuthDetailsService authDetailsService; @@ -32,10 +40,10 @@ public class JwtTokenProvider { @PostConstruct public void init() { - byte[] keyBytes = Decoders.BASE64.decode(jwtProperties.getAccessTokenKey()); + byte[] keyBytes = Decoders.BASE64.decode(jwtProperties.accessToken()); accessTokenkey = Keys.hmacShaKeyFor(keyBytes); - byte[] refreshKeyBytes = Decoders.BASE64.decode(jwtProperties.getRefreshTokenKey()); + byte[] refreshKeyBytes = Decoders.BASE64.decode(jwtProperties.refreshToken()); refreshtokenkey = Keys.hmacShaKeyFor(refreshKeyBytes); } From e06e043395a49ab0456aa2d82c9a2787d0bf72f8 Mon Sep 17 00:00:00 2001 From: ta2ye0n Date: Fri, 11 Oct 2024 11:09:27 +0900 Subject: [PATCH 27/29] =?UTF-8?q?update=20::=20record=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/response/TokenInfoResponseDto.java | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/gcms/v3/domain/auth/presentation/data/response/TokenInfoResponseDto.java b/src/main/java/com/gcms/v3/domain/auth/presentation/data/response/TokenInfoResponseDto.java index 004eb24..9adc2d8 100644 --- a/src/main/java/com/gcms/v3/domain/auth/presentation/data/response/TokenInfoResponseDto.java +++ b/src/main/java/com/gcms/v3/domain/auth/presentation/data/response/TokenInfoResponseDto.java @@ -1,15 +1,14 @@ package com.gcms.v3.domain.auth.presentation.data.response; import lombok.Builder; -import lombok.Getter; import java.time.LocalDateTime; -@Getter @Builder -public class TokenInfoResponseDto { - private String accessToken; - private String refreshToken; - private LocalDateTime accessTokenExpiresIn; - private LocalDateTime refreshTokenExpiresIn; +public record TokenInfoResponseDto( + String accessToken, + String refreshToken, + LocalDateTime accessTokenExpiresIn, + LocalDateTime refreshTokenExpiresIn +) { } From 292e0055e42491eccee58946fcbf66cf2a91b553 Mon Sep 17 00:00:00 2001 From: ta2ye0n Date: Fri, 11 Oct 2024 12:03:43 +0900 Subject: [PATCH 28/29] =?UTF-8?q?update=20::=20record=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/gcms/v3/global/error/ErrorResponse.java | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/gcms/v3/global/error/ErrorResponse.java b/src/main/java/com/gcms/v3/global/error/ErrorResponse.java index 6302d9f..695d736 100644 --- a/src/main/java/com/gcms/v3/global/error/ErrorResponse.java +++ b/src/main/java/com/gcms/v3/global/error/ErrorResponse.java @@ -1,18 +1,14 @@ package com.gcms.v3.global.error; -import lombok.AllArgsConstructor; import lombok.Builder; -import lombok.Getter; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -@Getter @Builder -@AllArgsConstructor -public class ErrorResponse { - private HttpStatus status; - private String message; - +public record ErrorResponse( + HttpStatus status, + String message +) { public static ResponseEntity toResponseEntity(ErrorCode e){ return ResponseEntity .status(e.getHttpStatus()) From 3943c761887fb669cdc7abcb5b4b21875660e08b Mon Sep 17 00:00:00 2001 From: ta2ye0n Date: Sat, 12 Oct 2024 00:55:55 +0900 Subject: [PATCH 29/29] update :: ParameterizedTypeReference<> --- .../gcms/v3/global/oauth2/OAuth2Service.java | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/gcms/v3/global/oauth2/OAuth2Service.java b/src/main/java/com/gcms/v3/global/oauth2/OAuth2Service.java index 64ca78f..7cf1d4d 100644 --- a/src/main/java/com/gcms/v3/global/oauth2/OAuth2Service.java +++ b/src/main/java/com/gcms/v3/global/oauth2/OAuth2Service.java @@ -1,6 +1,7 @@ package com.gcms.v3.global.oauth2; import lombok.RequiredArgsConstructor; +import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.*; import org.springframework.stereotype.Service; import org.springframework.util.LinkedMultiValueMap; @@ -35,8 +36,16 @@ public String requestAccessToken(String code) { HttpEntity request = new HttpEntity<>(body, httpHeaders); - ResponseEntity response = restTemplate.postForEntity(config.getTokenUri(), request, Map.class); - return (String) response.getBody().get("access_token"); + ResponseEntity> response = + restTemplate.exchange( + config.getTokenUri(), + HttpMethod.POST, + request, + new ParameterizedTypeReference<>() { + } + ); + + return response.getBody().get("access_token"); } @@ -49,7 +58,13 @@ public GoogleOAuth2UserInfo requestUserInfo(String accessToken) { HttpEntity request = new HttpEntity<>(body, httpHeaders); - ResponseEntity response = restTemplate.exchange(config.getUserInfoUri(), HttpMethod.GET, request, Map.class); + ResponseEntity> response = + restTemplate.exchange( + config.getUserInfoUri(), + HttpMethod.GET, + request, + new ParameterizedTypeReference<>() {} + ); return new GoogleOAuth2UserInfo(response.getBody()); }