From c06f21b6306ba4451a07edaa4d180802917babec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=ED=98=9C=EC=97=B0?= Date: Fri, 5 Jan 2024 23:40:24 +0900 Subject: [PATCH 01/17] =?UTF-8?q?=E2=9C=A8=20feat:=20=EC=B9=B4=EC=B9=B4?= =?UTF-8?q?=EC=98=A4=20=ED=9A=8C=EC=9B=90=EA=B0=80=EC=9E=85/=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 12 +++ .../domain/user/Service/OauthService.java | 94 +++++++++++++++++++ .../user/controller/OauthController.java | 25 +++++ .../user/dto/request/OAuth2UserInfo.java | 7 ++ .../domain/user/dto/request/UserProfile.java | 32 +++++++ .../user/dto/response/LoginResponse.java | 24 +++++ .../user/dto/response/OauthTokenResponse.java | 22 +++++ .../domain/user/entity/SocialPlatform.java | 4 + .../domain/user/entity/User.java | 16 ++-- .../user/repository/UserRepository.java | 15 +++ .../global/config/oauth/JwtTokenProvider.java | 77 +++++++++++++++ ...th2ClientRegistrationRepositoryConfig.java | 35 +++++++ 12 files changed, 356 insertions(+), 7 deletions(-) create mode 100644 src/main/java/sopt/org/motivooServer/domain/user/Service/OauthService.java create mode 100644 src/main/java/sopt/org/motivooServer/domain/user/controller/OauthController.java create mode 100644 src/main/java/sopt/org/motivooServer/domain/user/dto/request/OAuth2UserInfo.java create mode 100644 src/main/java/sopt/org/motivooServer/domain/user/dto/request/UserProfile.java create mode 100644 src/main/java/sopt/org/motivooServer/domain/user/dto/response/LoginResponse.java create mode 100644 src/main/java/sopt/org/motivooServer/domain/user/dto/response/OauthTokenResponse.java create mode 100644 src/main/java/sopt/org/motivooServer/domain/user/repository/UserRepository.java create mode 100644 src/main/java/sopt/org/motivooServer/global/config/oauth/JwtTokenProvider.java create mode 100644 src/main/java/sopt/org/motivooServer/global/config/oauth/OAuth2ClientRegistrationRepositoryConfig.java diff --git a/build.gradle b/build.gradle index 7671a241..56e9e2c0 100644 --- a/build.gradle +++ b/build.gradle @@ -47,6 +47,18 @@ dependencies { compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' + //OAuth2 + implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' + implementation 'org.springframework.boot:spring-boot-starter-webflux' + + //jwt + implementation group: 'io.jsonwebtoken', name: 'jjwt-api', version: '0.11.2' + implementation group: 'io.jsonwebtoken', name: 'jjwt-impl', version: '0.11.2' + implementation group: 'io.jsonwebtoken', name: 'jjwt-jackson', version: '0.11.2' + + //redis + implementation 'org.springframework.boot:spring-boot-starter-data-redis' + testImplementation 'org.springframework.boot:spring-boot-starter-test' } diff --git a/src/main/java/sopt/org/motivooServer/domain/user/Service/OauthService.java b/src/main/java/sopt/org/motivooServer/domain/user/Service/OauthService.java new file mode 100644 index 00000000..26f7955e --- /dev/null +++ b/src/main/java/sopt/org/motivooServer/domain/user/Service/OauthService.java @@ -0,0 +1,94 @@ +package sopt.org.motivooServer.domain.user.Service; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.security.oauth2.client.registration.ClientRegistration; +import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.reactive.function.client.WebClient; +import sopt.org.motivooServer.global.config.oauth.JwtTokenProvider; +import sopt.org.motivooServer.domain.user.dto.request.OAuth2UserInfo; +import sopt.org.motivooServer.domain.user.dto.request.UserProfile; +import sopt.org.motivooServer.domain.user.dto.response.LoginResponse; +import sopt.org.motivooServer.domain.user.dto.response.OauthTokenResponse; +import sopt.org.motivooServer.domain.user.entity.SocialPlatform; +import sopt.org.motivooServer.domain.user.entity.User; +import sopt.org.motivooServer.domain.user.entity.UserType; +import sopt.org.motivooServer.domain.user.repository.UserRepository; + +import java.util.Map; + +@Service +@RequiredArgsConstructor +@Slf4j +@Transactional(readOnly = true) +public class OauthService { + private static final String BEARER_TYPE = "Bearer"; + private final InMemoryClientRegistrationRepository inMemoryRepository; + private final UserRepository userRepository; + private final JwtTokenProvider jwtTokenProvider; + + @Transactional + public LoginResponse login(String providerName, OauthTokenResponse tokenResponse) { + ClientRegistration provider = inMemoryRepository.findByRegistrationId(providerName); + + User user = getUserProfile(providerName, tokenResponse, provider); + + String accessToken = tokenResponse.getAccessToken(); + String refreshToken = jwtTokenProvider.createRefreshToken(); + + return LoginResponse.builder() + .id(user.getSocialId()) + .nickname(user.getNickname()) + .tokenType(BEARER_TYPE) + .accessToken(accessToken) + .refreshToken(refreshToken) + .build(); + } + + + private User getUserProfile(String providerName, OauthTokenResponse tokenResponse, ClientRegistration provider){ + Map userAttributes = getUserAttributes(provider, tokenResponse); + OAuth2UserInfo oAuth2UserInfo = null; + SocialPlatform socialPlatform = null; + if(providerName.equals("kakao")){ + oAuth2UserInfo = new UserProfile(userAttributes); + socialPlatform = SocialPlatform.KAKAO; + } else { + log.info("허용되지 않은 접근 입니다."); + } + + String providerId = oAuth2UserInfo.getProviderId(); + String nickName = oAuth2UserInfo.getNickName(); + + User userEntity = userRepository.findBySocialId(providerId); + + if(userEntity == null){ + userEntity = User.builder() + .nickname(nickName) + .socialId(providerId) + .socialPlatform(socialPlatform) + .socialAccessToken(tokenResponse.getAccessToken()) + .type(UserType.NONE) + .deleted(Boolean.FALSE) + .build(); + userRepository.save(userEntity); + } + return userEntity; + } + + private Map getUserAttributes(ClientRegistration provider, OauthTokenResponse tokenResponse){ + return WebClient.create() + .get() + .uri(provider.getProviderDetails().getUserInfoEndpoint().getUri()) + .headers(header -> header.setBearerAuth(tokenResponse.getAccessToken())) + .retrieve() + .bodyToMono(new ParameterizedTypeReference>(){}) + .block(); + } + + + +} diff --git a/src/main/java/sopt/org/motivooServer/domain/user/controller/OauthController.java b/src/main/java/sopt/org/motivooServer/domain/user/controller/OauthController.java new file mode 100644 index 00000000..bac442ac --- /dev/null +++ b/src/main/java/sopt/org/motivooServer/domain/user/controller/OauthController.java @@ -0,0 +1,25 @@ +package sopt.org.motivooServer.domain.user.controller; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import sopt.org.motivooServer.domain.user.Service.OauthService; +import sopt.org.motivooServer.domain.user.dto.response.LoginResponse; +import sopt.org.motivooServer.domain.user.dto.response.OauthTokenResponse; + +@RequiredArgsConstructor +@Slf4j +@RestController +public class OauthController { + private final OauthService oauthService; + + @GetMapping("/login/oauth/{provider}") + public ResponseEntity login(@PathVariable String provider, @RequestBody OauthTokenResponse tokenResponse){ + LoginResponse loginResponse = oauthService.login(provider, tokenResponse); + + return ResponseEntity.ok().body(loginResponse); + } + + +} diff --git a/src/main/java/sopt/org/motivooServer/domain/user/dto/request/OAuth2UserInfo.java b/src/main/java/sopt/org/motivooServer/domain/user/dto/request/OAuth2UserInfo.java new file mode 100644 index 00000000..84c8e360 --- /dev/null +++ b/src/main/java/sopt/org/motivooServer/domain/user/dto/request/OAuth2UserInfo.java @@ -0,0 +1,7 @@ +package sopt.org.motivooServer.domain.user.dto.request; + +public interface OAuth2UserInfo { + String getProviderId(); + String getProvider(); + String getNickName(); +} diff --git a/src/main/java/sopt/org/motivooServer/domain/user/dto/request/UserProfile.java b/src/main/java/sopt/org/motivooServer/domain/user/dto/request/UserProfile.java new file mode 100644 index 00000000..84d1a432 --- /dev/null +++ b/src/main/java/sopt/org/motivooServer/domain/user/dto/request/UserProfile.java @@ -0,0 +1,32 @@ +package sopt.org.motivooServer.domain.user.dto.request; + +import lombok.Getter; + +import java.util.Map; + +@Getter +public class UserProfile implements OAuth2UserInfo{ + private Map attributes; + + public UserProfile(Map attributes){ + this.attributes = attributes; + } + + @Override + public String getProviderId() { + return String.valueOf(attributes.get("sub")); + } + + @Override + public String getProvider() { + return "kakao"; + } + + + @Override + public String getNickName() { + return String.valueOf(attributes.get("nickname")); + } + + +} diff --git a/src/main/java/sopt/org/motivooServer/domain/user/dto/response/LoginResponse.java b/src/main/java/sopt/org/motivooServer/domain/user/dto/response/LoginResponse.java new file mode 100644 index 00000000..d8a23dcf --- /dev/null +++ b/src/main/java/sopt/org/motivooServer/domain/user/dto/response/LoginResponse.java @@ -0,0 +1,24 @@ +package sopt.org.motivooServer.domain.user.dto.response; + +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +public class LoginResponse { + private String id; + private String nickname; + private String tokenType; + private String accessToken; + private String refreshToken; + + @Builder + public LoginResponse(String id, String nickname, String tokenType, String accessToken, String refreshToken){ + this.id = id; + this.nickname = nickname; + this.tokenType = tokenType; + this.accessToken = accessToken; + this.refreshToken = refreshToken; + } +} diff --git a/src/main/java/sopt/org/motivooServer/domain/user/dto/response/OauthTokenResponse.java b/src/main/java/sopt/org/motivooServer/domain/user/dto/response/OauthTokenResponse.java new file mode 100644 index 00000000..1a43ae77 --- /dev/null +++ b/src/main/java/sopt/org/motivooServer/domain/user/dto/response/OauthTokenResponse.java @@ -0,0 +1,22 @@ +package sopt.org.motivooServer.domain.user.dto.response; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +public class OauthTokenResponse { + @JsonProperty("access_token") + private String accessToken; + + @JsonProperty("token_type") + private String tokenType; + + @Builder + public OauthTokenResponse(String accessToken, String tokenType){ + this.accessToken = accessToken; + this.tokenType = tokenType; + } +} diff --git a/src/main/java/sopt/org/motivooServer/domain/user/entity/SocialPlatform.java b/src/main/java/sopt/org/motivooServer/domain/user/entity/SocialPlatform.java index a6f40b64..1f85ab0a 100644 --- a/src/main/java/sopt/org/motivooServer/domain/user/entity/SocialPlatform.java +++ b/src/main/java/sopt/org/motivooServer/domain/user/entity/SocialPlatform.java @@ -3,9 +3,11 @@ import static sopt.org.motivooServer.domain.user.exception.UserExceptionType.*; import java.util.Arrays; +import java.util.Map; import lombok.AccessLevel; import lombok.RequiredArgsConstructor; +import sopt.org.motivooServer.domain.user.dto.request.UserProfile; import sopt.org.motivooServer.domain.user.exception.UserException; @RequiredArgsConstructor(access = AccessLevel.PRIVATE) @@ -22,4 +24,6 @@ public static SocialPlatform of(String value) { .findFirst() .orElseThrow(() -> new UserException(INVALID_SOCIAL_PLATFORM)); } + + } diff --git a/src/main/java/sopt/org/motivooServer/domain/user/entity/User.java b/src/main/java/sopt/org/motivooServer/domain/user/entity/User.java index 18a7f97f..a3da0084 100644 --- a/src/main/java/sopt/org/motivooServer/domain/user/entity/User.java +++ b/src/main/java/sopt/org/motivooServer/domain/user/entity/User.java @@ -1,9 +1,9 @@ package sopt.org.motivooServer.domain.user.entity; -import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; +import lombok.*; import org.hibernate.annotations.SQLDelete; import jakarta.persistence.Column; @@ -17,7 +17,6 @@ import jakarta.persistence.ManyToOne; import jakarta.persistence.OneToMany; import jakarta.persistence.Table; -import lombok.Getter; import sopt.org.motivooServer.domain.common.BaseTimeEntity; import sopt.org.motivooServer.domain.mission.entity.UserMission; import sopt.org.motivooServer.domain.parentchild.entity.Parentchild; @@ -25,6 +24,8 @@ @Getter @Entity @Table(name = "`user`") +@Builder +@AllArgsConstructor(access = AccessLevel.PROTECTED) @SQLDelete(sql = "UPDATE user SET user.deleted=true WHERE user_id=?") public class User extends BaseTimeEntity { @@ -32,8 +33,6 @@ public class User extends BaseTimeEntity { @Column(name = "user_id") private Long id; - private String username; - private Integer age; @Enumerated(EnumType.STRING) @@ -44,9 +43,9 @@ public class User extends BaseTimeEntity { private Boolean deleted = Boolean.FALSE; @Column(nullable = false) - private Long socialId; + private String socialId; - private String socialNickname; + private String nickname; private String socialAccessToken; @@ -54,7 +53,6 @@ public class User extends BaseTimeEntity { @Column(nullable = false) private SocialPlatform socialPlatform; - @ManyToOne @JoinColumn(name = "parentchild_id") private Parentchild parentchild; @@ -62,6 +60,10 @@ public class User extends BaseTimeEntity { @OneToMany(mappedBy = "user") private List userMissions = new ArrayList<>(); + public User() { + + } + //== 연관관계 메서드 ==// public void addUserMission(UserMission userMission) { diff --git a/src/main/java/sopt/org/motivooServer/domain/user/repository/UserRepository.java b/src/main/java/sopt/org/motivooServer/domain/user/repository/UserRepository.java new file mode 100644 index 00000000..c1ad19bb --- /dev/null +++ b/src/main/java/sopt/org/motivooServer/domain/user/repository/UserRepository.java @@ -0,0 +1,15 @@ +package sopt.org.motivooServer.domain.user.repository; + + +import org.springframework.data.repository.Repository; +import sopt.org.motivooServer.domain.user.entity.User; + +import java.util.Optional; + +public interface UserRepository extends Repository { + void save(User user); + Optional findById(Long id); + + User findBySocialId(String socialId); + +} diff --git a/src/main/java/sopt/org/motivooServer/global/config/oauth/JwtTokenProvider.java b/src/main/java/sopt/org/motivooServer/global/config/oauth/JwtTokenProvider.java new file mode 100644 index 00000000..50da0626 --- /dev/null +++ b/src/main/java/sopt/org/motivooServer/global/config/oauth/JwtTokenProvider.java @@ -0,0 +1,77 @@ +package sopt.org.motivooServer.global.config.oauth; + +import io.jsonwebtoken.*; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.oauth2.jwt.JwtException; +import org.springframework.stereotype.Component; +import java.nio.charset.StandardCharsets; +import java.util.Date; +import java.util.Random; + +@Component +@Slf4j +public class JwtTokenProvider { + + @Value("${jwt.access-token.expire-length}") + private long accessTokenValidityInMilliseconds; + + @Value("${jwt.refresh-token.expire-length}") + private long refreshTokenValidityInMilliseconds; + + @Value("${jwt.token.secret-key}") + private String secretKey; + + public String createAccessToken(String payload) { + return createToken(payload, accessTokenValidityInMilliseconds); + } + + public String createRefreshToken() { + byte[] array = new byte[7]; + new Random().nextBytes(array); + String generatedString = new String(array, StandardCharsets.UTF_8); + return createToken(generatedString, refreshTokenValidityInMilliseconds); + } + + public String createToken(String payload, long expireLength) { + Claims claims = Jwts.claims().setSubject(payload); + Date now = new Date(); + Date validity = new Date(now.getTime() + expireLength); + return Jwts.builder() + .setClaims(claims) + .setIssuedAt(now) + .setExpiration(validity) + .signWith(SignatureAlgorithm.HS256,secretKey) + .compact(); + } + + public String getPayload(String token){ + try { + return Jwts.parserBuilder() + .setSigningKey(secretKey) + .build() + .parseClaimsJws(token) + .getBody() + .getSubject(); + } catch (ExpiredJwtException e) { + return e.getClaims().getSubject(); + } catch (JwtException e){ + throw new RuntimeException("유효하지 않은 토큰 입니다"); + } + } + + public boolean validateToken(String token) { + try { + Jws claimsJws = Jwts.parserBuilder() + .setSigningKey(secretKey) + .build() + .parseClaimsJws(token); + return !claimsJws.getBody().getExpiration().before(new Date()); + } catch (JwtException | IllegalArgumentException exception) { + return false; + } + } + + + +} \ No newline at end of file diff --git a/src/main/java/sopt/org/motivooServer/global/config/oauth/OAuth2ClientRegistrationRepositoryConfig.java b/src/main/java/sopt/org/motivooServer/global/config/oauth/OAuth2ClientRegistrationRepositoryConfig.java new file mode 100644 index 00000000..9bf3bdd5 --- /dev/null +++ b/src/main/java/sopt/org/motivooServer/global/config/oauth/OAuth2ClientRegistrationRepositoryConfig.java @@ -0,0 +1,35 @@ +package sopt.org.motivooServer.global.config.oauth; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.security.oauth2.client.ClientsConfiguredCondition; +import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientProperties; +import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientPropertiesRegistrationAdapter; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.oauth2.client.registration.ClientRegistration; +import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; +import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository; + +import java.util.ArrayList; +import java.util.List; + +@Configuration +@EnableConfigurationProperties(OAuth2ClientProperties.class) +@Conditional(ClientsConfiguredCondition.class) +public class OAuth2ClientRegistrationRepositoryConfig { + private final OAuth2ClientProperties properties; + + OAuth2ClientRegistrationRepositoryConfig(OAuth2ClientProperties properties) { + this.properties = properties; + } + + @Bean + @ConditionalOnMissingBean(ClientRegistrationRepository.class) + public InMemoryClientRegistrationRepository clientRegistrationRepository() { + List registrations = new ArrayList<>( + OAuth2ClientPropertiesRegistrationAdapter.getClientRegistrations(this.properties).values()); + return new InMemoryClientRegistrationRepository(registrations); + } +} From 23521cf92b17d57d46947996348c639005d190aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=ED=98=9C=EC=97=B0?= Date: Sat, 6 Jan 2024 09:11:25 +0900 Subject: [PATCH 02/17] =?UTF-8?q?=E2=9C=A8=20feat:=20redis=EB=A5=BC=20?= =?UTF-8?q?=ED=99=9C=EC=9A=A9=ED=95=9C=20refreshtoken=20=EC=9E=AC=EB=B0=9C?= =?UTF-8?q?=EA=B8=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/user/Service/OauthService.java | 25 +++++- .../{request => oauth}/OAuth2UserInfo.java | 2 +- .../domain/user/dto/redis/RefreshToken.java | 14 ++++ .../domain/user/dto/request/UserProfile.java | 3 +- .../user/repository/TokenRedisRepository.java | 78 +++++++++++++++++++ .../global/config/oauth/JwtTokenProvider.java | 1 + .../global/config/redis/RedisConfig.java | 32 ++++++++ src/main/resources/application.yml | 33 ++++++++ 8 files changed, 185 insertions(+), 3 deletions(-) rename src/main/java/sopt/org/motivooServer/domain/user/dto/{request => oauth}/OAuth2UserInfo.java (67%) create mode 100644 src/main/java/sopt/org/motivooServer/domain/user/dto/redis/RefreshToken.java create mode 100644 src/main/java/sopt/org/motivooServer/domain/user/repository/TokenRedisRepository.java create mode 100644 src/main/java/sopt/org/motivooServer/global/config/redis/RedisConfig.java create mode 100644 src/main/resources/application.yml diff --git a/src/main/java/sopt/org/motivooServer/domain/user/Service/OauthService.java b/src/main/java/sopt/org/motivooServer/domain/user/Service/OauthService.java index 26f7955e..6e8ef4c1 100644 --- a/src/main/java/sopt/org/motivooServer/domain/user/Service/OauthService.java +++ b/src/main/java/sopt/org/motivooServer/domain/user/Service/OauthService.java @@ -1,5 +1,6 @@ package sopt.org.motivooServer.domain.user.Service; +import jakarta.persistence.EntityNotFoundException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.core.ParameterizedTypeReference; @@ -8,8 +9,9 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.reactive.function.client.WebClient; +import sopt.org.motivooServer.domain.user.repository.TokenRedisRepository; import sopt.org.motivooServer.global.config.oauth.JwtTokenProvider; -import sopt.org.motivooServer.domain.user.dto.request.OAuth2UserInfo; +import sopt.org.motivooServer.domain.user.dto.oauth.OAuth2UserInfo; import sopt.org.motivooServer.domain.user.dto.request.UserProfile; import sopt.org.motivooServer.domain.user.dto.response.LoginResponse; import sopt.org.motivooServer.domain.user.dto.response.OauthTokenResponse; @@ -28,8 +30,10 @@ public class OauthService { private static final String BEARER_TYPE = "Bearer"; private final InMemoryClientRegistrationRepository inMemoryRepository; private final UserRepository userRepository; + private final TokenRedisRepository tokenRedisRepository; private final JwtTokenProvider jwtTokenProvider; + @Transactional public LoginResponse login(String providerName, OauthTokenResponse tokenResponse) { ClientRegistration provider = inMemoryRepository.findByRegistrationId(providerName); @@ -39,6 +43,8 @@ public LoginResponse login(String providerName, OauthTokenResponse tokenResponse String accessToken = tokenResponse.getAccessToken(); String refreshToken = jwtTokenProvider.createRefreshToken(); + reissue(user.getId(), refreshToken); //refresh token 재발급 + return LoginResponse.builder() .id(user.getSocialId()) .nickname(user.getNickname()) @@ -89,6 +95,23 @@ private Map getUserAttributes(ClientRegistration provider, Oauth .block(); } + public String reissue(Long userId, String refreshToken){ + User user = userRepository.findById(userId).orElseThrow(()->new EntityNotFoundException()); + + if(jwtTokenProvider.validateToken(refreshToken)){ + + } + String reissuedToken = jwtTokenProvider.createRefreshToken(); + tokenRedisRepository.saveRefreshToken(reissuedToken, String.valueOf(userId)); + tokenRedisRepository.deleteRefreshToken(refreshToken); + return reissuedToken; + } + + public void logout(String accessToken, String refreshToken){ + //tokenRedisRepository.saveBlockedToken(accessToken); + tokenRedisRepository.deleteRefreshToken(refreshToken); + + } } diff --git a/src/main/java/sopt/org/motivooServer/domain/user/dto/request/OAuth2UserInfo.java b/src/main/java/sopt/org/motivooServer/domain/user/dto/oauth/OAuth2UserInfo.java similarity index 67% rename from src/main/java/sopt/org/motivooServer/domain/user/dto/request/OAuth2UserInfo.java rename to src/main/java/sopt/org/motivooServer/domain/user/dto/oauth/OAuth2UserInfo.java index 84c8e360..669b9d65 100644 --- a/src/main/java/sopt/org/motivooServer/domain/user/dto/request/OAuth2UserInfo.java +++ b/src/main/java/sopt/org/motivooServer/domain/user/dto/oauth/OAuth2UserInfo.java @@ -1,4 +1,4 @@ -package sopt.org.motivooServer.domain.user.dto.request; +package sopt.org.motivooServer.domain.user.dto.oauth; public interface OAuth2UserInfo { String getProviderId(); diff --git a/src/main/java/sopt/org/motivooServer/domain/user/dto/redis/RefreshToken.java b/src/main/java/sopt/org/motivooServer/domain/user/dto/redis/RefreshToken.java new file mode 100644 index 00000000..599277ec --- /dev/null +++ b/src/main/java/sopt/org/motivooServer/domain/user/dto/redis/RefreshToken.java @@ -0,0 +1,14 @@ +package sopt.org.motivooServer.domain.user.dto.redis; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +@AllArgsConstructor +public class RefreshToken { + private Long id; + private String refreshToken; + +} diff --git a/src/main/java/sopt/org/motivooServer/domain/user/dto/request/UserProfile.java b/src/main/java/sopt/org/motivooServer/domain/user/dto/request/UserProfile.java index 84d1a432..e5ce120f 100644 --- a/src/main/java/sopt/org/motivooServer/domain/user/dto/request/UserProfile.java +++ b/src/main/java/sopt/org/motivooServer/domain/user/dto/request/UserProfile.java @@ -1,11 +1,12 @@ package sopt.org.motivooServer.domain.user.dto.request; import lombok.Getter; +import sopt.org.motivooServer.domain.user.dto.oauth.OAuth2UserInfo; import java.util.Map; @Getter -public class UserProfile implements OAuth2UserInfo{ +public class UserProfile implements OAuth2UserInfo { private Map attributes; public UserProfile(Map attributes){ diff --git a/src/main/java/sopt/org/motivooServer/domain/user/repository/TokenRedisRepository.java b/src/main/java/sopt/org/motivooServer/domain/user/repository/TokenRedisRepository.java new file mode 100644 index 00000000..898234cb --- /dev/null +++ b/src/main/java/sopt/org/motivooServer/domain/user/repository/TokenRedisRepository.java @@ -0,0 +1,78 @@ +package sopt.org.motivooServer.domain.user.repository; + +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.data.redis.core.ValueOperations; +import org.springframework.stereotype.Repository; + +import java.util.Optional; +import java.util.concurrent.TimeUnit; + +@Repository +@RequiredArgsConstructor +public class TokenRedisRepository { + private final StringRedisTemplate redisTemplate; + + @Value("${jwt.refresh-token.expire-length}") + private long refreshTokenValidityInMilliseconds; + private final static String PREFIX_REFRESH = "REFRESH:"; + private final static String PREFIX_BLOCKED = "BLOCKED:"; + + /** + * 리프레시토큰을 저장합니다. + * 'PREFIX:[리프레시토큰]'을 key로 사용합니다. + * 회원의 계정을 value로 설정합니다. + * + * @param refreshToken 리프레시토큰 + * @param account 회원의 계정 + */ + public void saveRefreshToken(String refreshToken, String account) { + ValueOperations valueOperations = redisTemplate.opsForValue(); + String key = PREFIX_REFRESH + refreshToken; + valueOperations.set(key, account); + redisTemplate.expire(key, refreshTokenValidityInMilliseconds, TimeUnit.SECONDS); + } + + /** + * Block된 어세스토큰을 저장합니다. + * 'PREFIX:[어세스토큰]'을 key로 사용합니다. + * "empty"문자열을 value로 설정합니다. + * 어세스토큰의 남은 시간동안 저장됩니다. + * + * @param accessToken 어세스토큰 + * @param remainTime 남은 시간 + */ + public void saveBlockedToken(String accessToken, Long remainTime) { + ValueOperations valueOperations = redisTemplate.opsForValue(); + String key = PREFIX_BLOCKED + accessToken; + valueOperations.set(key, "empty"); + redisTemplate.expire(key, remainTime, TimeUnit.SECONDS); + } + + /** + * 리프레시토큰에 저장된 회원의 계정을 찾습니다. + * + * @param refreshToken 리프레시토큰 + * @return 회원의 계정 + */ + public Optional findByRefreshToken(String refreshToken) { + ValueOperations valueOperations = redisTemplate.opsForValue(); + String key = PREFIX_REFRESH + refreshToken; + return Optional.ofNullable(valueOperations.get(key)); + } + + + public boolean doesTokenBlocked(String accessToken) { + ValueOperations valueOperations = redisTemplate.opsForValue(); + String key = PREFIX_BLOCKED + accessToken; + return valueOperations.get(key) != null; + } + + + public void deleteRefreshToken(String refreshToken) { + ValueOperations valueOperations = redisTemplate.opsForValue(); + String key = PREFIX_REFRESH + refreshToken; + redisTemplate.delete(key); + } +} \ No newline at end of file diff --git a/src/main/java/sopt/org/motivooServer/global/config/oauth/JwtTokenProvider.java b/src/main/java/sopt/org/motivooServer/global/config/oauth/JwtTokenProvider.java index 50da0626..116e2c20 100644 --- a/src/main/java/sopt/org/motivooServer/global/config/oauth/JwtTokenProvider.java +++ b/src/main/java/sopt/org/motivooServer/global/config/oauth/JwtTokenProvider.java @@ -1,5 +1,6 @@ package sopt.org.motivooServer.global.config.oauth; +import ch.qos.logback.core.subst.Token; import io.jsonwebtoken.*; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; diff --git a/src/main/java/sopt/org/motivooServer/global/config/redis/RedisConfig.java b/src/main/java/sopt/org/motivooServer/global/config/redis/RedisConfig.java new file mode 100644 index 00000000..7a7ff464 --- /dev/null +++ b/src/main/java/sopt/org/motivooServer/global/config/redis/RedisConfig.java @@ -0,0 +1,32 @@ +package sopt.org.motivooServer.global.config.redis; + + +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.repository.configuration.EnableRedisRepositories; + +@Configuration +@EnableRedisRepositories +public class RedisConfig { + @Value("${spring.data.redis.host}") + private String redisHost; + + @Value("${spring.data.redis.port}") + private int redisPort; + + @Bean + public RedisConnectionFactory redisConnectionFactory() { + return new LettuceConnectionFactory(redisHost, redisPort); + } + + @Bean + public RedisTemplate redisTemplate() { + RedisTemplate redisTemplate = new RedisTemplate<>(); + redisTemplate.setConnectionFactory(redisConnectionFactory()); + return redisTemplate; + } +} \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 00000000..ce4d1484 --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,33 @@ +spring: + security: + oauth2: + client: + registration: + kakao: + client-id: ${kakao.client.id} + redirect-uri: http://localhost:8080/kakao/callback + client-authentication-method: POST + client-secret: ${kakao.client.secret} + authorization-grant-type: authorization_code + scope: + - sub + - nickname + client_name: kakao + provider: + kakao: + authorization-uri: https://kauth.kakao.com/oauth/authorize + token-uri: https://kauth.kakao.com/oauth/token + user-info-uri: https://kapi.kakao.com/v1/oidc/userinfo + user-name-attribute: id + data: + redis: + host: redis # 로컬에서 테스트 할 때는 localhost로 사용 + port: 6379 + +jwt: + token: + secret-key: Q4NSl604sgyHJj1qwEkR3ycUeR4uUAt7WJraD7EN3O9DVM4yyYuHxMEbSF4XXyYJkal13eqgB0F7Bq4H + access-token: + expire-length: 1800000 + refresh-token: + expire-length: 1209600000 \ No newline at end of file From a4ba7ba2cbe89ae28e84c81eb13bf509903a93cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=ED=98=9C=EC=97=B0?= Date: Sun, 7 Jan 2024 10:06:50 +0900 Subject: [PATCH 03/17] =?UTF-8?q?=E2=9C=A8=20feat:=20=EC=B9=B4=EC=B9=B4?= =?UTF-8?q?=EC=98=A4=20=EB=A1=9C=EA=B7=B8=EC=95=84=EC=9B=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/user/Service/OauthService.java | 29 +++++++++++++------ .../user/controller/OauthController.java | 15 ++++++++++ .../domain/user/entity/User.java | 6 ++++ .../user/repository/TokenRedisRepository.java | 15 ++++++++-- .../user/repository/UserRepository.java | 8 +++++ .../global/config/SecurityConfig.java | 8 ++--- .../global/config/redis/RedisConfig.java | 4 +-- 7 files changed, 67 insertions(+), 18 deletions(-) diff --git a/src/main/java/sopt/org/motivooServer/domain/user/Service/OauthService.java b/src/main/java/sopt/org/motivooServer/domain/user/Service/OauthService.java index 6e8ef4c1..793587a0 100644 --- a/src/main/java/sopt/org/motivooServer/domain/user/Service/OauthService.java +++ b/src/main/java/sopt/org/motivooServer/domain/user/Service/OauthService.java @@ -38,10 +38,9 @@ public class OauthService { public LoginResponse login(String providerName, OauthTokenResponse tokenResponse) { ClientRegistration provider = inMemoryRepository.findByRegistrationId(providerName); - User user = getUserProfile(providerName, tokenResponse, provider); - - String accessToken = tokenResponse.getAccessToken(); String refreshToken = jwtTokenProvider.createRefreshToken(); + User user = getUserProfile(providerName, tokenResponse, provider, refreshToken); + String accessToken = jwtTokenProvider.createAccessToken(String.valueOf(user.getId())); reissue(user.getId(), refreshToken); //refresh token 재발급 @@ -55,7 +54,8 @@ public LoginResponse login(String providerName, OauthTokenResponse tokenResponse } - private User getUserProfile(String providerName, OauthTokenResponse tokenResponse, ClientRegistration provider){ + @Transactional + public User getUserProfile(String providerName, OauthTokenResponse tokenResponse, ClientRegistration provider, String refreshToken){ Map userAttributes = getUserAttributes(provider, tokenResponse); OAuth2UserInfo oAuth2UserInfo = null; SocialPlatform socialPlatform = null; @@ -63,7 +63,7 @@ private User getUserProfile(String providerName, OauthTokenResponse tokenRespons oAuth2UserInfo = new UserProfile(userAttributes); socialPlatform = SocialPlatform.KAKAO; } else { - log.info("허용되지 않은 접근 입니다."); + log.info("허용되지 않은 접근 입니다."); //예외처리 } String providerId = oAuth2UserInfo.getProviderId(); @@ -77,11 +77,15 @@ private User getUserProfile(String providerName, OauthTokenResponse tokenRespons .socialId(providerId) .socialPlatform(socialPlatform) .socialAccessToken(tokenResponse.getAccessToken()) + .socialRefreshToken(refreshToken) .type(UserType.NONE) .deleted(Boolean.FALSE) .build(); userRepository.save(userEntity); } + else{ + userEntity.updateRefreshToken(refreshToken); + } return userEntity; } @@ -98,18 +102,25 @@ private Map getUserAttributes(ClientRegistration provider, Oauth public String reissue(Long userId, String refreshToken){ User user = userRepository.findById(userId).orElseThrow(()->new EntityNotFoundException()); - if(jwtTokenProvider.validateToken(refreshToken)){ - + if(!jwtTokenProvider.validateToken(refreshToken)){ + //예외처리 } + String reissuedToken = jwtTokenProvider.createRefreshToken(); + tokenRedisRepository.saveRefreshToken(reissuedToken, String.valueOf(userId)); tokenRedisRepository.deleteRefreshToken(refreshToken); return reissuedToken; } - public void logout(String accessToken, String refreshToken){ - //tokenRedisRepository.saveBlockedToken(accessToken); + public void logout(String accessToken){ + String userId = jwtTokenProvider.getPayload(accessToken); + String refreshToken = userRepository.findRefreshTokenById(Long.parseLong(userId)); + System.out.println(accessToken); + System.out.println(userId); + System.out.println(refreshToken); + tokenRedisRepository.saveBlockedToken(accessToken); tokenRedisRepository.deleteRefreshToken(refreshToken); } diff --git a/src/main/java/sopt/org/motivooServer/domain/user/controller/OauthController.java b/src/main/java/sopt/org/motivooServer/domain/user/controller/OauthController.java index bac442ac..fdcd0ef9 100644 --- a/src/main/java/sopt/org/motivooServer/domain/user/controller/OauthController.java +++ b/src/main/java/sopt/org/motivooServer/domain/user/controller/OauthController.java @@ -1,13 +1,18 @@ package sopt.org.motivooServer.domain.user.controller; + import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseCookie; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import sopt.org.motivooServer.domain.user.Service.OauthService; import sopt.org.motivooServer.domain.user.dto.response.LoginResponse; import sopt.org.motivooServer.domain.user.dto.response.OauthTokenResponse; + @RequiredArgsConstructor @Slf4j @RestController @@ -21,5 +26,15 @@ public ResponseEntity login(@PathVariable String provider, @Reque return ResponseEntity.ok().body(loginResponse); } + @PostMapping("/logout") + public ResponseEntity logout(@RequestHeader("Authorization") String accessToken){ + + oauthService.logout(accessToken); + + + return ResponseEntity.status(HttpStatus.OK) + .header("로그아웃","로그아웃 성공") + .build(); + } } diff --git a/src/main/java/sopt/org/motivooServer/domain/user/entity/User.java b/src/main/java/sopt/org/motivooServer/domain/user/entity/User.java index a3da0084..bc940f7b 100644 --- a/src/main/java/sopt/org/motivooServer/domain/user/entity/User.java +++ b/src/main/java/sopt/org/motivooServer/domain/user/entity/User.java @@ -49,6 +49,8 @@ public class User extends BaseTimeEntity { private String socialAccessToken; + private String socialRefreshToken; + @Enumerated(EnumType.STRING) @Column(nullable = false) private SocialPlatform socialPlatform; @@ -72,4 +74,8 @@ public void addUserMission(UserMission userMission) { userMission.setUser(this); } } + + public void updateRefreshToken(String refreshToken){ + this.socialRefreshToken = refreshToken; + } } diff --git a/src/main/java/sopt/org/motivooServer/domain/user/repository/TokenRedisRepository.java b/src/main/java/sopt/org/motivooServer/domain/user/repository/TokenRedisRepository.java index 898234cb..baed3c50 100644 --- a/src/main/java/sopt/org/motivooServer/domain/user/repository/TokenRedisRepository.java +++ b/src/main/java/sopt/org/motivooServer/domain/user/repository/TokenRedisRepository.java @@ -1,11 +1,13 @@ package sopt.org.motivooServer.domain.user.repository; +import io.jsonwebtoken.Jwts; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.core.ValueOperations; import org.springframework.stereotype.Repository; +import java.util.Date; import java.util.Optional; import java.util.concurrent.TimeUnit; @@ -41,12 +43,21 @@ public void saveRefreshToken(String refreshToken, String account) { * 어세스토큰의 남은 시간동안 저장됩니다. * * @param accessToken 어세스토큰 - * @param remainTime 남은 시간 + * remainTime 남은 시간 */ - public void saveBlockedToken(String accessToken, Long remainTime) { + public void saveBlockedToken(String accessToken) { ValueOperations valueOperations = redisTemplate.opsForValue(); String key = PREFIX_BLOCKED + accessToken; valueOperations.set(key, "empty"); + + Date expiration = Jwts.parserBuilder() + .setSigningKey(key) + .build() + .parseClaimsJws(accessToken) + .getBody() + .getExpiration(); + Long now = new Date().getTime(); + Long remainTime = expiration.getTime() - now; redisTemplate.expire(key, remainTime, TimeUnit.SECONDS); } diff --git a/src/main/java/sopt/org/motivooServer/domain/user/repository/UserRepository.java b/src/main/java/sopt/org/motivooServer/domain/user/repository/UserRepository.java index c1ad19bb..e064cb79 100644 --- a/src/main/java/sopt/org/motivooServer/domain/user/repository/UserRepository.java +++ b/src/main/java/sopt/org/motivooServer/domain/user/repository/UserRepository.java @@ -1,7 +1,11 @@ package sopt.org.motivooServer.domain.user.repository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.Repository; +import org.springframework.data.repository.query.Param; +import org.springframework.security.core.parameters.P; import sopt.org.motivooServer.domain.user.entity.User; import java.util.Optional; @@ -12,4 +16,8 @@ public interface UserRepository extends Repository { User findBySocialId(String socialId); + @Query("select u.socialRefreshToken from User u where u.id=?1") + String findRefreshTokenById(Long id); + + } diff --git a/src/main/java/sopt/org/motivooServer/global/config/SecurityConfig.java b/src/main/java/sopt/org/motivooServer/global/config/SecurityConfig.java index b46102d6..f066bea5 100644 --- a/src/main/java/sopt/org/motivooServer/global/config/SecurityConfig.java +++ b/src/main/java/sopt/org/motivooServer/global/config/SecurityConfig.java @@ -4,6 +4,7 @@ import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.web.SecurityFilterChain; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @@ -14,8 +15,7 @@ public class SecurityConfig { // TODO 인증인가 구현 이후 수정 (현재는 모두 허용해둔 상태) private static final String[] AUTH_WHITELIST = { - "/**", - "/" + "/login/**", "/logout" }; @Bean @@ -24,11 +24,9 @@ SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { .csrf().disable() .formLogin().disable() .httpBasic().disable() // Security 설정 제외 - - .authorizeHttpRequests() + .authorizeHttpRequests() .requestMatchers(AUTH_WHITELIST).permitAll() .anyRequest().authenticated() - .and().build(); // -> 스프링 빈으로 등록, 어플리케이션 실행 시 자동으로 필터에 등록된다! } diff --git a/src/main/java/sopt/org/motivooServer/global/config/redis/RedisConfig.java b/src/main/java/sopt/org/motivooServer/global/config/redis/RedisConfig.java index 7a7ff464..a43c3e35 100644 --- a/src/main/java/sopt/org/motivooServer/global/config/redis/RedisConfig.java +++ b/src/main/java/sopt/org/motivooServer/global/config/redis/RedisConfig.java @@ -12,10 +12,10 @@ @Configuration @EnableRedisRepositories public class RedisConfig { - @Value("${spring.data.redis.host}") + @Value("${data.redis.host}") private String redisHost; - @Value("${spring.data.redis.port}") + @Value("${data.redis.port}") private int redisPort; @Bean From 77f2e11050de606b5fc78fda857e96b77bdb9c89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=ED=98=9C=EC=97=B0?= Date: Mon, 8 Jan 2024 00:36:11 +0900 Subject: [PATCH 04/17] =?UTF-8?q?=E2=9C=A8=20feat:=20=EC=B9=B4=EC=B9=B4?= =?UTF-8?q?=EC=98=A4=20=EB=A1=9C=EA=B7=B8=EC=95=84=EC=9B=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MotivooServerApplication.java | 3 +- .../domain/user/Service/OauthService.java | 16 +++--- .../user/controller/OauthController.java | 9 +-- .../global/config/SecurityConfig.java | 37 +++++++++--- .../oauth/CustomAccessDeniedHandler.java | 23 ++++++++ .../CustomJwtAuthenticationEntryPoint.java | 20 +++++++ .../config/oauth/JwtAuthenticationFilter.java | 57 +++++++++++++++++++ .../global/config/oauth/JwtTokenProvider.java | 21 +++++-- .../config/oauth/JwtValidationType.java | 10 ++++ .../config/oauth/UserAuthentication.java | 14 +++++ src/main/resources/application.yml | 8 +-- 11 files changed, 186 insertions(+), 32 deletions(-) create mode 100644 src/main/java/sopt/org/motivooServer/global/config/oauth/CustomAccessDeniedHandler.java create mode 100644 src/main/java/sopt/org/motivooServer/global/config/oauth/CustomJwtAuthenticationEntryPoint.java create mode 100644 src/main/java/sopt/org/motivooServer/global/config/oauth/JwtAuthenticationFilter.java create mode 100644 src/main/java/sopt/org/motivooServer/global/config/oauth/JwtValidationType.java create mode 100644 src/main/java/sopt/org/motivooServer/global/config/oauth/UserAuthentication.java diff --git a/src/main/java/sopt/org/motivooServer/MotivooServerApplication.java b/src/main/java/sopt/org/motivooServer/MotivooServerApplication.java index 9e382634..ce514fe2 100644 --- a/src/main/java/sopt/org/motivooServer/MotivooServerApplication.java +++ b/src/main/java/sopt/org/motivooServer/MotivooServerApplication.java @@ -2,8 +2,9 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; -@SpringBootApplication +@SpringBootApplication(exclude = {SecurityAutoConfiguration.class}) public class MotivooServerApplication { public static void main(String[] args) { diff --git a/src/main/java/sopt/org/motivooServer/domain/user/Service/OauthService.java b/src/main/java/sopt/org/motivooServer/domain/user/Service/OauthService.java index 793587a0..e6b6b9e8 100644 --- a/src/main/java/sopt/org/motivooServer/domain/user/Service/OauthService.java +++ b/src/main/java/sopt/org/motivooServer/domain/user/Service/OauthService.java @@ -19,6 +19,7 @@ import sopt.org.motivooServer.domain.user.entity.User; import sopt.org.motivooServer.domain.user.entity.UserType; import sopt.org.motivooServer.domain.user.repository.UserRepository; +import sopt.org.motivooServer.global.config.oauth.JwtValidationType; import java.util.Map; @@ -102,7 +103,7 @@ private Map getUserAttributes(ClientRegistration provider, Oauth public String reissue(Long userId, String refreshToken){ User user = userRepository.findById(userId).orElseThrow(()->new EntityNotFoundException()); - if(!jwtTokenProvider.validateToken(refreshToken)){ + if(jwtTokenProvider.validateToken(refreshToken)!= JwtValidationType.VALID_JWT){ //예외처리 } @@ -115,13 +116,12 @@ public String reissue(Long userId, String refreshToken){ } public void logout(String accessToken){ - String userId = jwtTokenProvider.getPayload(accessToken); - String refreshToken = userRepository.findRefreshTokenById(Long.parseLong(userId)); - System.out.println(accessToken); - System.out.println(userId); - System.out.println(refreshToken); - tokenRedisRepository.saveBlockedToken(accessToken); - tokenRedisRepository.deleteRefreshToken(refreshToken); +// String userId = jwtTokenProvider.getPayload(accessToken); +// String refreshToken = userRepository.findRefreshTokenById(Long.parseLong(userId)); +// +// tokenRedisRepository.saveBlockedToken(accessToken); +// tokenRedisRepository.deleteRefreshToken(refreshToken); + System.out.println("ㅎㅎㅎ"); } diff --git a/src/main/java/sopt/org/motivooServer/domain/user/controller/OauthController.java b/src/main/java/sopt/org/motivooServer/domain/user/controller/OauthController.java index fdcd0ef9..4f050cca 100644 --- a/src/main/java/sopt/org/motivooServer/domain/user/controller/OauthController.java +++ b/src/main/java/sopt/org/motivooServer/domain/user/controller/OauthController.java @@ -12,6 +12,8 @@ import sopt.org.motivooServer.domain.user.dto.response.LoginResponse; import sopt.org.motivooServer.domain.user.dto.response.OauthTokenResponse; +import java.security.Principal; + @RequiredArgsConstructor @Slf4j @@ -27,13 +29,12 @@ public ResponseEntity login(@PathVariable String provider, @Reque } @PostMapping("/logout") - public ResponseEntity logout(@RequestHeader("Authorization") String accessToken){ - - oauthService.logout(accessToken); + public ResponseEntity logout(Principal principal){ + System.out.println(principal.getName()); + //oauthService.logout(accessToken); return ResponseEntity.status(HttpStatus.OK) - .header("로그아웃","로그아웃 성공") .build(); } diff --git a/src/main/java/sopt/org/motivooServer/global/config/SecurityConfig.java b/src/main/java/sopt/org/motivooServer/global/config/SecurityConfig.java index f066bea5..ddf4dfaa 100644 --- a/src/main/java/sopt/org/motivooServer/global/config/SecurityConfig.java +++ b/src/main/java/sopt/org/motivooServer/global/config/SecurityConfig.java @@ -1,33 +1,52 @@ package sopt.org.motivooServer.global.config; +import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import sopt.org.motivooServer.global.config.oauth.CustomAccessDeniedHandler; +import sopt.org.motivooServer.global.config.oauth.CustomJwtAuthenticationEntryPoint; +import sopt.org.motivooServer.global.config.oauth.JwtAuthenticationFilter; + +import static org.springframework.security.config.http.SessionCreationPolicy.STATELESS; @Configuration -@EnableWebSecurity +@EnableWebSecurity(debug = true) +@RequiredArgsConstructor public class SecurityConfig { - // TODO 인증인가 구현 이후 수정 (현재는 모두 허용해둔 상태) + private final JwtAuthenticationFilter jwtAuthenticationFilter; + private final CustomJwtAuthenticationEntryPoint customJwtAuthenticationEntryPoint; + private final CustomAccessDeniedHandler customAccessDeniedHandler; private static final String[] AUTH_WHITELIST = { - "/login/**", "/logout" + "/login/**" }; @Bean SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { return http - .csrf().disable() - .formLogin().disable() - .httpBasic().disable() // Security 설정 제외 + .csrf().disable() + .formLogin().disable() + .httpBasic().disable() + .sessionManagement() + .sessionCreationPolicy(STATELESS) + .and() + .exceptionHandling() + .authenticationEntryPoint(customJwtAuthenticationEntryPoint) + .accessDeniedHandler(customAccessDeniedHandler) + .and() .authorizeHttpRequests() - .requestMatchers(AUTH_WHITELIST).permitAll() - .anyRequest().authenticated() - .and().build(); // -> 스프링 빈으로 등록, 어플리케이션 실행 시 자동으로 필터에 등록된다! + .requestMatchers(AUTH_WHITELIST).permitAll() + .anyRequest().authenticated() + .and() + .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class) + .build(); } @Bean diff --git a/src/main/java/sopt/org/motivooServer/global/config/oauth/CustomAccessDeniedHandler.java b/src/main/java/sopt/org/motivooServer/global/config/oauth/CustomAccessDeniedHandler.java new file mode 100644 index 00000000..6c9109f1 --- /dev/null +++ b/src/main/java/sopt/org/motivooServer/global/config/oauth/CustomAccessDeniedHandler.java @@ -0,0 +1,23 @@ +package sopt.org.motivooServer.global.config.oauth; + +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 CustomAccessDeniedHandler implements AccessDeniedHandler { + @Override + public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException { + setResponse(response); + } + + private void setResponse(HttpServletResponse response) { + response.setStatus(HttpServletResponse.SC_FORBIDDEN); + } + +} \ No newline at end of file diff --git a/src/main/java/sopt/org/motivooServer/global/config/oauth/CustomJwtAuthenticationEntryPoint.java b/src/main/java/sopt/org/motivooServer/global/config/oauth/CustomJwtAuthenticationEntryPoint.java new file mode 100644 index 00000000..66c3136c --- /dev/null +++ b/src/main/java/sopt/org/motivooServer/global/config/oauth/CustomJwtAuthenticationEntryPoint.java @@ -0,0 +1,20 @@ +package sopt.org.motivooServer.global.config.oauth; + +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; + +@Component +public class CustomJwtAuthenticationEntryPoint implements AuthenticationEntryPoint { + + @Override + public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) { + setResponse(response); + } + + private void setResponse(HttpServletResponse response) { + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + } +} \ No newline at end of file diff --git a/src/main/java/sopt/org/motivooServer/global/config/oauth/JwtAuthenticationFilter.java b/src/main/java/sopt/org/motivooServer/global/config/oauth/JwtAuthenticationFilter.java new file mode 100644 index 00000000..eb966d6a --- /dev/null +++ b/src/main/java/sopt/org/motivooServer/global/config/oauth/JwtAuthenticationFilter.java @@ -0,0 +1,57 @@ +package sopt.org.motivooServer.global.config.oauth; + + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; + +@Component +@RequiredArgsConstructor +public class JwtAuthenticationFilter extends OncePerRequestFilter { + + private final JwtTokenProvider jwtTokenProvider; + + @Override + protected void doFilterInternal(@NonNull HttpServletRequest request, + @NonNull HttpServletResponse response, + @NonNull FilterChain filterChain) throws ServletException, IOException { + try { + final String token = getJwtFromRequest(request); + System.out.println(jwtTokenProvider.validateToken(token)); + if (jwtTokenProvider.validateToken(token) == JwtValidationType.VALID_JWT) { + Long memberId = Long.parseLong(jwtTokenProvider.getPayload(token)); + // authentication 객체 생성 -> principal에 유저정보를 담는다. + System.out.println("id="+memberId); + UserAuthentication authentication = new UserAuthentication(memberId.toString(), null, null); + authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + SecurityContextHolder.getContext().setAuthentication(authentication); + } + } catch (Exception exception) { + try { + exception.printStackTrace(); + } catch (Exception e) { + throw new ServletException("Authentication failed: " + exception.getMessage(), exception); + } + } + // 다음 필터로 요청 전달 + filterChain.doFilter(request, response); + } + + private String getJwtFromRequest(HttpServletRequest request) { + String bearerToken = request.getHeader("Authorization"); + if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) { + return bearerToken.substring("Bearer ".length()); + } + return null; + } +} \ No newline at end of file diff --git a/src/main/java/sopt/org/motivooServer/global/config/oauth/JwtTokenProvider.java b/src/main/java/sopt/org/motivooServer/global/config/oauth/JwtTokenProvider.java index 116e2c20..fa0771b8 100644 --- a/src/main/java/sopt/org/motivooServer/global/config/oauth/JwtTokenProvider.java +++ b/src/main/java/sopt/org/motivooServer/global/config/oauth/JwtTokenProvider.java @@ -61,18 +61,27 @@ public String getPayload(String token){ } } - public boolean validateToken(String token) { + public JwtValidationType validateToken(String token) { try { - Jws claimsJws = Jwts.parserBuilder() + final Claims claimsJws = Jwts.parserBuilder() .setSigningKey(secretKey) .build() - .parseClaimsJws(token); - return !claimsJws.getBody().getExpiration().before(new Date()); - } catch (JwtException | IllegalArgumentException exception) { - return false; + .parseClaimsJws(token) + .getBody(); + return JwtValidationType.VALID_JWT; + } catch (MalformedJwtException ex){ + return JwtValidationType.INVALID_JWT_TOKEN; + } catch (ExpiredJwtException ex) { + return JwtValidationType.EXPIRED_JWT_TOKEN; + } catch (UnsupportedJwtException ex) { + return JwtValidationType.UNSUPPORTED_JWT_TOKEN; + } catch (IllegalArgumentException ex) { + return JwtValidationType.EMPTY_JWT; } } + + } \ No newline at end of file diff --git a/src/main/java/sopt/org/motivooServer/global/config/oauth/JwtValidationType.java b/src/main/java/sopt/org/motivooServer/global/config/oauth/JwtValidationType.java new file mode 100644 index 00000000..c53b3957 --- /dev/null +++ b/src/main/java/sopt/org/motivooServer/global/config/oauth/JwtValidationType.java @@ -0,0 +1,10 @@ +package sopt.org.motivooServer.global.config.oauth; + +public enum JwtValidationType { + VALID_JWT, // 유효한 JWT + INVALID_JWT_SIGNATURE, // 유효하지 않은 서명 + INVALID_JWT_TOKEN, // 유효하지 않은 토큰 + EXPIRED_JWT_TOKEN, // 만료된 토큰 + UNSUPPORTED_JWT_TOKEN, // 지원하지 않는 형식의 토큰 + EMPTY_JWT // 빈 JWT +} \ No newline at end of file diff --git a/src/main/java/sopt/org/motivooServer/global/config/oauth/UserAuthentication.java b/src/main/java/sopt/org/motivooServer/global/config/oauth/UserAuthentication.java new file mode 100644 index 00000000..cfe4140f --- /dev/null +++ b/src/main/java/sopt/org/motivooServer/global/config/oauth/UserAuthentication.java @@ -0,0 +1,14 @@ +package sopt.org.motivooServer.global.config.oauth; + +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.GrantedAuthority; + +import java.util.Collection; + +public class UserAuthentication extends UsernamePasswordAuthenticationToken { + + // 사용자 인증 객체 생성 + public UserAuthentication(Object principal, Object credentials, Collection authorities) { + super(principal, credentials, authorities); + } +} \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index ce4d1484..c5cac6d5 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -19,10 +19,10 @@ spring: token-uri: https://kauth.kakao.com/oauth/token user-info-uri: https://kapi.kakao.com/v1/oidc/userinfo user-name-attribute: id - data: - redis: - host: redis # 로컬에서 테스트 할 때는 localhost로 사용 - port: 6379 +data: + redis: + host: localhost # 로컬에서 테스트 할 때는 localhost로 사용 + port: 6379 jwt: token: From 2979ee91061fd2e3af47081b7cb0a20bebf2578c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=ED=98=9C=EC=97=B0?= Date: Mon, 8 Jan 2024 11:15:52 +0900 Subject: [PATCH 05/17] =?UTF-8?q?=F0=9F=90=9B=20bugfix=20:=20logout=20cont?= =?UTF-8?q?roller=20mapping=20=EB=AC=B8=EC=A0=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../user/controller/OauthController.java | 27 ++++----- .../domain/user/dto/request/UserProfile.java | 1 - .../domain/user/entity/SocialPlatform.java | 2 - .../user/exception/UserExceptionType.java | 12 +++- .../user/repository/TokenRedisRepository.java | 56 +++++++++---------- .../user/repository/UserRepository.java | 2 +- .../{Service => service}/OauthService.java | 30 ++++++---- .../global/advice/GlobalExceptionHandler.java | 8 ++- .../global/config/SecurityConfig.java | 11 +++- .../config/oauth/JwtAuthenticationFilter.java | 2 - .../global/config/oauth/JwtTokenProvider.java | 20 ++++--- .../global/response/SuccessType.java | 7 ++- 12 files changed, 98 insertions(+), 80 deletions(-) rename src/main/java/sopt/org/motivooServer/domain/user/{Service => service}/OauthService.java (82%) diff --git a/src/main/java/sopt/org/motivooServer/domain/user/controller/OauthController.java b/src/main/java/sopt/org/motivooServer/domain/user/controller/OauthController.java index 4f050cca..830809bb 100644 --- a/src/main/java/sopt/org/motivooServer/domain/user/controller/OauthController.java +++ b/src/main/java/sopt/org/motivooServer/domain/user/controller/OauthController.java @@ -3,16 +3,14 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseCookie; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; -import sopt.org.motivooServer.domain.user.Service.OauthService; import sopt.org.motivooServer.domain.user.dto.response.LoginResponse; import sopt.org.motivooServer.domain.user.dto.response.OauthTokenResponse; - -import java.security.Principal; +import sopt.org.motivooServer.domain.user.service.OauthService; +import sopt.org.motivooServer.global.response.ApiResponse; +import static sopt.org.motivooServer.global.response.SuccessType.LOGIN_SUCCESS; +import static sopt.org.motivooServer.global.response.SuccessType.LOGOUT_SUCCESS; @RequiredArgsConstructor @@ -21,21 +19,18 @@ public class OauthController { private final OauthService oauthService; - @GetMapping("/login/oauth/{provider}") - public ResponseEntity login(@PathVariable String provider, @RequestBody OauthTokenResponse tokenResponse){ + @GetMapping("/oauth/login/{provider}") + public ResponseEntity> login(@PathVariable String provider, @RequestBody OauthTokenResponse tokenResponse){ LoginResponse loginResponse = oauthService.login(provider, tokenResponse); - return ResponseEntity.ok().body(loginResponse); + return ApiResponse.success(LOGIN_SUCCESS, loginResponse); } - @PostMapping("/logout") - public ResponseEntity logout(Principal principal){ - System.out.println(principal.getName()); - - //oauthService.logout(accessToken); + @PostMapping("/oauth/logout") + public ResponseEntity> logout(@RequestHeader("Authorization") String accessToken){ + oauthService.logout(accessToken); - return ResponseEntity.status(HttpStatus.OK) - .build(); + return ApiResponse.success(LOGOUT_SUCCESS); } } diff --git a/src/main/java/sopt/org/motivooServer/domain/user/dto/request/UserProfile.java b/src/main/java/sopt/org/motivooServer/domain/user/dto/request/UserProfile.java index e5ce120f..1f901518 100644 --- a/src/main/java/sopt/org/motivooServer/domain/user/dto/request/UserProfile.java +++ b/src/main/java/sopt/org/motivooServer/domain/user/dto/request/UserProfile.java @@ -23,7 +23,6 @@ public String getProvider() { return "kakao"; } - @Override public String getNickName() { return String.valueOf(attributes.get("nickname")); diff --git a/src/main/java/sopt/org/motivooServer/domain/user/entity/SocialPlatform.java b/src/main/java/sopt/org/motivooServer/domain/user/entity/SocialPlatform.java index 1f85ab0a..5559a410 100644 --- a/src/main/java/sopt/org/motivooServer/domain/user/entity/SocialPlatform.java +++ b/src/main/java/sopt/org/motivooServer/domain/user/entity/SocialPlatform.java @@ -3,11 +3,9 @@ import static sopt.org.motivooServer.domain.user.exception.UserExceptionType.*; import java.util.Arrays; -import java.util.Map; import lombok.AccessLevel; import lombok.RequiredArgsConstructor; -import sopt.org.motivooServer.domain.user.dto.request.UserProfile; import sopt.org.motivooServer.domain.user.exception.UserException; @RequiredArgsConstructor(access = AccessLevel.PRIVATE) diff --git a/src/main/java/sopt/org/motivooServer/domain/user/exception/UserExceptionType.java b/src/main/java/sopt/org/motivooServer/domain/user/exception/UserExceptionType.java index 07255bf3..87cbcc3d 100644 --- a/src/main/java/sopt/org/motivooServer/domain/user/exception/UserExceptionType.java +++ b/src/main/java/sopt/org/motivooServer/domain/user/exception/UserExceptionType.java @@ -13,14 +13,24 @@ public enum UserExceptionType implements BusinessExceptionType { * 400 Bad Request */ INVALID_USER_TYPE(HttpStatus.BAD_REQUEST, "유효하지 않은 회원 유형입니다."), + INVALID_SOCIAL_PLATFORM(HttpStatus.BAD_REQUEST, "유효하지 않은 소셜 플랫폼입니다."), + /** + * 400 Not Found + */ + TOKEN_NOT_FOUND(HttpStatus.NOT_FOUND, "토큰이 없습니다."), + + /** + * 401 Not Found + */ + TOKEN_EXPIRED(HttpStatus.NOT_FOUND, "토큰이 유효하지 않습니다."), + /** * 404 Not Found */ USER_NOT_FOUND(HttpStatus.NOT_FOUND, "존재하지 않는 유저입니다.") - ; private final HttpStatus status; diff --git a/src/main/java/sopt/org/motivooServer/domain/user/repository/TokenRedisRepository.java b/src/main/java/sopt/org/motivooServer/domain/user/repository/TokenRedisRepository.java index baed3c50..de51e03e 100644 --- a/src/main/java/sopt/org/motivooServer/domain/user/repository/TokenRedisRepository.java +++ b/src/main/java/sopt/org/motivooServer/domain/user/repository/TokenRedisRepository.java @@ -1,12 +1,16 @@ package sopt.org.motivooServer.domain.user.repository; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.ExpiredJwtException; import io.jsonwebtoken.Jwts; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.core.ValueOperations; +import org.springframework.security.oauth2.jwt.JwtException; import org.springframework.stereotype.Repository; +import java.util.Base64; import java.util.Date; import java.util.Optional; import java.util.concurrent.TimeUnit; @@ -21,14 +25,9 @@ public class TokenRedisRepository { private final static String PREFIX_REFRESH = "REFRESH:"; private final static String PREFIX_BLOCKED = "BLOCKED:"; - /** - * 리프레시토큰을 저장합니다. - * 'PREFIX:[리프레시토큰]'을 key로 사용합니다. - * 회원의 계정을 value로 설정합니다. - * - * @param refreshToken 리프레시토큰 - * @param account 회원의 계정 - */ + @Value("${jwt.token.secret-key}") + private String secretKey; + public void saveRefreshToken(String refreshToken, String account) { ValueOperations valueOperations = redisTemplate.opsForValue(); String key = PREFIX_REFRESH + refreshToken; @@ -36,37 +35,36 @@ public void saveRefreshToken(String refreshToken, String account) { redisTemplate.expire(key, refreshTokenValidityInMilliseconds, TimeUnit.SECONDS); } - /** - * Block된 어세스토큰을 저장합니다. - * 'PREFIX:[어세스토큰]'을 key로 사용합니다. - * "empty"문자열을 value로 설정합니다. - * 어세스토큰의 남은 시간동안 저장됩니다. - * - * @param accessToken 어세스토큰 - * remainTime 남은 시간 - */ + public void saveBlockedToken(String accessToken) { ValueOperations valueOperations = redisTemplate.opsForValue(); String key = PREFIX_BLOCKED + accessToken; valueOperations.set(key, "empty"); - Date expiration = Jwts.parserBuilder() - .setSigningKey(key) - .build() - .parseClaimsJws(accessToken) - .getBody() - .getExpiration(); + Date expiration = null; + + try { + accessToken = accessToken.replaceAll("\\s+", ""); + accessToken = accessToken.replace("Bearer", ""); + Claims claims = Jwts.parserBuilder() + .setSigningKey(secretKey) + .build() + .parseClaimsJws(accessToken) + .getBody(); + + expiration = claims + .getExpiration(); + + } catch (JwtException e){ + throw new RuntimeException("유효하지 않은 토큰 입니다"); + } + + Long now = new Date().getTime(); Long remainTime = expiration.getTime() - now; redisTemplate.expire(key, remainTime, TimeUnit.SECONDS); } - /** - * 리프레시토큰에 저장된 회원의 계정을 찾습니다. - * - * @param refreshToken 리프레시토큰 - * @return 회원의 계정 - */ public Optional findByRefreshToken(String refreshToken) { ValueOperations valueOperations = redisTemplate.opsForValue(); String key = PREFIX_REFRESH + refreshToken; diff --git a/src/main/java/sopt/org/motivooServer/domain/user/repository/UserRepository.java b/src/main/java/sopt/org/motivooServer/domain/user/repository/UserRepository.java index e064cb79..5803a8c5 100644 --- a/src/main/java/sopt/org/motivooServer/domain/user/repository/UserRepository.java +++ b/src/main/java/sopt/org/motivooServer/domain/user/repository/UserRepository.java @@ -14,7 +14,7 @@ public interface UserRepository extends Repository { void save(User user); Optional findById(Long id); - User findBySocialId(String socialId); + Optional findBySocialId(String socialId); @Query("select u.socialRefreshToken from User u where u.id=?1") String findRefreshTokenById(Long id); diff --git a/src/main/java/sopt/org/motivooServer/domain/user/Service/OauthService.java b/src/main/java/sopt/org/motivooServer/domain/user/service/OauthService.java similarity index 82% rename from src/main/java/sopt/org/motivooServer/domain/user/Service/OauthService.java rename to src/main/java/sopt/org/motivooServer/domain/user/service/OauthService.java index e6b6b9e8..97b35e5c 100644 --- a/src/main/java/sopt/org/motivooServer/domain/user/Service/OauthService.java +++ b/src/main/java/sopt/org/motivooServer/domain/user/service/OauthService.java @@ -1,14 +1,16 @@ -package sopt.org.motivooServer.domain.user.Service; +package sopt.org.motivooServer.domain.user.service; -import jakarta.persistence.EntityNotFoundException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.core.ParameterizedTypeReference; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.reactive.function.client.WebClient; +import sopt.org.motivooServer.domain.user.exception.UserException; +import sopt.org.motivooServer.domain.user.exception.UserExceptionType; import sopt.org.motivooServer.domain.user.repository.TokenRedisRepository; import sopt.org.motivooServer.global.config.oauth.JwtTokenProvider; import sopt.org.motivooServer.domain.user.dto.oauth.OAuth2UserInfo; @@ -64,13 +66,15 @@ public User getUserProfile(String providerName, OauthTokenResponse tokenResponse oAuth2UserInfo = new UserProfile(userAttributes); socialPlatform = SocialPlatform.KAKAO; } else { - log.info("허용되지 않은 접근 입니다."); //예외처리 + throw new UserException(UserExceptionType.INVALID_SOCIAL_PLATFORM); } String providerId = oAuth2UserInfo.getProviderId(); String nickName = oAuth2UserInfo.getNickName(); - User userEntity = userRepository.findBySocialId(providerId); + User userEntity = userRepository.findBySocialId(providerId) + .orElseThrow( + () -> new UserException(UserExceptionType.USER_NOT_FOUND)); if(userEntity == null){ userEntity = User.builder() @@ -100,11 +104,14 @@ private Map getUserAttributes(ClientRegistration provider, Oauth .block(); } + @Transactional public String reissue(Long userId, String refreshToken){ - User user = userRepository.findById(userId).orElseThrow(()->new EntityNotFoundException()); + User user = userRepository.findById(userId) + .orElseThrow( + () -> new UserException(UserExceptionType.USER_NOT_FOUND)); if(jwtTokenProvider.validateToken(refreshToken)!= JwtValidationType.VALID_JWT){ - //예외처리 + throw new UserException(UserExceptionType.TOKEN_EXPIRED); } String reissuedToken = jwtTokenProvider.createRefreshToken(); @@ -115,14 +122,13 @@ public String reissue(Long userId, String refreshToken){ return reissuedToken; } + @Transactional public void logout(String accessToken){ -// String userId = jwtTokenProvider.getPayload(accessToken); -// String refreshToken = userRepository.findRefreshTokenById(Long.parseLong(userId)); -// -// tokenRedisRepository.saveBlockedToken(accessToken); -// tokenRedisRepository.deleteRefreshToken(refreshToken); - System.out.println("ㅎㅎㅎ"); + String userId = SecurityContextHolder.getContext().getAuthentication().getName(); + String refreshToken = userRepository.findRefreshTokenById(Long.parseLong(userId)); + tokenRedisRepository.saveBlockedToken(accessToken); + tokenRedisRepository.deleteRefreshToken(refreshToken); } } diff --git a/src/main/java/sopt/org/motivooServer/global/advice/GlobalExceptionHandler.java b/src/main/java/sopt/org/motivooServer/global/advice/GlobalExceptionHandler.java index e327b330..f7ea3cc6 100644 --- a/src/main/java/sopt/org/motivooServer/global/advice/GlobalExceptionHandler.java +++ b/src/main/java/sopt/org/motivooServer/global/advice/GlobalExceptionHandler.java @@ -18,6 +18,12 @@ public class GlobalExceptionHandler { /** * 500 Internal Server Error */ + @ExceptionHandler(Exception.class) + public ResponseEntity handleException(final Exception e) { + log.error("Error 발생:{}", e.getStackTrace()); + log.error("Error 발생메시지:{}", e.getMessage()); + return ResponseEntity.internalServerError().body(e); + } /** @@ -29,6 +35,6 @@ public ResponseEntity handleBusinessException(final BusinessExcep log.error("🚨🚨🚨 BusinessException occured: {} 🚨🚨🚨", e.getMessage()); return ResponseEntity.status(e.getHttpStatus()) - .body(ErrorResponse.of(e.getExceptionType())); + .body(ErrorResponse.of(e.getExceptionType())); } } diff --git a/src/main/java/sopt/org/motivooServer/global/config/SecurityConfig.java b/src/main/java/sopt/org/motivooServer/global/config/SecurityConfig.java index ddf4dfaa..b68cece2 100644 --- a/src/main/java/sopt/org/motivooServer/global/config/SecurityConfig.java +++ b/src/main/java/sopt/org/motivooServer/global/config/SecurityConfig.java @@ -1,5 +1,6 @@ package sopt.org.motivooServer.global.config; +import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -14,10 +15,11 @@ import sopt.org.motivooServer.global.config.oauth.CustomJwtAuthenticationEntryPoint; import sopt.org.motivooServer.global.config.oauth.JwtAuthenticationFilter; +import static javax.management.Query.and; import static org.springframework.security.config.http.SessionCreationPolicy.STATELESS; @Configuration -@EnableWebSecurity(debug = true) +@EnableWebSecurity @RequiredArgsConstructor public class SecurityConfig { @@ -25,7 +27,7 @@ public class SecurityConfig { private final CustomJwtAuthenticationEntryPoint customJwtAuthenticationEntryPoint; private final CustomAccessDeniedHandler customAccessDeniedHandler; private static final String[] AUTH_WHITELIST = { - "/login/**" + "/oauth/**" }; @Bean @@ -44,7 +46,10 @@ SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { .authorizeHttpRequests() .requestMatchers(AUTH_WHITELIST).permitAll() .anyRequest().authenticated() - .and() + .and().logout(logout -> logout.permitAll() + .logoutSuccessHandler((request, response, authentication) -> { + response.setStatus(HttpServletResponse.SC_OK); + })) .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class) .build(); } diff --git a/src/main/java/sopt/org/motivooServer/global/config/oauth/JwtAuthenticationFilter.java b/src/main/java/sopt/org/motivooServer/global/config/oauth/JwtAuthenticationFilter.java index eb966d6a..69331310 100644 --- a/src/main/java/sopt/org/motivooServer/global/config/oauth/JwtAuthenticationFilter.java +++ b/src/main/java/sopt/org/motivooServer/global/config/oauth/JwtAuthenticationFilter.java @@ -27,11 +27,9 @@ protected void doFilterInternal(@NonNull HttpServletRequest request, @NonNull FilterChain filterChain) throws ServletException, IOException { try { final String token = getJwtFromRequest(request); - System.out.println(jwtTokenProvider.validateToken(token)); if (jwtTokenProvider.validateToken(token) == JwtValidationType.VALID_JWT) { Long memberId = Long.parseLong(jwtTokenProvider.getPayload(token)); // authentication 객체 생성 -> principal에 유저정보를 담는다. - System.out.println("id="+memberId); UserAuthentication authentication = new UserAuthentication(memberId.toString(), null, null); authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authentication); diff --git a/src/main/java/sopt/org/motivooServer/global/config/oauth/JwtTokenProvider.java b/src/main/java/sopt/org/motivooServer/global/config/oauth/JwtTokenProvider.java index fa0771b8..03371042 100644 --- a/src/main/java/sopt/org/motivooServer/global/config/oauth/JwtTokenProvider.java +++ b/src/main/java/sopt/org/motivooServer/global/config/oauth/JwtTokenProvider.java @@ -1,14 +1,11 @@ package sopt.org.motivooServer.global.config.oauth; -import ch.qos.logback.core.subst.Token; import io.jsonwebtoken.*; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.security.oauth2.jwt.JwtException; import org.springframework.stereotype.Component; -import java.nio.charset.StandardCharsets; -import java.util.Date; -import java.util.Random; +import java.util.*; @Component @Slf4j @@ -30,12 +27,13 @@ public String createAccessToken(String payload) { public String createRefreshToken() { byte[] array = new byte[7]; new Random().nextBytes(array); - String generatedString = new String(array, StandardCharsets.UTF_8); + String generatedString = Base64.getEncoder().encodeToString(array); return createToken(generatedString, refreshTokenValidityInMilliseconds); } public String createToken(String payload, long expireLength) { - Claims claims = Jwts.claims().setSubject(payload); + Map claims = new HashMap<>(); + claims.put("payload", payload); Date now = new Date(); Date validity = new Date(now.getTime() + expireLength); return Jwts.builder() @@ -48,12 +46,16 @@ public String createToken(String payload, long expireLength) { public String getPayload(String token){ try { - return Jwts.parserBuilder() + Claims claims = Jwts.parserBuilder() .setSigningKey(secretKey) .build() .parseClaimsJws(token) - .getBody() - .getSubject(); + .getBody(); + + Object subject = claims.get("payload"); + + return String.valueOf(subject); + } catch (ExpiredJwtException e) { return e.getClaims().getSubject(); } catch (JwtException e){ diff --git a/src/main/java/sopt/org/motivooServer/global/response/SuccessType.java b/src/main/java/sopt/org/motivooServer/global/response/SuccessType.java index 20df00c2..bc23b8a1 100644 --- a/src/main/java/sopt/org/motivooServer/global/response/SuccessType.java +++ b/src/main/java/sopt/org/motivooServer/global/response/SuccessType.java @@ -13,10 +13,11 @@ public enum SuccessType { /** * 200 Ok */ - HEALTH_CHECK_SUCCESS(HttpStatus.OK, "헬스체크용 API 호출에 성공했습니다.") - - + HEALTH_CHECK_SUCCESS(HttpStatus.OK, "헬스체크용 API 호출에 성공했습니다."), + //소셜 로그인 + LOGIN_SUCCESS(HttpStatus.OK, "로그인에 성공했습니다."), + LOGOUT_SUCCESS(HttpStatus.OK, "로그아웃에 성공했습니다.") ; private final HttpStatus httpStatus; From 3f8cf7b86cb51eb613871d2021bda4b0b1fc5230 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=ED=98=9C=EC=97=B0?= Date: Mon, 8 Jan 2024 20:52:58 +0900 Subject: [PATCH 06/17] =?UTF-8?q?=20bugfix=20:=20=EC=B6=A9=EB=8F=8C=20?= =?UTF-8?q?=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/user/exception/UserExceptionType.java | 1 - .../org/motivooServer/domain/user/service/OauthService.java | 4 +--- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/main/java/sopt/org/motivooServer/domain/user/exception/UserExceptionType.java b/src/main/java/sopt/org/motivooServer/domain/user/exception/UserExceptionType.java index 87cbcc3d..94f15356 100644 --- a/src/main/java/sopt/org/motivooServer/domain/user/exception/UserExceptionType.java +++ b/src/main/java/sopt/org/motivooServer/domain/user/exception/UserExceptionType.java @@ -29,7 +29,6 @@ public enum UserExceptionType implements BusinessExceptionType { /** * 404 Not Found */ - USER_NOT_FOUND(HttpStatus.NOT_FOUND, "존재하지 않는 유저입니다.") ; diff --git a/src/main/java/sopt/org/motivooServer/domain/user/service/OauthService.java b/src/main/java/sopt/org/motivooServer/domain/user/service/OauthService.java index 9c501366..7a6a4095 100644 --- a/src/main/java/sopt/org/motivooServer/domain/user/service/OauthService.java +++ b/src/main/java/sopt/org/motivooServer/domain/user/service/OauthService.java @@ -26,8 +26,6 @@ import sopt.org.motivooServer.domain.user.repository.UserRepository; import sopt.org.motivooServer.global.config.oauth.JwtValidationType; -import java.nio.charset.StandardCharsets; -import java.util.Collections; import java.util.Map; @Service @@ -113,7 +111,7 @@ private Map getUserAttributes(ClientRegistration provider, Oauth public String reissue(Long userId, String refreshToken){ User user = userRepository.findById(userId) .orElseThrow( - () -> new UserException(UserExceptionType.USER_NOT_FOUND)); + () -> new UserException(UserExceptionType.INVALID_USER_TYPE)); if(jwtTokenProvider.validateToken(refreshToken)!= JwtValidationType.VALID_JWT){ throw new UserException(UserExceptionType.TOKEN_EXPIRED); From 0ff905426bb332af01ecce2284194a2a10263731 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=ED=98=9C=EC=97=B0?= Date: Mon, 8 Jan 2024 22:18:29 +0900 Subject: [PATCH 07/17] =?UTF-8?q?=20bugfix=20:=20=EC=B6=A9=EB=8F=8C=20?= =?UTF-8?q?=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 31 +++++++++++++++-- .../global/config/SecurityConfig.java | 2 +- src/main/resources/application-dev.yml | 33 +++++++++++++++++++ src/main/resources/application.yml | 33 ------------------- 4 files changed, 62 insertions(+), 37 deletions(-) delete mode 100644 src/main/resources/application.yml diff --git a/build.gradle b/build.gradle index 1a8d8680..6cf0cf52 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,32 @@ +buildscript { + ext { + restdocsApiSpecVersion = '0.18.2' + } +} + plugins { id 'java' id 'org.springframework.boot' version '3.0.11' id 'io.spring.dependency-management' version '1.1.4' + + id 'com.epages.restdocs-api-spec' version "${restdocsApiSpecVersion}" + id 'org.hidetake.swagger.generator' version '2.18.2' +} + + +swaggerSources { + sample { + setInputFile(file("${project.buildDir}/api-spec/openapi3.yaml")) + } +} + +// openapi3 스펙 생성 시 설정 정보 +openapi3 { + server = "http://localhost:8080" + title = "모티부 API 명세서" + description = "Motivoo REST Docs with SwaggerUI" + version = "v0.0.1" + format = "yaml" } group = 'sopt.org' @@ -41,7 +66,7 @@ dependencies { // Security implementation 'org.springframework.boot:spring-boot-starter-security' - testImplementation 'org.springframework.security:spring-segicurity-test' + testImplementation 'org.springframework.security:spring-security-test' // Web implementation 'org.springframework.boot:spring-boot-starter-web' @@ -87,6 +112,7 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-redis' testImplementation 'org.springframework.boot:spring-boot-starter-test' + } tasks.named('test') { @@ -117,5 +143,4 @@ openapi3 { format = "json" outputFileNamePrefix = "open-api-3.0.1" outputDirectory = 'build/resources/main/static/docs' -} - +} \ No newline at end of file diff --git a/src/main/java/sopt/org/motivooServer/global/config/SecurityConfig.java b/src/main/java/sopt/org/motivooServer/global/config/SecurityConfig.java index b68cece2..458454f0 100644 --- a/src/main/java/sopt/org/motivooServer/global/config/SecurityConfig.java +++ b/src/main/java/sopt/org/motivooServer/global/config/SecurityConfig.java @@ -27,7 +27,7 @@ public class SecurityConfig { private final CustomJwtAuthenticationEntryPoint customJwtAuthenticationEntryPoint; private final CustomAccessDeniedHandler customAccessDeniedHandler; private static final String[] AUTH_WHITELIST = { - "/oauth/**" + "/oauth/**", "/api/**" }; @Bean diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index 643ef03c..fe672981 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -16,6 +16,39 @@ spring: hibernate: ddl-auto: update + security: + oauth2: + client: + registration: + kakao: + client-id: ${kakao.client.id} + redirect-uri: http://localhost:8080/kakao/callback + client-authentication-method: POST + client-secret: ${kakao.client.secret} + authorization-grant-type: authorization_code + scope: + - sub + - nickname + client_name: kakao + provider: + kakao: + authorization-uri: https://kauth.kakao.com/oauth/authorize + token-uri: https://kauth.kakao.com/oauth/token + user-info-uri: https://kapi.kakao.com/v1/oidc/userinfo + user-name-attribute: id +data: + redis: + host: localhost # 로컬에서 테스트 할 때는 localhost로 사용 + port: 6379 + +jwt: + token: + secret-key: Q4NSl604sgyHJj1qwEkR3ycUeR4uUAt7WJraD7EN3O9DVM4yyYuHxMEbSF4XXyYJkal13eqgB0F7Bq4H + access-token: + expire-length: 1800000 + refresh-token: + expire-length: 1209600000 + servlet: multipart: enabled: true diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml deleted file mode 100644 index c5cac6d5..00000000 --- a/src/main/resources/application.yml +++ /dev/null @@ -1,33 +0,0 @@ -spring: - security: - oauth2: - client: - registration: - kakao: - client-id: ${kakao.client.id} - redirect-uri: http://localhost:8080/kakao/callback - client-authentication-method: POST - client-secret: ${kakao.client.secret} - authorization-grant-type: authorization_code - scope: - - sub - - nickname - client_name: kakao - provider: - kakao: - authorization-uri: https://kauth.kakao.com/oauth/authorize - token-uri: https://kauth.kakao.com/oauth/token - user-info-uri: https://kapi.kakao.com/v1/oidc/userinfo - user-name-attribute: id -data: - redis: - host: localhost # 로컬에서 테스트 할 때는 localhost로 사용 - port: 6379 - -jwt: - token: - secret-key: Q4NSl604sgyHJj1qwEkR3ycUeR4uUAt7WJraD7EN3O9DVM4yyYuHxMEbSF4XXyYJkal13eqgB0F7Bq4H - access-token: - expire-length: 1800000 - refresh-token: - expire-length: 1209600000 \ No newline at end of file From e0c79c89ddf2cfeb4c8f5a79fc595bb2dd195216 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=ED=98=9C=EC=97=B0?= Date: Tue, 9 Jan 2024 17:04:20 +0900 Subject: [PATCH 08/17] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor:=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=EB=A6=AC=EB=B7=B0=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/controller/OauthController.java | 42 ++++++ .../dto/redis/RefreshToken.java | 2 +- .../dto/request/OauthTokenRequest.java} | 6 +- .../auth/dto/request/RefreshRequest.java | 11 ++ .../dto/response/LoginResponse.java | 2 +- .../auth/dto/response/OauthTokenResponse.java | 22 ++++ .../repository/TokenRedisRepository.java | 18 +-- .../service}/OAuth2UserInfo.java | 2 +- .../{user => auth}/service/OauthService.java | 124 ++++++++++-------- .../user/controller/OauthController.java | 36 ----- .../domain/user/dto/request/UserProfile.java | 5 +- .../domain/user/entity/SocialPlatform.java | 8 +- .../domain/user/entity/User.java | 3 +- .../domain/user/entity/UserType.java | 1 - .../user/exception/UserExceptionType.java | 1 + .../user/repository/UserRepository.java | 8 -- .../global/config/SecurityConfig.java | 9 +- .../CustomAccessDeniedHandler.java | 2 +- .../CustomJwtAuthenticationEntryPoint.java | 3 +- .../JwtAuthenticationFilter.java | 24 ++-- .../{oauth => auth}/JwtTokenProvider.java | 31 +++-- .../{oauth => auth}/JwtValidationType.java | 2 +- ...th2ClientRegistrationRepositoryConfig.java | 2 +- .../{oauth => auth}/UserAuthentication.java | 3 +- .../global/response/SuccessType.java | 3 +- 25 files changed, 196 insertions(+), 174 deletions(-) create mode 100644 src/main/java/sopt/org/motivooServer/domain/auth/controller/OauthController.java rename src/main/java/sopt/org/motivooServer/domain/{user => auth}/dto/redis/RefreshToken.java (78%) rename src/main/java/sopt/org/motivooServer/domain/{user/dto/response/OauthTokenResponse.java => auth/dto/request/OauthTokenRequest.java} (70%) create mode 100644 src/main/java/sopt/org/motivooServer/domain/auth/dto/request/RefreshRequest.java rename src/main/java/sopt/org/motivooServer/domain/{user => auth}/dto/response/LoginResponse.java (91%) create mode 100644 src/main/java/sopt/org/motivooServer/domain/auth/dto/response/OauthTokenResponse.java rename src/main/java/sopt/org/motivooServer/domain/{user => auth}/repository/TokenRedisRepository.java (87%) rename src/main/java/sopt/org/motivooServer/domain/{user/dto/oauth => auth/service}/OAuth2UserInfo.java (68%) rename src/main/java/sopt/org/motivooServer/domain/{user => auth}/service/OauthService.java (50%) delete mode 100644 src/main/java/sopt/org/motivooServer/domain/user/controller/OauthController.java rename src/main/java/sopt/org/motivooServer/global/config/{oauth => auth}/CustomAccessDeniedHandler.java (93%) rename src/main/java/sopt/org/motivooServer/global/config/{oauth => auth}/CustomJwtAuthenticationEntryPoint.java (92%) rename src/main/java/sopt/org/motivooServer/global/config/{oauth => auth}/JwtAuthenticationFilter.java (64%) rename src/main/java/sopt/org/motivooServer/global/config/{oauth => auth}/JwtTokenProvider.java (75%) rename src/main/java/sopt/org/motivooServer/global/config/{oauth => auth}/JwtValidationType.java (87%) rename src/main/java/sopt/org/motivooServer/global/config/{oauth => auth}/OAuth2ClientRegistrationRepositoryConfig.java (97%) rename src/main/java/sopt/org/motivooServer/global/config/{oauth => auth}/UserAuthentication.java (89%) diff --git a/src/main/java/sopt/org/motivooServer/domain/auth/controller/OauthController.java b/src/main/java/sopt/org/motivooServer/domain/auth/controller/OauthController.java new file mode 100644 index 00000000..8386a361 --- /dev/null +++ b/src/main/java/sopt/org/motivooServer/domain/auth/controller/OauthController.java @@ -0,0 +1,42 @@ +package sopt.org.motivooServer.domain.auth.controller; + +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import sopt.org.motivooServer.domain.auth.dto.request.RefreshRequest; +import sopt.org.motivooServer.domain.auth.dto.response.LoginResponse; +import sopt.org.motivooServer.domain.auth.dto.request.OauthTokenRequest; +import sopt.org.motivooServer.domain.auth.dto.response.OauthTokenResponse; +import sopt.org.motivooServer.domain.auth.service.OauthService; +import sopt.org.motivooServer.global.response.ApiResponse; +import static sopt.org.motivooServer.global.response.SuccessType.*; + + +@RequiredArgsConstructor +@RestController +public class OauthController { + private final OauthService oauthService; + + @PostMapping("/oauth/login") + public ResponseEntity> login(@RequestBody OauthTokenRequest tokenRequest) { + LoginResponse loginResponse = oauthService.login(tokenRequest); + + return ApiResponse.success(LOGIN_SUCCESS, loginResponse); + } + + @PostMapping("/oauth/reissue") + public ResponseEntity> reissue( + @RequestHeader("Authorization") String refreshToken, + @RequestBody final RefreshRequest request) { + + return ApiResponse.success(REISSUE_SUCCESS, oauthService.reissue(request.getUserId(), refreshToken)); + } + + @PostMapping("/oauth/logout") + public ResponseEntity> logout(@RequestHeader("Authorization") String accessToken) { + oauthService.logout(accessToken); + + return ApiResponse.success(LOGOUT_SUCCESS); + } + +} diff --git a/src/main/java/sopt/org/motivooServer/domain/user/dto/redis/RefreshToken.java b/src/main/java/sopt/org/motivooServer/domain/auth/dto/redis/RefreshToken.java similarity index 78% rename from src/main/java/sopt/org/motivooServer/domain/user/dto/redis/RefreshToken.java rename to src/main/java/sopt/org/motivooServer/domain/auth/dto/redis/RefreshToken.java index 599277ec..7ab0b1e5 100644 --- a/src/main/java/sopt/org/motivooServer/domain/user/dto/redis/RefreshToken.java +++ b/src/main/java/sopt/org/motivooServer/domain/auth/dto/redis/RefreshToken.java @@ -1,4 +1,4 @@ -package sopt.org.motivooServer.domain.user.dto.redis; +package sopt.org.motivooServer.domain.auth.dto.redis; import lombok.AllArgsConstructor; import lombok.Builder; diff --git a/src/main/java/sopt/org/motivooServer/domain/user/dto/response/OauthTokenResponse.java b/src/main/java/sopt/org/motivooServer/domain/auth/dto/request/OauthTokenRequest.java similarity index 70% rename from src/main/java/sopt/org/motivooServer/domain/user/dto/response/OauthTokenResponse.java rename to src/main/java/sopt/org/motivooServer/domain/auth/dto/request/OauthTokenRequest.java index 1a43ae77..3c5a3200 100644 --- a/src/main/java/sopt/org/motivooServer/domain/user/dto/response/OauthTokenResponse.java +++ b/src/main/java/sopt/org/motivooServer/domain/auth/dto/request/OauthTokenRequest.java @@ -1,4 +1,4 @@ -package sopt.org.motivooServer.domain.user.dto.response; +package sopt.org.motivooServer.domain.auth.dto.request; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Builder; @@ -7,7 +7,7 @@ @Getter @NoArgsConstructor -public class OauthTokenResponse { +public class OauthTokenRequest { @JsonProperty("access_token") private String accessToken; @@ -15,7 +15,7 @@ public class OauthTokenResponse { private String tokenType; @Builder - public OauthTokenResponse(String accessToken, String tokenType){ + public OauthTokenRequest(String accessToken, String tokenType){ this.accessToken = accessToken; this.tokenType = tokenType; } diff --git a/src/main/java/sopt/org/motivooServer/domain/auth/dto/request/RefreshRequest.java b/src/main/java/sopt/org/motivooServer/domain/auth/dto/request/RefreshRequest.java new file mode 100644 index 00000000..31cfaa7f --- /dev/null +++ b/src/main/java/sopt/org/motivooServer/domain/auth/dto/request/RefreshRequest.java @@ -0,0 +1,11 @@ +package sopt.org.motivooServer.domain.auth.dto.request; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class RefreshRequest { + private Long userId; +} diff --git a/src/main/java/sopt/org/motivooServer/domain/user/dto/response/LoginResponse.java b/src/main/java/sopt/org/motivooServer/domain/auth/dto/response/LoginResponse.java similarity index 91% rename from src/main/java/sopt/org/motivooServer/domain/user/dto/response/LoginResponse.java rename to src/main/java/sopt/org/motivooServer/domain/auth/dto/response/LoginResponse.java index d8a23dcf..8bc159e3 100644 --- a/src/main/java/sopt/org/motivooServer/domain/user/dto/response/LoginResponse.java +++ b/src/main/java/sopt/org/motivooServer/domain/auth/dto/response/LoginResponse.java @@ -1,4 +1,4 @@ -package sopt.org.motivooServer.domain.user.dto.response; +package sopt.org.motivooServer.domain.auth.dto.response; import lombok.Builder; import lombok.Getter; diff --git a/src/main/java/sopt/org/motivooServer/domain/auth/dto/response/OauthTokenResponse.java b/src/main/java/sopt/org/motivooServer/domain/auth/dto/response/OauthTokenResponse.java new file mode 100644 index 00000000..0c7c58e6 --- /dev/null +++ b/src/main/java/sopt/org/motivooServer/domain/auth/dto/response/OauthTokenResponse.java @@ -0,0 +1,22 @@ +package sopt.org.motivooServer.domain.auth.dto.response; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +public class OauthTokenResponse { + @JsonProperty("access_token") + private String accessToken; + + @JsonProperty("refresh_token") + private String refreshToken; + + @Builder + public OauthTokenResponse(String accessToken, String refreshToken){ + this.accessToken = accessToken; + this.refreshToken = refreshToken; + } +} diff --git a/src/main/java/sopt/org/motivooServer/domain/user/repository/TokenRedisRepository.java b/src/main/java/sopt/org/motivooServer/domain/auth/repository/TokenRedisRepository.java similarity index 87% rename from src/main/java/sopt/org/motivooServer/domain/user/repository/TokenRedisRepository.java rename to src/main/java/sopt/org/motivooServer/domain/auth/repository/TokenRedisRepository.java index de51e03e..7a92986e 100644 --- a/src/main/java/sopt/org/motivooServer/domain/user/repository/TokenRedisRepository.java +++ b/src/main/java/sopt/org/motivooServer/domain/auth/repository/TokenRedisRepository.java @@ -1,7 +1,6 @@ -package sopt.org.motivooServer.domain.user.repository; +package sopt.org.motivooServer.domain.auth.repository; import io.jsonwebtoken.Claims; -import io.jsonwebtoken.ExpiredJwtException; import io.jsonwebtoken.Jwts; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; @@ -9,12 +8,13 @@ import org.springframework.data.redis.core.ValueOperations; import org.springframework.security.oauth2.jwt.JwtException; import org.springframework.stereotype.Repository; - -import java.util.Base64; +import sopt.org.motivooServer.domain.user.exception.UserException; import java.util.Date; import java.util.Optional; import java.util.concurrent.TimeUnit; +import static sopt.org.motivooServer.domain.user.exception.UserExceptionType.TOKEN_EXPIRED; + @Repository @RequiredArgsConstructor public class TokenRedisRepository { @@ -22,8 +22,8 @@ public class TokenRedisRepository { @Value("${jwt.refresh-token.expire-length}") private long refreshTokenValidityInMilliseconds; - private final static String PREFIX_REFRESH = "REFRESH:"; - private final static String PREFIX_BLOCKED = "BLOCKED:"; + private static final String PREFIX_REFRESH = "REFRESH:"; + private static final String PREFIX_BLOCKED = "BLOCKED:"; @Value("${jwt.token.secret-key}") private String secretKey; @@ -35,7 +35,6 @@ public void saveRefreshToken(String refreshToken, String account) { redisTemplate.expire(key, refreshTokenValidityInMilliseconds, TimeUnit.SECONDS); } - public void saveBlockedToken(String accessToken) { ValueOperations valueOperations = redisTemplate.opsForValue(); String key = PREFIX_BLOCKED + accessToken; @@ -56,10 +55,9 @@ public void saveBlockedToken(String accessToken) { .getExpiration(); } catch (JwtException e){ - throw new RuntimeException("유효하지 않은 토큰 입니다"); + throw new UserException(TOKEN_EXPIRED); } - Long now = new Date().getTime(); Long remainTime = expiration.getTime() - now; redisTemplate.expire(key, remainTime, TimeUnit.SECONDS); @@ -71,14 +69,12 @@ public Optional findByRefreshToken(String refreshToken) { return Optional.ofNullable(valueOperations.get(key)); } - public boolean doesTokenBlocked(String accessToken) { ValueOperations valueOperations = redisTemplate.opsForValue(); String key = PREFIX_BLOCKED + accessToken; return valueOperations.get(key) != null; } - public void deleteRefreshToken(String refreshToken) { ValueOperations valueOperations = redisTemplate.opsForValue(); String key = PREFIX_REFRESH + refreshToken; diff --git a/src/main/java/sopt/org/motivooServer/domain/user/dto/oauth/OAuth2UserInfo.java b/src/main/java/sopt/org/motivooServer/domain/auth/service/OAuth2UserInfo.java similarity index 68% rename from src/main/java/sopt/org/motivooServer/domain/user/dto/oauth/OAuth2UserInfo.java rename to src/main/java/sopt/org/motivooServer/domain/auth/service/OAuth2UserInfo.java index 669b9d65..2764eb9e 100644 --- a/src/main/java/sopt/org/motivooServer/domain/user/dto/oauth/OAuth2UserInfo.java +++ b/src/main/java/sopt/org/motivooServer/domain/auth/service/OAuth2UserInfo.java @@ -1,4 +1,4 @@ -package sopt.org.motivooServer.domain.user.dto.oauth; +package sopt.org.motivooServer.domain.auth.service; public interface OAuth2UserInfo { String getProviderId(); diff --git a/src/main/java/sopt/org/motivooServer/domain/user/service/OauthService.java b/src/main/java/sopt/org/motivooServer/domain/auth/service/OauthService.java similarity index 50% rename from src/main/java/sopt/org/motivooServer/domain/user/service/OauthService.java rename to src/main/java/sopt/org/motivooServer/domain/auth/service/OauthService.java index 7a6a4095..a8ae594f 100644 --- a/src/main/java/sopt/org/motivooServer/domain/user/service/OauthService.java +++ b/src/main/java/sopt/org/motivooServer/domain/auth/service/OauthService.java @@ -1,30 +1,26 @@ -package sopt.org.motivooServer.domain.user.service; +package sopt.org.motivooServer.domain.auth.service; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.core.ParameterizedTypeReference; -import org.springframework.http.MediaType; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; import org.springframework.web.reactive.function.client.WebClient; +import sopt.org.motivooServer.domain.auth.dto.response.OauthTokenResponse; import sopt.org.motivooServer.domain.user.exception.UserException; import sopt.org.motivooServer.domain.user.exception.UserExceptionType; -import sopt.org.motivooServer.domain.user.repository.TokenRedisRepository; -import sopt.org.motivooServer.global.config.oauth.JwtTokenProvider; -import sopt.org.motivooServer.domain.user.dto.oauth.OAuth2UserInfo; +import sopt.org.motivooServer.domain.auth.repository.TokenRedisRepository; +import sopt.org.motivooServer.global.config.auth.JwtTokenProvider; import sopt.org.motivooServer.domain.user.dto.request.UserProfile; -import sopt.org.motivooServer.domain.user.dto.response.LoginResponse; -import sopt.org.motivooServer.domain.user.dto.response.OauthTokenResponse; +import sopt.org.motivooServer.domain.auth.dto.response.LoginResponse; +import sopt.org.motivooServer.domain.auth.dto.request.OauthTokenRequest; import sopt.org.motivooServer.domain.user.entity.SocialPlatform; import sopt.org.motivooServer.domain.user.entity.User; import sopt.org.motivooServer.domain.user.entity.UserType; import sopt.org.motivooServer.domain.user.repository.UserRepository; -import sopt.org.motivooServer.global.config.oauth.JwtValidationType; import java.util.Map; @@ -39,17 +35,19 @@ public class OauthService { private final TokenRedisRepository tokenRedisRepository; private final JwtTokenProvider jwtTokenProvider; - @Transactional - public LoginResponse login(String providerName, OauthTokenResponse tokenResponse) { + public LoginResponse login(OauthTokenRequest tokenRequest) { + String providerName = tokenRequest.getTokenType(); ClientRegistration provider = inMemoryRepository.findByRegistrationId(providerName); String refreshToken = jwtTokenProvider.createRefreshToken(); - User user = getUserProfile(providerName, tokenResponse, provider, refreshToken); + User user = getUserProfile(providerName, tokenRequest, provider, refreshToken); String accessToken = jwtTokenProvider.createAccessToken(String.valueOf(user.getId())); - reissue(user.getId(), refreshToken); //refresh token 재발급 + return buildLoginResponse(user, accessToken, refreshToken); + } + private LoginResponse buildLoginResponse(User user, String accessToken, String refreshToken) { return LoginResponse.builder() .id(user.getSocialId()) .nickname(user.getNickname()) @@ -59,79 +57,89 @@ public LoginResponse login(String providerName, OauthTokenResponse tokenResponse .build(); } - - - @Transactional - public User getUserProfile(String providerName, OauthTokenResponse tokenResponse, ClientRegistration provider, String refreshToken){ - Map userAttributes = getUserAttributes(provider, tokenResponse); - OAuth2UserInfo oAuth2UserInfo = null; - SocialPlatform socialPlatform = null; - if(providerName.equals("kakao")){ - oAuth2UserInfo = new UserProfile(userAttributes); - socialPlatform = SocialPlatform.KAKAO; - } else { - throw new UserException(UserExceptionType.INVALID_SOCIAL_PLATFORM); - } + public User getUserProfile(String providerName, OauthTokenRequest tokenRequest, ClientRegistration provider, String refreshToken) { + Map userAttributes = getUserAttributes(provider, tokenRequest); + OAuth2UserInfo oAuth2UserInfo = getOAuth2UserInfo(providerName, userAttributes); + SocialPlatform socialPlatform = getSocialPlatform(providerName); String providerId = oAuth2UserInfo.getProviderId(); String nickName = oAuth2UserInfo.getNickName(); - User userEntity = userRepository.findBySocialId(providerId); + if (userEntity != null) { + updateRefreshToken(userEntity, refreshToken); + } else { + userEntity = saveUser(nickName, providerId, socialPlatform, tokenRequest, refreshToken); + } - if(userEntity == null){ - userEntity = User.builder() - .nickname(nickName) - .socialId(providerId) - .socialPlatform(socialPlatform) - .socialAccessToken(tokenResponse.getAccessToken()) - .socialRefreshToken(refreshToken) - .type(UserType.NONE) - .deleted(Boolean.FALSE) - .build(); - userRepository.save(userEntity); + return userEntity; + } + + private OAuth2UserInfo getOAuth2UserInfo(String providerName, Map userAttributes) { + if (providerName.equals("kakao")) { + return new UserProfile(userAttributes); + } else { + throw new UserException(UserExceptionType.INVALID_SOCIAL_PLATFORM); } - else{ - userEntity.updateRefreshToken(refreshToken); + } + + private SocialPlatform getSocialPlatform(String providerName) { + if (providerName.equals("kakao")) { + return SocialPlatform.KAKAO; + } else { + throw new UserException(UserExceptionType.INVALID_SOCIAL_PLATFORM); } - return userEntity; } - private Map getUserAttributes(ClientRegistration provider, OauthTokenResponse tokenResponse){ + public void updateRefreshToken(User userEntity, String refreshToken) { + userEntity.updateRefreshToken(refreshToken); + } + + public User saveUser(String nickName, String providerId, SocialPlatform socialPlatform, OauthTokenRequest tokenRequest, String refreshToken) { + User newUser = User.builder() + .nickname(nickName) + .socialId(providerId) + .socialPlatform(socialPlatform) + .socialAccessToken(tokenRequest.getAccessToken()) + .socialRefreshToken(refreshToken) + .type(UserType.NONE) + .deleted(Boolean.FALSE) + .build(); + userRepository.save(newUser); + return newUser; + } + + private Map getUserAttributes(ClientRegistration provider, OauthTokenRequest tokenRequest) { return WebClient.create() .get() .uri(provider.getProviderDetails().getUserInfoEndpoint().getUri()) - .headers(header -> header.setBearerAuth(tokenResponse.getAccessToken())) + .headers(header -> header.setBearerAuth(tokenRequest.getAccessToken())) .retrieve() - .bodyToMono(new ParameterizedTypeReference>(){}) + .bodyToMono(new ParameterizedTypeReference>() { + }) .block(); } @Transactional - public String reissue(Long userId, String refreshToken){ - User user = userRepository.findById(userId) - .orElseThrow( - () -> new UserException(UserExceptionType.INVALID_USER_TYPE)); + public OauthTokenResponse reissue(Long userId, String refreshToken) { + jwtTokenProvider.validateToken(refreshToken); - if(jwtTokenProvider.validateToken(refreshToken)!= JwtValidationType.VALID_JWT){ - throw new UserException(UserExceptionType.TOKEN_EXPIRED); - } - - String reissuedToken = jwtTokenProvider.createRefreshToken(); + String reissuedAccessToken = jwtTokenProvider.createAccessToken(String.valueOf(userId)); + String reissuedRefreshToken = jwtTokenProvider.createRefreshToken(); + OauthTokenResponse tokenResponse = new OauthTokenResponse(reissuedAccessToken, reissuedAccessToken); - tokenRedisRepository.saveRefreshToken(reissuedToken, String.valueOf(userId)); + tokenRedisRepository.saveRefreshToken(reissuedRefreshToken, String.valueOf(userId)); tokenRedisRepository.deleteRefreshToken(refreshToken); - return reissuedToken; + return tokenResponse; } @Transactional - public void logout(String accessToken){ + public void logout(String accessToken) { String userId = SecurityContextHolder.getContext().getAuthentication().getName(); String refreshToken = userRepository.findRefreshTokenById(Long.parseLong(userId)); tokenRedisRepository.saveBlockedToken(accessToken); tokenRedisRepository.deleteRefreshToken(refreshToken); } - } diff --git a/src/main/java/sopt/org/motivooServer/domain/user/controller/OauthController.java b/src/main/java/sopt/org/motivooServer/domain/user/controller/OauthController.java deleted file mode 100644 index 830809bb..00000000 --- a/src/main/java/sopt/org/motivooServer/domain/user/controller/OauthController.java +++ /dev/null @@ -1,36 +0,0 @@ -package sopt.org.motivooServer.domain.user.controller; - - -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; -import sopt.org.motivooServer.domain.user.dto.response.LoginResponse; -import sopt.org.motivooServer.domain.user.dto.response.OauthTokenResponse; -import sopt.org.motivooServer.domain.user.service.OauthService; -import sopt.org.motivooServer.global.response.ApiResponse; -import static sopt.org.motivooServer.global.response.SuccessType.LOGIN_SUCCESS; -import static sopt.org.motivooServer.global.response.SuccessType.LOGOUT_SUCCESS; - - -@RequiredArgsConstructor -@Slf4j -@RestController -public class OauthController { - private final OauthService oauthService; - - @GetMapping("/oauth/login/{provider}") - public ResponseEntity> login(@PathVariable String provider, @RequestBody OauthTokenResponse tokenResponse){ - LoginResponse loginResponse = oauthService.login(provider, tokenResponse); - - return ApiResponse.success(LOGIN_SUCCESS, loginResponse); - } - - @PostMapping("/oauth/logout") - public ResponseEntity> logout(@RequestHeader("Authorization") String accessToken){ - oauthService.logout(accessToken); - - return ApiResponse.success(LOGOUT_SUCCESS); - } - -} diff --git a/src/main/java/sopt/org/motivooServer/domain/user/dto/request/UserProfile.java b/src/main/java/sopt/org/motivooServer/domain/user/dto/request/UserProfile.java index 1f901518..552e1ace 100644 --- a/src/main/java/sopt/org/motivooServer/domain/user/dto/request/UserProfile.java +++ b/src/main/java/sopt/org/motivooServer/domain/user/dto/request/UserProfile.java @@ -1,7 +1,7 @@ package sopt.org.motivooServer.domain.user.dto.request; import lombok.Getter; -import sopt.org.motivooServer.domain.user.dto.oauth.OAuth2UserInfo; +import sopt.org.motivooServer.domain.auth.service.OAuth2UserInfo; import java.util.Map; @@ -21,8 +21,7 @@ public String getProviderId() { @Override public String getProvider() { return "kakao"; - } - + } //TODO @Override public String getNickName() { return String.valueOf(attributes.get("nickname")); diff --git a/src/main/java/sopt/org/motivooServer/domain/user/entity/SocialPlatform.java b/src/main/java/sopt/org/motivooServer/domain/user/entity/SocialPlatform.java index 5559a410..1ec4751f 100644 --- a/src/main/java/sopt/org/motivooServer/domain/user/entity/SocialPlatform.java +++ b/src/main/java/sopt/org/motivooServer/domain/user/entity/SocialPlatform.java @@ -10,7 +10,6 @@ @RequiredArgsConstructor(access = AccessLevel.PRIVATE) public enum SocialPlatform { - KAKAO("카카오"), APPLE("애플"); @@ -18,10 +17,9 @@ public enum SocialPlatform { public static SocialPlatform of(String value) { return Arrays.stream(SocialPlatform.values()) - .filter(socialPlatform -> value.equals(socialPlatform.value)) - .findFirst() - .orElseThrow(() -> new UserException(INVALID_SOCIAL_PLATFORM)); + .filter(socialPlatform -> value.equals(socialPlatform.value)) + .findFirst() + .orElseThrow(() -> new UserException(INVALID_SOCIAL_PLATFORM)); } - } diff --git a/src/main/java/sopt/org/motivooServer/domain/user/entity/User.java b/src/main/java/sopt/org/motivooServer/domain/user/entity/User.java index bc940f7b..2e727bfc 100644 --- a/src/main/java/sopt/org/motivooServer/domain/user/entity/User.java +++ b/src/main/java/sopt/org/motivooServer/domain/user/entity/User.java @@ -62,11 +62,10 @@ public class User extends BaseTimeEntity { @OneToMany(mappedBy = "user") private List userMissions = new ArrayList<>(); - public User() { + protected User() { } - //== 연관관계 메서드 ==// public void addUserMission(UserMission userMission) { this.userMissions.add(userMission); diff --git a/src/main/java/sopt/org/motivooServer/domain/user/entity/UserType.java b/src/main/java/sopt/org/motivooServer/domain/user/entity/UserType.java index 324e5e01..05751e8b 100644 --- a/src/main/java/sopt/org/motivooServer/domain/user/entity/UserType.java +++ b/src/main/java/sopt/org/motivooServer/domain/user/entity/UserType.java @@ -10,7 +10,6 @@ @RequiredArgsConstructor(access = AccessLevel.PRIVATE) public enum UserType { - PARENT("부모"), CHILD("자녀"), NONE("없음") diff --git a/src/main/java/sopt/org/motivooServer/domain/user/exception/UserExceptionType.java b/src/main/java/sopt/org/motivooServer/domain/user/exception/UserExceptionType.java index 94f15356..fc787208 100644 --- a/src/main/java/sopt/org/motivooServer/domain/user/exception/UserExceptionType.java +++ b/src/main/java/sopt/org/motivooServer/domain/user/exception/UserExceptionType.java @@ -20,6 +20,7 @@ public enum UserExceptionType implements BusinessExceptionType { * 400 Not Found */ TOKEN_NOT_FOUND(HttpStatus.NOT_FOUND, "토큰이 없습니다."), + TOKEN_UNSUPPORTED(HttpStatus.NOT_FOUND, "변조된 토큰입니다."), /** * 401 Not Found diff --git a/src/main/java/sopt/org/motivooServer/domain/user/repository/UserRepository.java b/src/main/java/sopt/org/motivooServer/domain/user/repository/UserRepository.java index e064cb79..e3b92d9a 100644 --- a/src/main/java/sopt/org/motivooServer/domain/user/repository/UserRepository.java +++ b/src/main/java/sopt/org/motivooServer/domain/user/repository/UserRepository.java @@ -1,11 +1,7 @@ package sopt.org.motivooServer.domain.user.repository; - -import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.Repository; -import org.springframework.data.repository.query.Param; -import org.springframework.security.core.parameters.P; import sopt.org.motivooServer.domain.user.entity.User; import java.util.Optional; @@ -13,11 +9,7 @@ public interface UserRepository extends Repository { void save(User user); Optional findById(Long id); - User findBySocialId(String socialId); - @Query("select u.socialRefreshToken from User u where u.id=?1") String findRefreshTokenById(Long id); - - } diff --git a/src/main/java/sopt/org/motivooServer/global/config/SecurityConfig.java b/src/main/java/sopt/org/motivooServer/global/config/SecurityConfig.java index 458454f0..100c2144 100644 --- a/src/main/java/sopt/org/motivooServer/global/config/SecurityConfig.java +++ b/src/main/java/sopt/org/motivooServer/global/config/SecurityConfig.java @@ -6,16 +6,13 @@ import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; -import sopt.org.motivooServer.global.config.oauth.CustomAccessDeniedHandler; -import sopt.org.motivooServer.global.config.oauth.CustomJwtAuthenticationEntryPoint; -import sopt.org.motivooServer.global.config.oauth.JwtAuthenticationFilter; - -import static javax.management.Query.and; +import sopt.org.motivooServer.global.config.auth.CustomAccessDeniedHandler; +import sopt.org.motivooServer.global.config.auth.CustomJwtAuthenticationEntryPoint; +import sopt.org.motivooServer.global.config.auth.JwtAuthenticationFilter; import static org.springframework.security.config.http.SessionCreationPolicy.STATELESS; @Configuration diff --git a/src/main/java/sopt/org/motivooServer/global/config/oauth/CustomAccessDeniedHandler.java b/src/main/java/sopt/org/motivooServer/global/config/auth/CustomAccessDeniedHandler.java similarity index 93% rename from src/main/java/sopt/org/motivooServer/global/config/oauth/CustomAccessDeniedHandler.java rename to src/main/java/sopt/org/motivooServer/global/config/auth/CustomAccessDeniedHandler.java index 6c9109f1..623812f2 100644 --- a/src/main/java/sopt/org/motivooServer/global/config/oauth/CustomAccessDeniedHandler.java +++ b/src/main/java/sopt/org/motivooServer/global/config/auth/CustomAccessDeniedHandler.java @@ -1,4 +1,4 @@ -package sopt.org.motivooServer.global.config.oauth; +package sopt.org.motivooServer.global.config.auth; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; diff --git a/src/main/java/sopt/org/motivooServer/global/config/oauth/CustomJwtAuthenticationEntryPoint.java b/src/main/java/sopt/org/motivooServer/global/config/auth/CustomJwtAuthenticationEntryPoint.java similarity index 92% rename from src/main/java/sopt/org/motivooServer/global/config/oauth/CustomJwtAuthenticationEntryPoint.java rename to src/main/java/sopt/org/motivooServer/global/config/auth/CustomJwtAuthenticationEntryPoint.java index 66c3136c..def950cb 100644 --- a/src/main/java/sopt/org/motivooServer/global/config/oauth/CustomJwtAuthenticationEntryPoint.java +++ b/src/main/java/sopt/org/motivooServer/global/config/auth/CustomJwtAuthenticationEntryPoint.java @@ -1,4 +1,4 @@ -package sopt.org.motivooServer.global.config.oauth; +package sopt.org.motivooServer.global.config.auth; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; @@ -8,7 +8,6 @@ @Component public class CustomJwtAuthenticationEntryPoint implements AuthenticationEntryPoint { - @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) { setResponse(response); diff --git a/src/main/java/sopt/org/motivooServer/global/config/oauth/JwtAuthenticationFilter.java b/src/main/java/sopt/org/motivooServer/global/config/auth/JwtAuthenticationFilter.java similarity index 64% rename from src/main/java/sopt/org/motivooServer/global/config/oauth/JwtAuthenticationFilter.java rename to src/main/java/sopt/org/motivooServer/global/config/auth/JwtAuthenticationFilter.java index 69331310..21c1f691 100644 --- a/src/main/java/sopt/org/motivooServer/global/config/oauth/JwtAuthenticationFilter.java +++ b/src/main/java/sopt/org/motivooServer/global/config/auth/JwtAuthenticationFilter.java @@ -1,5 +1,4 @@ -package sopt.org.motivooServer.global.config.oauth; - +package sopt.org.motivooServer.global.config.auth; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; @@ -27,19 +26,16 @@ protected void doFilterInternal(@NonNull HttpServletRequest request, @NonNull FilterChain filterChain) throws ServletException, IOException { try { final String token = getJwtFromRequest(request); - if (jwtTokenProvider.validateToken(token) == JwtValidationType.VALID_JWT) { - Long memberId = Long.parseLong(jwtTokenProvider.getPayload(token)); - // authentication 객체 생성 -> principal에 유저정보를 담는다. - UserAuthentication authentication = new UserAuthentication(memberId.toString(), null, null); - authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); - SecurityContextHolder.getContext().setAuthentication(authentication); - } + jwtTokenProvider.validateToken(token); + + Long memberId = Long.parseLong(jwtTokenProvider.getPayload(token)); + // authentication 객체 생성 -> principal에 유저정보를 담는다. + UserAuthentication authentication = new UserAuthentication(memberId.toString(), null, null); + authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + SecurityContextHolder.getContext().setAuthentication(authentication); + } catch (Exception exception) { - try { - exception.printStackTrace(); - } catch (Exception e) { - throw new ServletException("Authentication failed: " + exception.getMessage(), exception); - } + exception.printStackTrace(); } // 다음 필터로 요청 전달 filterChain.doFilter(request, response); diff --git a/src/main/java/sopt/org/motivooServer/global/config/oauth/JwtTokenProvider.java b/src/main/java/sopt/org/motivooServer/global/config/auth/JwtTokenProvider.java similarity index 75% rename from src/main/java/sopt/org/motivooServer/global/config/oauth/JwtTokenProvider.java rename to src/main/java/sopt/org/motivooServer/global/config/auth/JwtTokenProvider.java index 03371042..2dcd96f2 100644 --- a/src/main/java/sopt/org/motivooServer/global/config/oauth/JwtTokenProvider.java +++ b/src/main/java/sopt/org/motivooServer/global/config/auth/JwtTokenProvider.java @@ -1,12 +1,16 @@ -package sopt.org.motivooServer.global.config.oauth; +package sopt.org.motivooServer.global.config.auth; import io.jsonwebtoken.*; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.security.oauth2.jwt.JwtException; import org.springframework.stereotype.Component; +import sopt.org.motivooServer.domain.user.exception.UserException; + import java.util.*; +import static sopt.org.motivooServer.domain.user.exception.UserExceptionType.*; + @Component @Slf4j public class JwtTokenProvider { @@ -59,31 +63,26 @@ public String getPayload(String token){ } catch (ExpiredJwtException e) { return e.getClaims().getSubject(); } catch (JwtException e){ - throw new RuntimeException("유효하지 않은 토큰 입니다"); + throw new RuntimeException(String.valueOf(JwtValidationType.INVALID_JWT_TOKEN)); } } - public JwtValidationType validateToken(String token) { + public void validateToken(String token) { try { - final Claims claimsJws = Jwts.parserBuilder() + token = token.replaceAll("\\s+", ""); + token = token.replace("Bearer", ""); + Jwts.parserBuilder() .setSigningKey(secretKey) .build() - .parseClaimsJws(token) - .getBody(); - return JwtValidationType.VALID_JWT; + .parseClaimsJws(token); } catch (MalformedJwtException ex){ - return JwtValidationType.INVALID_JWT_TOKEN; + throw new UserException(TOKEN_NOT_FOUND); } catch (ExpiredJwtException ex) { - return JwtValidationType.EXPIRED_JWT_TOKEN; + throw new UserException(TOKEN_EXPIRED); } catch (UnsupportedJwtException ex) { - return JwtValidationType.UNSUPPORTED_JWT_TOKEN; + throw new UserException(TOKEN_UNSUPPORTED); } catch (IllegalArgumentException ex) { - return JwtValidationType.EMPTY_JWT; + throw new UserException(TOKEN_NOT_FOUND); } } - - - - - } \ No newline at end of file diff --git a/src/main/java/sopt/org/motivooServer/global/config/oauth/JwtValidationType.java b/src/main/java/sopt/org/motivooServer/global/config/auth/JwtValidationType.java similarity index 87% rename from src/main/java/sopt/org/motivooServer/global/config/oauth/JwtValidationType.java rename to src/main/java/sopt/org/motivooServer/global/config/auth/JwtValidationType.java index c53b3957..3a3c0a4b 100644 --- a/src/main/java/sopt/org/motivooServer/global/config/oauth/JwtValidationType.java +++ b/src/main/java/sopt/org/motivooServer/global/config/auth/JwtValidationType.java @@ -1,4 +1,4 @@ -package sopt.org.motivooServer.global.config.oauth; +package sopt.org.motivooServer.global.config.auth; public enum JwtValidationType { VALID_JWT, // 유효한 JWT diff --git a/src/main/java/sopt/org/motivooServer/global/config/oauth/OAuth2ClientRegistrationRepositoryConfig.java b/src/main/java/sopt/org/motivooServer/global/config/auth/OAuth2ClientRegistrationRepositoryConfig.java similarity index 97% rename from src/main/java/sopt/org/motivooServer/global/config/oauth/OAuth2ClientRegistrationRepositoryConfig.java rename to src/main/java/sopt/org/motivooServer/global/config/auth/OAuth2ClientRegistrationRepositoryConfig.java index 9bf3bdd5..de76e467 100644 --- a/src/main/java/sopt/org/motivooServer/global/config/oauth/OAuth2ClientRegistrationRepositoryConfig.java +++ b/src/main/java/sopt/org/motivooServer/global/config/auth/OAuth2ClientRegistrationRepositoryConfig.java @@ -1,4 +1,4 @@ -package sopt.org.motivooServer.global.config.oauth; +package sopt.org.motivooServer.global.config.auth; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.security.oauth2.client.ClientsConfiguredCondition; diff --git a/src/main/java/sopt/org/motivooServer/global/config/oauth/UserAuthentication.java b/src/main/java/sopt/org/motivooServer/global/config/auth/UserAuthentication.java similarity index 89% rename from src/main/java/sopt/org/motivooServer/global/config/oauth/UserAuthentication.java rename to src/main/java/sopt/org/motivooServer/global/config/auth/UserAuthentication.java index cfe4140f..94164fd3 100644 --- a/src/main/java/sopt/org/motivooServer/global/config/oauth/UserAuthentication.java +++ b/src/main/java/sopt/org/motivooServer/global/config/auth/UserAuthentication.java @@ -1,8 +1,7 @@ -package sopt.org.motivooServer.global.config.oauth; +package sopt.org.motivooServer.global.config.auth; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.GrantedAuthority; - import java.util.Collection; public class UserAuthentication extends UsernamePasswordAuthenticationToken { diff --git a/src/main/java/sopt/org/motivooServer/global/response/SuccessType.java b/src/main/java/sopt/org/motivooServer/global/response/SuccessType.java index 1fc8fd9b..4c2a56be 100644 --- a/src/main/java/sopt/org/motivooServer/global/response/SuccessType.java +++ b/src/main/java/sopt/org/motivooServer/global/response/SuccessType.java @@ -20,7 +20,8 @@ public enum SuccessType { //소셜 로그인 LOGIN_SUCCESS(HttpStatus.OK, "로그인에 성공했습니다."), - LOGOUT_SUCCESS(HttpStatus.OK, "로그아웃에 성공했습니다.") + LOGOUT_SUCCESS(HttpStatus.OK, "로그아웃에 성공했습니다."), + REISSUE_SUCCESS(HttpStatus.OK, "토큰 재발급에 성공했습니다."); ; private final HttpStatus httpStatus; From bb390fae7b8686999cee5cd6cd8c80160c88c7d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=ED=98=9C=EC=97=B0?= Date: Tue, 9 Jan 2024 17:08:32 +0900 Subject: [PATCH 09/17] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor:=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=EB=A6=AC=EB=B7=B0=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/config}/CustomAccessDeniedHandler.java | 2 +- .../auth/config}/CustomJwtAuthenticationEntryPoint.java | 2 +- .../auth/config}/JwtAuthenticationFilter.java | 2 +- .../auth => domain/auth/config}/JwtTokenProvider.java | 2 +- .../auth => domain/auth/config}/JwtValidationType.java | 2 +- .../config}/OAuth2ClientRegistrationRepositoryConfig.java | 2 +- .../config/redis => domain/auth/config}/RedisConfig.java | 2 +- .../auth => domain/auth/config}/UserAuthentication.java | 2 +- .../org/motivooServer/domain/auth/service/OauthService.java | 2 +- .../org/motivooServer/global/config/SecurityConfig.java | 6 +++--- 10 files changed, 12 insertions(+), 12 deletions(-) rename src/main/java/sopt/org/motivooServer/{global/config/auth => domain/auth/config}/CustomAccessDeniedHandler.java (93%) rename src/main/java/sopt/org/motivooServer/{global/config/auth => domain/auth/config}/CustomJwtAuthenticationEntryPoint.java (93%) rename src/main/java/sopt/org/motivooServer/{global/config/auth => domain/auth/config}/JwtAuthenticationFilter.java (97%) rename src/main/java/sopt/org/motivooServer/{global/config/auth => domain/auth/config}/JwtTokenProvider.java (98%) rename src/main/java/sopt/org/motivooServer/{global/config/auth => domain/auth/config}/JwtValidationType.java (87%) rename src/main/java/sopt/org/motivooServer/{global/config/auth => domain/auth/config}/OAuth2ClientRegistrationRepositoryConfig.java (97%) rename src/main/java/sopt/org/motivooServer/{global/config/redis => domain/auth/config}/RedisConfig.java (95%) rename src/main/java/sopt/org/motivooServer/{global/config/auth => domain/auth/config}/UserAuthentication.java (90%) diff --git a/src/main/java/sopt/org/motivooServer/global/config/auth/CustomAccessDeniedHandler.java b/src/main/java/sopt/org/motivooServer/domain/auth/config/CustomAccessDeniedHandler.java similarity index 93% rename from src/main/java/sopt/org/motivooServer/global/config/auth/CustomAccessDeniedHandler.java rename to src/main/java/sopt/org/motivooServer/domain/auth/config/CustomAccessDeniedHandler.java index 623812f2..b8e993e1 100644 --- a/src/main/java/sopt/org/motivooServer/global/config/auth/CustomAccessDeniedHandler.java +++ b/src/main/java/sopt/org/motivooServer/domain/auth/config/CustomAccessDeniedHandler.java @@ -1,4 +1,4 @@ -package sopt.org.motivooServer.global.config.auth; +package sopt.org.motivooServer.domain.auth.config; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; diff --git a/src/main/java/sopt/org/motivooServer/global/config/auth/CustomJwtAuthenticationEntryPoint.java b/src/main/java/sopt/org/motivooServer/domain/auth/config/CustomJwtAuthenticationEntryPoint.java similarity index 93% rename from src/main/java/sopt/org/motivooServer/global/config/auth/CustomJwtAuthenticationEntryPoint.java rename to src/main/java/sopt/org/motivooServer/domain/auth/config/CustomJwtAuthenticationEntryPoint.java index def950cb..7f2973e0 100644 --- a/src/main/java/sopt/org/motivooServer/global/config/auth/CustomJwtAuthenticationEntryPoint.java +++ b/src/main/java/sopt/org/motivooServer/domain/auth/config/CustomJwtAuthenticationEntryPoint.java @@ -1,4 +1,4 @@ -package sopt.org.motivooServer.global.config.auth; +package sopt.org.motivooServer.domain.auth.config; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; diff --git a/src/main/java/sopt/org/motivooServer/global/config/auth/JwtAuthenticationFilter.java b/src/main/java/sopt/org/motivooServer/domain/auth/config/JwtAuthenticationFilter.java similarity index 97% rename from src/main/java/sopt/org/motivooServer/global/config/auth/JwtAuthenticationFilter.java rename to src/main/java/sopt/org/motivooServer/domain/auth/config/JwtAuthenticationFilter.java index 21c1f691..5fef4c7f 100644 --- a/src/main/java/sopt/org/motivooServer/global/config/auth/JwtAuthenticationFilter.java +++ b/src/main/java/sopt/org/motivooServer/domain/auth/config/JwtAuthenticationFilter.java @@ -1,4 +1,4 @@ -package sopt.org.motivooServer.global.config.auth; +package sopt.org.motivooServer.domain.auth.config; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; diff --git a/src/main/java/sopt/org/motivooServer/global/config/auth/JwtTokenProvider.java b/src/main/java/sopt/org/motivooServer/domain/auth/config/JwtTokenProvider.java similarity index 98% rename from src/main/java/sopt/org/motivooServer/global/config/auth/JwtTokenProvider.java rename to src/main/java/sopt/org/motivooServer/domain/auth/config/JwtTokenProvider.java index 2dcd96f2..c081537f 100644 --- a/src/main/java/sopt/org/motivooServer/global/config/auth/JwtTokenProvider.java +++ b/src/main/java/sopt/org/motivooServer/domain/auth/config/JwtTokenProvider.java @@ -1,4 +1,4 @@ -package sopt.org.motivooServer.global.config.auth; +package sopt.org.motivooServer.domain.auth.config; import io.jsonwebtoken.*; import lombok.extern.slf4j.Slf4j; diff --git a/src/main/java/sopt/org/motivooServer/global/config/auth/JwtValidationType.java b/src/main/java/sopt/org/motivooServer/domain/auth/config/JwtValidationType.java similarity index 87% rename from src/main/java/sopt/org/motivooServer/global/config/auth/JwtValidationType.java rename to src/main/java/sopt/org/motivooServer/domain/auth/config/JwtValidationType.java index 3a3c0a4b..7c8c447d 100644 --- a/src/main/java/sopt/org/motivooServer/global/config/auth/JwtValidationType.java +++ b/src/main/java/sopt/org/motivooServer/domain/auth/config/JwtValidationType.java @@ -1,4 +1,4 @@ -package sopt.org.motivooServer.global.config.auth; +package sopt.org.motivooServer.domain.auth.config; public enum JwtValidationType { VALID_JWT, // 유효한 JWT diff --git a/src/main/java/sopt/org/motivooServer/global/config/auth/OAuth2ClientRegistrationRepositoryConfig.java b/src/main/java/sopt/org/motivooServer/domain/auth/config/OAuth2ClientRegistrationRepositoryConfig.java similarity index 97% rename from src/main/java/sopt/org/motivooServer/global/config/auth/OAuth2ClientRegistrationRepositoryConfig.java rename to src/main/java/sopt/org/motivooServer/domain/auth/config/OAuth2ClientRegistrationRepositoryConfig.java index de76e467..cbcc5724 100644 --- a/src/main/java/sopt/org/motivooServer/global/config/auth/OAuth2ClientRegistrationRepositoryConfig.java +++ b/src/main/java/sopt/org/motivooServer/domain/auth/config/OAuth2ClientRegistrationRepositoryConfig.java @@ -1,4 +1,4 @@ -package sopt.org.motivooServer.global.config.auth; +package sopt.org.motivooServer.domain.auth.config; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.security.oauth2.client.ClientsConfiguredCondition; diff --git a/src/main/java/sopt/org/motivooServer/global/config/redis/RedisConfig.java b/src/main/java/sopt/org/motivooServer/domain/auth/config/RedisConfig.java similarity index 95% rename from src/main/java/sopt/org/motivooServer/global/config/redis/RedisConfig.java rename to src/main/java/sopt/org/motivooServer/domain/auth/config/RedisConfig.java index a43c3e35..731982e9 100644 --- a/src/main/java/sopt/org/motivooServer/global/config/redis/RedisConfig.java +++ b/src/main/java/sopt/org/motivooServer/domain/auth/config/RedisConfig.java @@ -1,4 +1,4 @@ -package sopt.org.motivooServer.global.config.redis; +package sopt.org.motivooServer.domain.auth.config; import org.springframework.beans.factory.annotation.Value; diff --git a/src/main/java/sopt/org/motivooServer/global/config/auth/UserAuthentication.java b/src/main/java/sopt/org/motivooServer/domain/auth/config/UserAuthentication.java similarity index 90% rename from src/main/java/sopt/org/motivooServer/global/config/auth/UserAuthentication.java rename to src/main/java/sopt/org/motivooServer/domain/auth/config/UserAuthentication.java index 94164fd3..d6855302 100644 --- a/src/main/java/sopt/org/motivooServer/global/config/auth/UserAuthentication.java +++ b/src/main/java/sopt/org/motivooServer/domain/auth/config/UserAuthentication.java @@ -1,4 +1,4 @@ -package sopt.org.motivooServer.global.config.auth; +package sopt.org.motivooServer.domain.auth.config; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.GrantedAuthority; diff --git a/src/main/java/sopt/org/motivooServer/domain/auth/service/OauthService.java b/src/main/java/sopt/org/motivooServer/domain/auth/service/OauthService.java index a8ae594f..5c8ca18f 100644 --- a/src/main/java/sopt/org/motivooServer/domain/auth/service/OauthService.java +++ b/src/main/java/sopt/org/motivooServer/domain/auth/service/OauthService.java @@ -13,7 +13,7 @@ import sopt.org.motivooServer.domain.user.exception.UserException; import sopt.org.motivooServer.domain.user.exception.UserExceptionType; import sopt.org.motivooServer.domain.auth.repository.TokenRedisRepository; -import sopt.org.motivooServer.global.config.auth.JwtTokenProvider; +import sopt.org.motivooServer.domain.auth.config.JwtTokenProvider; import sopt.org.motivooServer.domain.user.dto.request.UserProfile; import sopt.org.motivooServer.domain.auth.dto.response.LoginResponse; import sopt.org.motivooServer.domain.auth.dto.request.OauthTokenRequest; diff --git a/src/main/java/sopt/org/motivooServer/global/config/SecurityConfig.java b/src/main/java/sopt/org/motivooServer/global/config/SecurityConfig.java index 100c2144..dcc1c5fb 100644 --- a/src/main/java/sopt/org/motivooServer/global/config/SecurityConfig.java +++ b/src/main/java/sopt/org/motivooServer/global/config/SecurityConfig.java @@ -10,9 +10,9 @@ import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; -import sopt.org.motivooServer.global.config.auth.CustomAccessDeniedHandler; -import sopt.org.motivooServer.global.config.auth.CustomJwtAuthenticationEntryPoint; -import sopt.org.motivooServer.global.config.auth.JwtAuthenticationFilter; +import sopt.org.motivooServer.domain.auth.config.CustomAccessDeniedHandler; +import sopt.org.motivooServer.domain.auth.config.CustomJwtAuthenticationEntryPoint; +import sopt.org.motivooServer.domain.auth.config.JwtAuthenticationFilter; import static org.springframework.security.config.http.SessionCreationPolicy.STATELESS; @Configuration From f62262031dfbed60920ff91c1a33b06487e6d5b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=ED=98=9C=EC=97=B0?= Date: Tue, 9 Jan 2024 17:08:52 +0900 Subject: [PATCH 10/17] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor:=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=EB=A6=AC=EB=B7=B0=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- http/motivoo.http | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 http/motivoo.http diff --git a/http/motivoo.http b/http/motivoo.http new file mode 100644 index 00000000..2d2779c8 --- /dev/null +++ b/http/motivoo.http @@ -0,0 +1,9 @@ +GET http://localhost:8080/api/health/v2 + +POST http://localhost:8080/oauth/login +Content-Type: application/json + +{ + "id": 999, + "value": "content" +} \ No newline at end of file From 36a5c62969852814c3971e840c391ed95a63d42d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=ED=98=9C=EC=97=B0?= Date: Tue, 9 Jan 2024 17:16:14 +0900 Subject: [PATCH 11/17] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor:=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=EB=A6=AC=EB=B7=B0=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../motivooServer/domain/auth/controller/OauthController.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/sopt/org/motivooServer/domain/auth/controller/OauthController.java b/src/main/java/sopt/org/motivooServer/domain/auth/controller/OauthController.java index 8386a361..9e369949 100644 --- a/src/main/java/sopt/org/motivooServer/domain/auth/controller/OauthController.java +++ b/src/main/java/sopt/org/motivooServer/domain/auth/controller/OauthController.java @@ -11,7 +11,6 @@ import sopt.org.motivooServer.global.response.ApiResponse; import static sopt.org.motivooServer.global.response.SuccessType.*; - @RequiredArgsConstructor @RestController public class OauthController { From ec91658848c2ce04f455af82cc378f59c7f9a5e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=ED=98=9C=EC=97=B0?= Date: Wed, 10 Jan 2024 01:35:37 +0900 Subject: [PATCH 12/17] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor:=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=EB=A6=AC=EB=B7=B0=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/auth/config/JwtTokenProvider.java | 22 ++++++++- .../auth/controller/OauthController.java | 12 ++--- .../domain/auth/dto/redis/RefreshToken.java | 18 ++++--- .../auth/dto/request/OauthTokenRequest.java | 19 ++------ .../auth/dto/request/RefreshRequest.java | 13 +++-- .../auth/dto/response/LoginResponse.java | 31 +++++------- .../auth/dto/response/OauthTokenResponse.java | 18 ++----- .../auth/repository/TokenRedisRepository.java | 33 ++++++++----- .../domain/auth/service/OauthService.java | 48 +++++++------------ ...UserProfile.java => KakaoUserProfile.java} | 4 +- .../domain/user/entity/User.java | 20 +++++--- .../user/exception/UserExceptionType.java | 10 ++-- .../user/repository/UserRepository.java | 7 ++- .../global/config/SecurityConfig.java | 3 +- 14 files changed, 123 insertions(+), 135 deletions(-) rename src/main/java/sopt/org/motivooServer/domain/user/dto/request/{UserProfile.java => KakaoUserProfile.java} (82%) diff --git a/src/main/java/sopt/org/motivooServer/domain/auth/config/JwtTokenProvider.java b/src/main/java/sopt/org/motivooServer/domain/auth/config/JwtTokenProvider.java index c081537f..b69d1fa9 100644 --- a/src/main/java/sopt/org/motivooServer/domain/auth/config/JwtTokenProvider.java +++ b/src/main/java/sopt/org/motivooServer/domain/auth/config/JwtTokenProvider.java @@ -5,15 +5,18 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.security.oauth2.jwt.JwtException; import org.springframework.stereotype.Component; +import sopt.org.motivooServer.domain.auth.dto.response.OauthTokenResponse; +import sopt.org.motivooServer.domain.auth.repository.TokenRedisRepository; import sopt.org.motivooServer.domain.user.exception.UserException; import java.util.*; import static sopt.org.motivooServer.domain.user.exception.UserExceptionType.*; -@Component @Slf4j +@Component public class JwtTokenProvider { + private static final String BEARER_TYPE = "Bearer"; @Value("${jwt.access-token.expire-length}") private long accessTokenValidityInMilliseconds; @@ -24,6 +27,8 @@ public class JwtTokenProvider { @Value("${jwt.token.secret-key}") private String secretKey; + private TokenRedisRepository tokenRedisRepository; + public String createAccessToken(String payload) { return createToken(payload, accessTokenValidityInMilliseconds); } @@ -70,7 +75,7 @@ public String getPayload(String token){ public void validateToken(String token) { try { token = token.replaceAll("\\s+", ""); - token = token.replace("Bearer", ""); + token = token.replace(BEARER_TYPE, ""); Jwts.parserBuilder() .setSigningKey(secretKey) .build() @@ -85,4 +90,17 @@ public void validateToken(String token) { throw new UserException(TOKEN_NOT_FOUND); } } + + public OauthTokenResponse reissue(Long userId, String refreshToken) { + validateToken(refreshToken); + + String reissuedAccessToken = createAccessToken(String.valueOf(userId)); + String reissuedRefreshToken = createRefreshToken(); + OauthTokenResponse tokenResponse = new OauthTokenResponse(reissuedAccessToken, reissuedAccessToken); + + tokenRedisRepository.saveRefreshToken(reissuedRefreshToken, String.valueOf(userId)); + tokenRedisRepository.deleteRefreshToken(refreshToken); + + return tokenResponse; + } } \ No newline at end of file diff --git a/src/main/java/sopt/org/motivooServer/domain/auth/controller/OauthController.java b/src/main/java/sopt/org/motivooServer/domain/auth/controller/OauthController.java index 9e369949..d20ed3e6 100644 --- a/src/main/java/sopt/org/motivooServer/domain/auth/controller/OauthController.java +++ b/src/main/java/sopt/org/motivooServer/domain/auth/controller/OauthController.java @@ -1,8 +1,10 @@ package sopt.org.motivooServer.domain.auth.controller; +import lombok.AccessLevel; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import sopt.org.motivooServer.domain.auth.config.JwtTokenProvider; import sopt.org.motivooServer.domain.auth.dto.request.RefreshRequest; import sopt.org.motivooServer.domain.auth.dto.response.LoginResponse; import sopt.org.motivooServer.domain.auth.dto.request.OauthTokenRequest; @@ -11,24 +13,22 @@ import sopt.org.motivooServer.global.response.ApiResponse; import static sopt.org.motivooServer.global.response.SuccessType.*; -@RequiredArgsConstructor @RestController +@RequiredArgsConstructor(access = AccessLevel.PROTECTED) public class OauthController { private final OauthService oauthService; + private final JwtTokenProvider tokenProvider; @PostMapping("/oauth/login") public ResponseEntity> login(@RequestBody OauthTokenRequest tokenRequest) { - LoginResponse loginResponse = oauthService.login(tokenRequest); - - return ApiResponse.success(LOGIN_SUCCESS, loginResponse); + return ApiResponse.success(LOGIN_SUCCESS, oauthService.login(tokenRequest)); } @PostMapping("/oauth/reissue") public ResponseEntity> reissue( @RequestHeader("Authorization") String refreshToken, @RequestBody final RefreshRequest request) { - - return ApiResponse.success(REISSUE_SUCCESS, oauthService.reissue(request.getUserId(), refreshToken)); + return ApiResponse.success(REISSUE_SUCCESS, tokenProvider.reissue(request.userId(), refreshToken)); } @PostMapping("/oauth/logout") diff --git a/src/main/java/sopt/org/motivooServer/domain/auth/dto/redis/RefreshToken.java b/src/main/java/sopt/org/motivooServer/domain/auth/dto/redis/RefreshToken.java index 7ab0b1e5..87ca7cc9 100644 --- a/src/main/java/sopt/org/motivooServer/domain/auth/dto/redis/RefreshToken.java +++ b/src/main/java/sopt/org/motivooServer/domain/auth/dto/redis/RefreshToken.java @@ -1,14 +1,12 @@ package sopt.org.motivooServer.domain.auth.dto.redis; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; - -@Getter -@Builder -@AllArgsConstructor -public class RefreshToken { - private Long id; - private String refreshToken; +import com.fasterxml.jackson.annotation.JsonProperty; + +public record RefreshToken ( + Long id, + + @JsonProperty("refresh_token") + String refreshToken +){ } diff --git a/src/main/java/sopt/org/motivooServer/domain/auth/dto/request/OauthTokenRequest.java b/src/main/java/sopt/org/motivooServer/domain/auth/dto/request/OauthTokenRequest.java index 3c5a3200..5d0c5b51 100644 --- a/src/main/java/sopt/org/motivooServer/domain/auth/dto/request/OauthTokenRequest.java +++ b/src/main/java/sopt/org/motivooServer/domain/auth/dto/request/OauthTokenRequest.java @@ -1,22 +1,13 @@ package sopt.org.motivooServer.domain.auth.dto.request; import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; -@Getter -@NoArgsConstructor -public class OauthTokenRequest { - @JsonProperty("access_token") - private String accessToken; +public record OauthTokenRequest ( + @JsonProperty("access_token") + String accessToken, @JsonProperty("token_type") - private String tokenType; + String tokenType +){ - @Builder - public OauthTokenRequest(String accessToken, String tokenType){ - this.accessToken = accessToken; - this.tokenType = tokenType; - } } diff --git a/src/main/java/sopt/org/motivooServer/domain/auth/dto/request/RefreshRequest.java b/src/main/java/sopt/org/motivooServer/domain/auth/dto/request/RefreshRequest.java index 31cfaa7f..7d836b64 100644 --- a/src/main/java/sopt/org/motivooServer/domain/auth/dto/request/RefreshRequest.java +++ b/src/main/java/sopt/org/motivooServer/domain/auth/dto/request/RefreshRequest.java @@ -1,11 +1,10 @@ package sopt.org.motivooServer.domain.auth.dto.request; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.NoArgsConstructor; +import com.fasterxml.jackson.annotation.JsonProperty; + +public record RefreshRequest( + @JsonProperty("user_id") + Long userId +) { -@Getter -@NoArgsConstructor(access = AccessLevel.PRIVATE) -public class RefreshRequest { - private Long userId; } diff --git a/src/main/java/sopt/org/motivooServer/domain/auth/dto/response/LoginResponse.java b/src/main/java/sopt/org/motivooServer/domain/auth/dto/response/LoginResponse.java index 8bc159e3..8a638f56 100644 --- a/src/main/java/sopt/org/motivooServer/domain/auth/dto/response/LoginResponse.java +++ b/src/main/java/sopt/org/motivooServer/domain/auth/dto/response/LoginResponse.java @@ -1,24 +1,19 @@ package sopt.org.motivooServer.domain.auth.dto.response; + +import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; -@Getter -@NoArgsConstructor -public class LoginResponse { - private String id; - private String nickname; - private String tokenType; - private String accessToken; - private String refreshToken; +@Builder +public record LoginResponse ( + String id, + String nickname, + @JsonProperty("token_type") + String tokenType, - @Builder - public LoginResponse(String id, String nickname, String tokenType, String accessToken, String refreshToken){ - this.id = id; - this.nickname = nickname; - this.tokenType = tokenType; - this.accessToken = accessToken; - this.refreshToken = refreshToken; - } + @JsonProperty("access_token") + String accessToken, + @JsonProperty("refresh_token") + String refreshToken +){ } diff --git a/src/main/java/sopt/org/motivooServer/domain/auth/dto/response/OauthTokenResponse.java b/src/main/java/sopt/org/motivooServer/domain/auth/dto/response/OauthTokenResponse.java index 0c7c58e6..af502260 100644 --- a/src/main/java/sopt/org/motivooServer/domain/auth/dto/response/OauthTokenResponse.java +++ b/src/main/java/sopt/org/motivooServer/domain/auth/dto/response/OauthTokenResponse.java @@ -1,22 +1,12 @@ package sopt.org.motivooServer.domain.auth.dto.response; import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; -@Getter -@NoArgsConstructor -public class OauthTokenResponse { +public record OauthTokenResponse ( @JsonProperty("access_token") - private String accessToken; + String accessToken, @JsonProperty("refresh_token") - private String refreshToken; - - @Builder - public OauthTokenResponse(String accessToken, String refreshToken){ - this.accessToken = accessToken; - this.refreshToken = refreshToken; - } + String refreshToke +){ } diff --git a/src/main/java/sopt/org/motivooServer/domain/auth/repository/TokenRedisRepository.java b/src/main/java/sopt/org/motivooServer/domain/auth/repository/TokenRedisRepository.java index 7a92986e..7904cf8a 100644 --- a/src/main/java/sopt/org/motivooServer/domain/auth/repository/TokenRedisRepository.java +++ b/src/main/java/sopt/org/motivooServer/domain/auth/repository/TokenRedisRepository.java @@ -2,6 +2,7 @@ import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; +import lombok.AccessLevel; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.redis.core.StringRedisTemplate; @@ -16,67 +17,73 @@ import static sopt.org.motivooServer.domain.user.exception.UserExceptionType.TOKEN_EXPIRED; @Repository -@RequiredArgsConstructor public class TokenRedisRepository { private final StringRedisTemplate redisTemplate; + private final ValueOperations valueOperations; @Value("${jwt.refresh-token.expire-length}") private long refreshTokenValidityInMilliseconds; + private static final String BEARER_TYPE = "Bearer"; + private static final String PREFIX_REFRESH = "REFRESH:"; private static final String PREFIX_BLOCKED = "BLOCKED:"; @Value("${jwt.token.secret-key}") private String secretKey; - + + public TokenRedisRepository(StringRedisTemplate redisTemplate) { + this.redisTemplate = redisTemplate; + this.valueOperations = redisTemplate.opsForValue(); + } + + public void saveRefreshToken(String refreshToken, String account) { - ValueOperations valueOperations = redisTemplate.opsForValue(); String key = PREFIX_REFRESH + refreshToken; valueOperations.set(key, account); redisTemplate.expire(key, refreshTokenValidityInMilliseconds, TimeUnit.SECONDS); } public void saveBlockedToken(String accessToken) { - ValueOperations valueOperations = redisTemplate.opsForValue(); String key = PREFIX_BLOCKED + accessToken; valueOperations.set(key, "empty"); - Date expiration = null; + Date expiration = getExpirationFromToken(accessToken); + setExpirationInRedis(key, expiration); + } + private Date getExpirationFromToken(String accessToken) { try { accessToken = accessToken.replaceAll("\\s+", ""); - accessToken = accessToken.replace("Bearer", ""); + accessToken = accessToken.replace(BEARER_TYPE, ""); Claims claims = Jwts.parserBuilder() .setSigningKey(secretKey) .build() .parseClaimsJws(accessToken) .getBody(); - expiration = claims - .getExpiration(); - - } catch (JwtException e){ + return claims.getExpiration(); + } catch (JwtException e) { throw new UserException(TOKEN_EXPIRED); } + } + private void setExpirationInRedis(String key, Date expiration) { Long now = new Date().getTime(); Long remainTime = expiration.getTime() - now; redisTemplate.expire(key, remainTime, TimeUnit.SECONDS); } public Optional findByRefreshToken(String refreshToken) { - ValueOperations valueOperations = redisTemplate.opsForValue(); String key = PREFIX_REFRESH + refreshToken; return Optional.ofNullable(valueOperations.get(key)); } public boolean doesTokenBlocked(String accessToken) { - ValueOperations valueOperations = redisTemplate.opsForValue(); String key = PREFIX_BLOCKED + accessToken; return valueOperations.get(key) != null; } public void deleteRefreshToken(String refreshToken) { - ValueOperations valueOperations = redisTemplate.opsForValue(); String key = PREFIX_REFRESH + refreshToken; redisTemplate.delete(key); } diff --git a/src/main/java/sopt/org/motivooServer/domain/auth/service/OauthService.java b/src/main/java/sopt/org/motivooServer/domain/auth/service/OauthService.java index 5c8ca18f..aa5a828f 100644 --- a/src/main/java/sopt/org/motivooServer/domain/auth/service/OauthService.java +++ b/src/main/java/sopt/org/motivooServer/domain/auth/service/OauthService.java @@ -1,5 +1,6 @@ package sopt.org.motivooServer.domain.auth.service; +import lombok.AccessLevel; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.core.ParameterizedTypeReference; @@ -9,12 +10,11 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.reactive.function.client.WebClient; -import sopt.org.motivooServer.domain.auth.dto.response.OauthTokenResponse; import sopt.org.motivooServer.domain.user.exception.UserException; import sopt.org.motivooServer.domain.user.exception.UserExceptionType; import sopt.org.motivooServer.domain.auth.repository.TokenRedisRepository; import sopt.org.motivooServer.domain.auth.config.JwtTokenProvider; -import sopt.org.motivooServer.domain.user.dto.request.UserProfile; +import sopt.org.motivooServer.domain.user.dto.request.KakaoUserProfile; import sopt.org.motivooServer.domain.auth.dto.response.LoginResponse; import sopt.org.motivooServer.domain.auth.dto.request.OauthTokenRequest; import sopt.org.motivooServer.domain.user.entity.SocialPlatform; @@ -24,10 +24,10 @@ import java.util.Map; -@Service -@RequiredArgsConstructor @Slf4j +@Service @Transactional(readOnly = true) +@RequiredArgsConstructor(access = AccessLevel.PROTECTED) public class OauthService { private static final String BEARER_TYPE = "Bearer"; private final InMemoryClientRegistrationRepository inMemoryRepository; @@ -37,17 +37,17 @@ public class OauthService { @Transactional public LoginResponse login(OauthTokenRequest tokenRequest) { - String providerName = tokenRequest.getTokenType(); + String providerName = tokenRequest.tokenType(); ClientRegistration provider = inMemoryRepository.findByRegistrationId(providerName); String refreshToken = jwtTokenProvider.createRefreshToken(); User user = getUserProfile(providerName, tokenRequest, provider, refreshToken); String accessToken = jwtTokenProvider.createAccessToken(String.valueOf(user.getId())); - return buildLoginResponse(user, accessToken, refreshToken); + return getLoginResponse(user, accessToken, refreshToken); } - private LoginResponse buildLoginResponse(User user, String accessToken, String refreshToken) { + private LoginResponse getLoginResponse(User user, String accessToken, String refreshToken) { return LoginResponse.builder() .id(user.getSocialId()) .nickname(user.getNickname()) @@ -68,27 +68,26 @@ public User getUserProfile(String providerName, OauthTokenRequest tokenRequest, if (userEntity != null) { updateRefreshToken(userEntity, refreshToken); - } else { - userEntity = saveUser(nickName, providerId, socialPlatform, tokenRequest, refreshToken); + return userEntity; } - return userEntity; + return saveUser(nickName, providerId, socialPlatform, tokenRequest, refreshToken); + } private OAuth2UserInfo getOAuth2UserInfo(String providerName, Map userAttributes) { if (providerName.equals("kakao")) { - return new UserProfile(userAttributes); - } else { - throw new UserException(UserExceptionType.INVALID_SOCIAL_PLATFORM); + return new KakaoUserProfile(userAttributes); } + throw new UserException(UserExceptionType.INVALID_SOCIAL_PLATFORM); } private SocialPlatform getSocialPlatform(String providerName) { if (providerName.equals("kakao")) { return SocialPlatform.KAKAO; - } else { - throw new UserException(UserExceptionType.INVALID_SOCIAL_PLATFORM); } + throw new UserException(UserExceptionType.INVALID_SOCIAL_PLATFORM); + } public void updateRefreshToken(User userEntity, String refreshToken) { @@ -100,8 +99,8 @@ public User saveUser(String nickName, String providerId, SocialPlatform socialPl .nickname(nickName) .socialId(providerId) .socialPlatform(socialPlatform) - .socialAccessToken(tokenRequest.getAccessToken()) - .socialRefreshToken(refreshToken) + .socialAccessToken(tokenRequest.accessToken()) + .refreshToken(refreshToken) .type(UserType.NONE) .deleted(Boolean.FALSE) .build(); @@ -113,26 +112,13 @@ private Map getUserAttributes(ClientRegistration provider, Oauth return WebClient.create() .get() .uri(provider.getProviderDetails().getUserInfoEndpoint().getUri()) - .headers(header -> header.setBearerAuth(tokenRequest.getAccessToken())) + .headers(header -> header.setBearerAuth(tokenRequest.accessToken())) .retrieve() .bodyToMono(new ParameterizedTypeReference>() { }) .block(); } - @Transactional - public OauthTokenResponse reissue(Long userId, String refreshToken) { - jwtTokenProvider.validateToken(refreshToken); - - String reissuedAccessToken = jwtTokenProvider.createAccessToken(String.valueOf(userId)); - String reissuedRefreshToken = jwtTokenProvider.createRefreshToken(); - OauthTokenResponse tokenResponse = new OauthTokenResponse(reissuedAccessToken, reissuedAccessToken); - - tokenRedisRepository.saveRefreshToken(reissuedRefreshToken, String.valueOf(userId)); - tokenRedisRepository.deleteRefreshToken(refreshToken); - - return tokenResponse; - } @Transactional public void logout(String accessToken) { diff --git a/src/main/java/sopt/org/motivooServer/domain/user/dto/request/UserProfile.java b/src/main/java/sopt/org/motivooServer/domain/user/dto/request/KakaoUserProfile.java similarity index 82% rename from src/main/java/sopt/org/motivooServer/domain/user/dto/request/UserProfile.java rename to src/main/java/sopt/org/motivooServer/domain/user/dto/request/KakaoUserProfile.java index 552e1ace..770d1ef1 100644 --- a/src/main/java/sopt/org/motivooServer/domain/user/dto/request/UserProfile.java +++ b/src/main/java/sopt/org/motivooServer/domain/user/dto/request/KakaoUserProfile.java @@ -6,10 +6,10 @@ import java.util.Map; @Getter -public class UserProfile implements OAuth2UserInfo { +public class KakaoUserProfile implements OAuth2UserInfo { private Map attributes; - public UserProfile(Map attributes){ + public KakaoUserProfile(Map attributes){ this.attributes = attributes; } diff --git a/src/main/java/sopt/org/motivooServer/domain/user/entity/User.java b/src/main/java/sopt/org/motivooServer/domain/user/entity/User.java index 2e727bfc..3d080b76 100644 --- a/src/main/java/sopt/org/motivooServer/domain/user/entity/User.java +++ b/src/main/java/sopt/org/motivooServer/domain/user/entity/User.java @@ -24,11 +24,9 @@ @Getter @Entity @Table(name = "`user`") -@Builder -@AllArgsConstructor(access = AccessLevel.PROTECTED) +@RequiredArgsConstructor(access = AccessLevel.PROTECTED) @SQLDelete(sql = "UPDATE user SET user.deleted=true WHERE user_id=?") public class User extends BaseTimeEntity { - @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "user_id") private Long id; @@ -49,7 +47,7 @@ public class User extends BaseTimeEntity { private String socialAccessToken; - private String socialRefreshToken; + private String refreshToken; @Enumerated(EnumType.STRING) @Column(nullable = false) @@ -62,8 +60,16 @@ public class User extends BaseTimeEntity { @OneToMany(mappedBy = "user") private List userMissions = new ArrayList<>(); - protected User() { - + @Builder + private User(String nickname, String socialId, SocialPlatform socialPlatform, String socialAccessToken, + String refreshToken, UserType type, boolean deleted){ + this.nickname = nickname; + this.socialId = socialId; + this.socialPlatform = socialPlatform; + this.socialAccessToken = socialAccessToken; + this.refreshToken = refreshToken; + this.type = type; + this.deleted = deleted; } //== 연관관계 메서드 ==// @@ -75,6 +81,6 @@ public void addUserMission(UserMission userMission) { } public void updateRefreshToken(String refreshToken){ - this.socialRefreshToken = refreshToken; + this.refreshToken = refreshToken; } } diff --git a/src/main/java/sopt/org/motivooServer/domain/user/exception/UserExceptionType.java b/src/main/java/sopt/org/motivooServer/domain/user/exception/UserExceptionType.java index fc787208..f0a54606 100644 --- a/src/main/java/sopt/org/motivooServer/domain/user/exception/UserExceptionType.java +++ b/src/main/java/sopt/org/motivooServer/domain/user/exception/UserExceptionType.java @@ -17,19 +17,17 @@ public enum UserExceptionType implements BusinessExceptionType { INVALID_SOCIAL_PLATFORM(HttpStatus.BAD_REQUEST, "유효하지 않은 소셜 플랫폼입니다."), /** - * 400 Not Found + * 404 Not Found */ TOKEN_NOT_FOUND(HttpStatus.NOT_FOUND, "토큰이 없습니다."), TOKEN_UNSUPPORTED(HttpStatus.NOT_FOUND, "변조된 토큰입니다."), /** - * 401 Not Found + * 401 UNAUTHORIZED */ - TOKEN_EXPIRED(HttpStatus.NOT_FOUND, "토큰이 유효하지 않습니다."), + TOKEN_EXPIRED(HttpStatus.UNAUTHORIZED, "토큰이 유효하지 않습니다."), + - /** - * 404 Not Found - */ ; diff --git a/src/main/java/sopt/org/motivooServer/domain/user/repository/UserRepository.java b/src/main/java/sopt/org/motivooServer/domain/user/repository/UserRepository.java index e3b92d9a..ebb47e5a 100644 --- a/src/main/java/sopt/org/motivooServer/domain/user/repository/UserRepository.java +++ b/src/main/java/sopt/org/motivooServer/domain/user/repository/UserRepository.java @@ -1,15 +1,14 @@ package sopt.org.motivooServer.domain.user.repository; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.Repository; import sopt.org.motivooServer.domain.user.entity.User; import java.util.Optional; -public interface UserRepository extends Repository { - void save(User user); +public interface UserRepository extends JpaRepository { Optional findById(Long id); User findBySocialId(String socialId); - @Query("select u.socialRefreshToken from User u where u.id=?1") + @Query("select u.refreshToken from User u where u.id=?1") String findRefreshTokenById(Long id); } diff --git a/src/main/java/sopt/org/motivooServer/global/config/SecurityConfig.java b/src/main/java/sopt/org/motivooServer/global/config/SecurityConfig.java index dcc1c5fb..82e84404 100644 --- a/src/main/java/sopt/org/motivooServer/global/config/SecurityConfig.java +++ b/src/main/java/sopt/org/motivooServer/global/config/SecurityConfig.java @@ -1,6 +1,7 @@ package sopt.org.motivooServer.global.config; import jakarta.servlet.http.HttpServletResponse; +import lombok.AccessLevel; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -17,7 +18,7 @@ @Configuration @EnableWebSecurity -@RequiredArgsConstructor +@RequiredArgsConstructor(access = AccessLevel.PROTECTED) public class SecurityConfig { private final JwtAuthenticationFilter jwtAuthenticationFilter; From 201ea8213d6bb4bf28caf98926ba2de88dad700a Mon Sep 17 00:00:00 2001 From: jun02160 Date: Wed, 10 Jan 2024 01:49:59 +0900 Subject: [PATCH 13/17] =?UTF-8?q?=F0=9F=9A=91=20hotfix:=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=EC=97=90=20=EC=9D=98=ED=95=9C=20=EB=B9=8C?= =?UTF-8?q?=EB=93=9C=20=EC=98=A4=EB=A5=98=20=ED=95=B4=EA=B2=B0=20#2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/static/docs/open-api-3.0.1.json | 134 ++++++++++++++++++ .../MotivooServerApplicationTests.java | 2 +- .../controller/BaseControllerTest.java | 9 ++ .../controller/HealthCheckControllerTest.java | 11 +- 4 files changed, 154 insertions(+), 2 deletions(-) diff --git a/src/main/resources/static/docs/open-api-3.0.1.json b/src/main/resources/static/docs/open-api-3.0.1.json index 28f2029c..ba3007d1 100644 --- a/src/main/resources/static/docs/open-api-3.0.1.json +++ b/src/main/resources/static/docs/open-api-3.0.1.json @@ -34,6 +34,74 @@ } } } + }, + "/mypage/{userId}" : { + "get" : { + "tags" : [ "마이페이지" ], + "summary" : "헬스체크용 API", + "description" : "마이페이지 홈 조회", + "operationId" : "마이페이지 조회 API 성공 Example", + "parameters" : [ { + "name" : "userId", + "in" : "path", + "description" : "", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "200" : { + "description" : "200", + "content" : { + "application/json;charset=UTF-8" : { + "schema" : { + "$ref" : "#/components/schemas/mypage-userId643760186" + }, + "examples" : { + "마이페이지 조회 API 성공 Example" : { + "value" : "{\n \"code\" : 200,\n \"message\" : \"마이페이지 정보 조회에 성공했습니다.\",\n \"success\" : true,\n \"data\" : {\n \"user_nickname\" : \"모티뿡뿡이\",\n \"user_type\" : \"CHILD\"\n }\n}" + } + } + } + } + } + } + } + }, + "/mypage/info/{userId}" : { + "get" : { + "tags" : [ "마이페이지" ], + "summary" : "헬스체크용 API", + "description" : "마이페이지 내 정보 조회", + "operationId" : "마이페이지 내 정보 조회 API 성공 Example", + "parameters" : [ { + "name" : "userId", + "in" : "path", + "description" : "", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "200" : { + "description" : "200", + "content" : { + "application/json;charset=UTF-8" : { + "schema" : { + "$ref" : "#/components/schemas/mypage-info-userId1088240176" + }, + "examples" : { + "마이페이지 내 정보 조회 API 성공 Example" : { + "value" : "{\n \"code\" : 200,\n \"message\" : \"마이페이지 정보 조회에 성공했습니다.\",\n \"success\" : true,\n \"data\" : {\n \"user_nickname\" : \"모티뿡뿡이\",\n \"user_age\" : 20,\n \"user_type\" : \"CHILD\"\n }\n}" + } + } + } + } + } + } + } } }, "components" : { @@ -58,6 +126,72 @@ "description" : "상태 메세지" } } + }, + "mypage-userId643760186" : { + "type" : "object", + "properties" : { + "code" : { + "type" : "number", + "description" : "상태 코드" + }, + "data" : { + "type" : "object", + "properties" : { + "user_type" : { + "type" : "string", + "description" : "유저 타입 (부모/자식)" + }, + "user_nickname" : { + "type" : "string", + "description" : "유저 닉네임" + } + }, + "description" : "응답 데이터" + }, + "success" : { + "type" : "boolean", + "description" : "응답 성공 여부" + }, + "message" : { + "type" : "string", + "description" : "상태 메세지" + } + } + }, + "mypage-info-userId1088240176" : { + "type" : "object", + "properties" : { + "code" : { + "type" : "number", + "description" : "상태 코드" + }, + "data" : { + "type" : "object", + "properties" : { + "user_type" : { + "type" : "string", + "description" : "유저 타입 (부모/자식)" + }, + "user_nickname" : { + "type" : "string", + "description" : "유저 닉네임" + }, + "user_age" : { + "type" : "number", + "description" : "유저 나이" + } + }, + "description" : "응답 데이터" + }, + "success" : { + "type" : "boolean", + "description" : "응답 성공 여부" + }, + "message" : { + "type" : "string", + "description" : "상태 메세지" + } + } } } } diff --git a/src/test/java/sopt/org/motivooServer/MotivooServerApplicationTests.java b/src/test/java/sopt/org/motivooServer/MotivooServerApplicationTests.java index c2cb2eb6..21f51a43 100644 --- a/src/test/java/sopt/org/motivooServer/MotivooServerApplicationTests.java +++ b/src/test/java/sopt/org/motivooServer/MotivooServerApplicationTests.java @@ -8,7 +8,7 @@ import sopt.org.motivooServer.global.util.slack.SlackUtil; @SpringBootTest -@ActiveProfiles({"deploy", "local"}) +@ActiveProfiles({"local", "deploy"}) class MotivooServerApplicationTests { @MockBean diff --git a/src/test/java/sopt/org/motivooServer/controller/BaseControllerTest.java b/src/test/java/sopt/org/motivooServer/controller/BaseControllerTest.java index 554afe57..26c3b54c 100644 --- a/src/test/java/sopt/org/motivooServer/controller/BaseControllerTest.java +++ b/src/test/java/sopt/org/motivooServer/controller/BaseControllerTest.java @@ -18,6 +18,9 @@ import com.fasterxml.jackson.databind.ObjectMapper; +import sopt.org.motivooServer.domain.auth.config.CustomJwtAuthenticationEntryPoint; +import sopt.org.motivooServer.domain.auth.config.JwtTokenProvider; + @AutoConfigureMockMvc @AutoConfigureRestDocs @ExtendWith({RestDocumentationExtension.class}) @@ -33,6 +36,12 @@ public abstract class BaseControllerTest { @Autowired protected MockMvc mockMvc; + @Autowired + private JwtTokenProvider jwtTokenProvider; + + @Autowired + private CustomJwtAuthenticationEntryPoint customJwtAuthenticationEntryPoint; + @BeforeEach void setUp(final RestDocumentationContextProvider restDocumentation) { mockMvc = MockMvcBuilders.webAppContextSetup(ctx) diff --git a/src/test/java/sopt/org/motivooServer/controller/HealthCheckControllerTest.java b/src/test/java/sopt/org/motivooServer/controller/HealthCheckControllerTest.java index 09d41127..5632b3f1 100644 --- a/src/test/java/sopt/org/motivooServer/controller/HealthCheckControllerTest.java +++ b/src/test/java/sopt/org/motivooServer/controller/HealthCheckControllerTest.java @@ -7,6 +7,8 @@ import static org.springframework.restdocs.payload.PayloadDocumentation.*; import static sopt.org.motivooServer.global.response.SuccessType.*; +import java.security.Principal; + import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; @@ -21,12 +23,13 @@ import com.epages.restdocs.apispec.ResourceSnippetParameters; import lombok.extern.slf4j.Slf4j; +import sopt.org.motivooServer.domain.auth.config.CustomJwtAuthenticationEntryPoint; +import sopt.org.motivooServer.domain.auth.config.JwtTokenProvider; import sopt.org.motivooServer.global.healthcheck.HealthCheckController; import sopt.org.motivooServer.global.response.ApiResponse; import sopt.org.motivooServer.global.util.slack.SlackUtil; @Slf4j -@WithMockUser(roles = "USER") @DisplayName("HealthCheckController 테스트") @WebMvcTest(HealthCheckController.class) public class HealthCheckControllerTest extends BaseControllerTest { @@ -36,9 +39,15 @@ public class HealthCheckControllerTest extends BaseControllerTest { @MockBean private SlackUtil slackUtil; + @MockBean + private JwtTokenProvider jwtTokenProvider; + @MockBean HealthCheckController healthCheckController; + @MockBean + CustomJwtAuthenticationEntryPoint customJwtAuthenticationEntryPoint; + @DisplayName("Health Check Controller 테스트") @Test void healthCheckControllerTest() throws Exception { From 84319540c4a894a60a3ca45e7f4fff7c30f99195 Mon Sep 17 00:00:00 2001 From: jun02160 Date: Wed, 10 Jan 2024 01:58:11 +0900 Subject: [PATCH 14/17] =?UTF-8?q?=F0=9F=94=A7=20chore:=20HealthCheckContro?= =?UTF-8?q?ller=20=ED=95=84=EB=93=9C=20=EC=A0=95=EB=A6=AC=20#2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../motivooServer/controller/HealthCheckControllerTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/java/sopt/org/motivooServer/controller/HealthCheckControllerTest.java b/src/test/java/sopt/org/motivooServer/controller/HealthCheckControllerTest.java index 5632b3f1..df7c290b 100644 --- a/src/test/java/sopt/org/motivooServer/controller/HealthCheckControllerTest.java +++ b/src/test/java/sopt/org/motivooServer/controller/HealthCheckControllerTest.java @@ -43,10 +43,10 @@ public class HealthCheckControllerTest extends BaseControllerTest { private JwtTokenProvider jwtTokenProvider; @MockBean - HealthCheckController healthCheckController; + private CustomJwtAuthenticationEntryPoint customJwtAuthenticationEntryPoint; @MockBean - CustomJwtAuthenticationEntryPoint customJwtAuthenticationEntryPoint; + private HealthCheckController healthCheckController; @DisplayName("Health Check Controller 테스트") @Test From 494497bbbf0c9a705101897c040091e37b1cdf5a Mon Sep 17 00:00:00 2001 From: jun02160 Date: Wed, 10 Jan 2024 02:04:34 +0900 Subject: [PATCH 15/17] =?UTF-8?q?=F0=9F=94=A7=20chore:=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=ED=99=98=EA=B2=BD=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EC=A7=80=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/static/docs/open-api-3.0.1.json | 134 ------------------ .../MotivooServerApplicationTests.java | 2 +- 2 files changed, 1 insertion(+), 135 deletions(-) diff --git a/src/main/resources/static/docs/open-api-3.0.1.json b/src/main/resources/static/docs/open-api-3.0.1.json index ba3007d1..28f2029c 100644 --- a/src/main/resources/static/docs/open-api-3.0.1.json +++ b/src/main/resources/static/docs/open-api-3.0.1.json @@ -34,74 +34,6 @@ } } } - }, - "/mypage/{userId}" : { - "get" : { - "tags" : [ "마이페이지" ], - "summary" : "헬스체크용 API", - "description" : "마이페이지 홈 조회", - "operationId" : "마이페이지 조회 API 성공 Example", - "parameters" : [ { - "name" : "userId", - "in" : "path", - "description" : "", - "required" : true, - "schema" : { - "type" : "string" - } - } ], - "responses" : { - "200" : { - "description" : "200", - "content" : { - "application/json;charset=UTF-8" : { - "schema" : { - "$ref" : "#/components/schemas/mypage-userId643760186" - }, - "examples" : { - "마이페이지 조회 API 성공 Example" : { - "value" : "{\n \"code\" : 200,\n \"message\" : \"마이페이지 정보 조회에 성공했습니다.\",\n \"success\" : true,\n \"data\" : {\n \"user_nickname\" : \"모티뿡뿡이\",\n \"user_type\" : \"CHILD\"\n }\n}" - } - } - } - } - } - } - } - }, - "/mypage/info/{userId}" : { - "get" : { - "tags" : [ "마이페이지" ], - "summary" : "헬스체크용 API", - "description" : "마이페이지 내 정보 조회", - "operationId" : "마이페이지 내 정보 조회 API 성공 Example", - "parameters" : [ { - "name" : "userId", - "in" : "path", - "description" : "", - "required" : true, - "schema" : { - "type" : "string" - } - } ], - "responses" : { - "200" : { - "description" : "200", - "content" : { - "application/json;charset=UTF-8" : { - "schema" : { - "$ref" : "#/components/schemas/mypage-info-userId1088240176" - }, - "examples" : { - "마이페이지 내 정보 조회 API 성공 Example" : { - "value" : "{\n \"code\" : 200,\n \"message\" : \"마이페이지 정보 조회에 성공했습니다.\",\n \"success\" : true,\n \"data\" : {\n \"user_nickname\" : \"모티뿡뿡이\",\n \"user_age\" : 20,\n \"user_type\" : \"CHILD\"\n }\n}" - } - } - } - } - } - } - } } }, "components" : { @@ -126,72 +58,6 @@ "description" : "상태 메세지" } } - }, - "mypage-userId643760186" : { - "type" : "object", - "properties" : { - "code" : { - "type" : "number", - "description" : "상태 코드" - }, - "data" : { - "type" : "object", - "properties" : { - "user_type" : { - "type" : "string", - "description" : "유저 타입 (부모/자식)" - }, - "user_nickname" : { - "type" : "string", - "description" : "유저 닉네임" - } - }, - "description" : "응답 데이터" - }, - "success" : { - "type" : "boolean", - "description" : "응답 성공 여부" - }, - "message" : { - "type" : "string", - "description" : "상태 메세지" - } - } - }, - "mypage-info-userId1088240176" : { - "type" : "object", - "properties" : { - "code" : { - "type" : "number", - "description" : "상태 코드" - }, - "data" : { - "type" : "object", - "properties" : { - "user_type" : { - "type" : "string", - "description" : "유저 타입 (부모/자식)" - }, - "user_nickname" : { - "type" : "string", - "description" : "유저 닉네임" - }, - "user_age" : { - "type" : "number", - "description" : "유저 나이" - } - }, - "description" : "응답 데이터" - }, - "success" : { - "type" : "boolean", - "description" : "응답 성공 여부" - }, - "message" : { - "type" : "string", - "description" : "상태 메세지" - } - } } } } diff --git a/src/test/java/sopt/org/motivooServer/MotivooServerApplicationTests.java b/src/test/java/sopt/org/motivooServer/MotivooServerApplicationTests.java index 21f51a43..e048e380 100644 --- a/src/test/java/sopt/org/motivooServer/MotivooServerApplicationTests.java +++ b/src/test/java/sopt/org/motivooServer/MotivooServerApplicationTests.java @@ -8,7 +8,7 @@ import sopt.org.motivooServer.global.util.slack.SlackUtil; @SpringBootTest -@ActiveProfiles({"local", "deploy"}) +@ActiveProfiles({"test"}) class MotivooServerApplicationTests { @MockBean From 2471a77a9e202bc7438ca465757e49f51810d258 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=ED=98=9C=EC=97=B0?= Date: Wed, 10 Jan 2024 02:05:44 +0900 Subject: [PATCH 16/17] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor:=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=EB=A6=AC=EB=B7=B0=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/auth/config/JwtAuthenticationFilter.java | 13 +++++++------ .../domain/auth/config/JwtTokenProvider.java | 5 +++++ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/main/java/sopt/org/motivooServer/domain/auth/config/JwtAuthenticationFilter.java b/src/main/java/sopt/org/motivooServer/domain/auth/config/JwtAuthenticationFilter.java index 5fef4c7f..eec4e748 100644 --- a/src/main/java/sopt/org/motivooServer/domain/auth/config/JwtAuthenticationFilter.java +++ b/src/main/java/sopt/org/motivooServer/domain/auth/config/JwtAuthenticationFilter.java @@ -6,6 +6,7 @@ import jakarta.servlet.http.HttpServletResponse; import lombok.NonNull; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; import org.springframework.stereotype.Component; @@ -14,6 +15,7 @@ import java.io.IOException; +@Slf4j @Component @RequiredArgsConstructor public class JwtAuthenticationFilter extends OncePerRequestFilter { @@ -24,18 +26,17 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { protected void doFilterInternal(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull FilterChain filterChain) throws ServletException, IOException { - try { - final String token = getJwtFromRequest(request); - jwtTokenProvider.validateToken(token); + final String token = getJwtFromRequest(request); + jwtTokenProvider.validateToken(token); + try { Long memberId = Long.parseLong(jwtTokenProvider.getPayload(token)); // authentication 객체 생성 -> principal에 유저정보를 담는다. UserAuthentication authentication = new UserAuthentication(memberId.toString(), null, null); authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authentication); - - } catch (Exception exception) { - exception.printStackTrace(); + } catch (NumberFormatException e) { + log.error("refresh token은 유저 아이디를 담고있지 않습니다."); } // 다음 필터로 요청 전달 filterChain.doFilter(request, response); diff --git a/src/main/java/sopt/org/motivooServer/domain/auth/config/JwtTokenProvider.java b/src/main/java/sopt/org/motivooServer/domain/auth/config/JwtTokenProvider.java index b69d1fa9..b2dca933 100644 --- a/src/main/java/sopt/org/motivooServer/domain/auth/config/JwtTokenProvider.java +++ b/src/main/java/sopt/org/motivooServer/domain/auth/config/JwtTokenProvider.java @@ -29,6 +29,10 @@ public class JwtTokenProvider { private TokenRedisRepository tokenRedisRepository; + public JwtTokenProvider(TokenRedisRepository tokenRedisRepository){ + this.tokenRedisRepository = tokenRedisRepository; + } + public String createAccessToken(String payload) { return createToken(payload, accessTokenValidityInMilliseconds); } @@ -91,6 +95,7 @@ public void validateToken(String token) { } } + public OauthTokenResponse reissue(Long userId, String refreshToken) { validateToken(refreshToken); From 926dd3d862a134db77b487fa5d388fc9cac56308 Mon Sep 17 00:00:00 2001 From: jun02160 Date: Wed, 10 Jan 2024 02:09:00 +0900 Subject: [PATCH 17/17] =?UTF-8?q?=E2=9C=85=20test:=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EB=B9=8C=EB=93=9C=20=ED=99=98=EA=B2=BD=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 --- .../sopt/org/motivooServer/MotivooServerApplicationTests.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/sopt/org/motivooServer/MotivooServerApplicationTests.java b/src/test/java/sopt/org/motivooServer/MotivooServerApplicationTests.java index e048e380..21f51a43 100644 --- a/src/test/java/sopt/org/motivooServer/MotivooServerApplicationTests.java +++ b/src/test/java/sopt/org/motivooServer/MotivooServerApplicationTests.java @@ -8,7 +8,7 @@ import sopt.org.motivooServer.global.util.slack.SlackUtil; @SpringBootTest -@ActiveProfiles({"test"}) +@ActiveProfiles({"local", "deploy"}) class MotivooServerApplicationTests { @MockBean