Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: 회원 탈퇴를 구현한다. #50

Merged
merged 11 commits into from
Jan 13, 2024
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package net.teumteum.auth.domain.response;

import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

Expand All @@ -13,7 +14,8 @@ public class TokenResponse {
private String refreshToken;
private String oauthId;

@lombok.Builder

@Builder
public TokenResponse(String accessToken, String refreshToken) {
this.accessToken = accessToken;
this.refreshToken = refreshToken;
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/net/teumteum/auth/service/OAuthService.java
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,9 @@ private OAuthUserInfo getOAuthUserInfo(ClientRegistration clientRegistration, Au

private TokenResponse checkUserAndMakeResponse(OAuthUserInfo oAuthUserInfo, Authenticated authenticated) {
String oauthId = oAuthUserInfo.getOAuthId();
java.util.Optional<net.teumteum.user.domain.User> user = getUser(oauthId, authenticated);
Optional<User> user = getUser(oauthId, authenticated);
if (user.isEmpty()) {
return new net.teumteum.auth.domain.response.TokenResponse(oAuthUserInfo.getOAuthId());
return new TokenResponse(oAuthUserInfo.getOAuthId());
}
return jwtService.createServiceToken(user.get());
}
Expand Down
32 changes: 32 additions & 0 deletions src/main/java/net/teumteum/core/config/RedisConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package net.teumteum.core.config;

import lombok.RequiredArgsConstructor;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
@RequiredArgsConstructor
public class RedisConfig {

private final RedisProperties redisProperties;

@Bean
public RedisConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory(redisProperties.getHost(), redisProperties.getPort());
}

@Bean
public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, String> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class));
return redisTemplate;
}
}
32 changes: 16 additions & 16 deletions src/main/java/net/teumteum/core/security/SecurityConfig.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package net.teumteum.core.security;


import static org.springframework.security.config.http.SessionCreationPolicy.STATELESS;

import lombok.RequiredArgsConstructor;
import net.teumteum.core.security.filter.JwtAccessDeniedHandler;
import net.teumteum.core.security.filter.JwtAuthenticationEntryPoint;
Expand All @@ -15,8 +17,6 @@
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import static org.springframework.security.config.http.SessionCreationPolicy.STATELESS;

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
Expand All @@ -34,20 +34,20 @@ public WebSecurityCustomizer webSecurityCustomizer() {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.csrf(AbstractHttpConfigurer::disable)
.cors(Customizer.withDefaults())
.authorizeHttpRequests(
request -> request.requestMatchers("/**").permitAll()
.anyRequest().authenticated())
.httpBasic(AbstractHttpConfigurer::disable)
.formLogin(AbstractHttpConfigurer::disable)
.sessionManagement(sessionManagement -> sessionManagement.sessionCreationPolicy(STATELESS))
.exceptionHandling(
exceptionHandling ->
exceptionHandling
.authenticationEntryPoint(jwtAuthenticationEntryPoint)
.accessDeniedHandler(accessDeniedHandler)
)
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
.cors(Customizer.withDefaults())
.authorizeHttpRequests(
request -> request.requestMatchers("/**").permitAll()
.anyRequest().authenticated())
.httpBasic(AbstractHttpConfigurer::disable)
.formLogin(AbstractHttpConfigurer::disable)
.sessionManagement(sessionManagement -> sessionManagement.sessionCreationPolicy(STATELESS))
.exceptionHandling(
exceptionHandling ->
exceptionHandling
.authenticationEntryPoint(jwtAuthenticationEntryPoint)
.accessDeniedHandler(accessDeniedHandler)
)
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,10 @@ public TokenResponse createServiceToken(User users) {
String accessToken = createAccessToken(String.valueOf(users.getId()));
String refreshToken = createRefreshToken();

this.redisService.setDataExpire(String.valueOf(users.getId()), refreshToken,
this.redisService.setDataWithExpiration(String.valueOf(users.getId()), refreshToken,
this.jwtProperty.getRefresh().getExpiration());

return new TokenResponse(jwtProperty.getBearer() + " " + accessToken, refreshToken);

}

public String createAccessToken(String payload) {
Expand All @@ -70,7 +69,6 @@ public String createAccessToken(String payload) {

public String createRefreshToken() {
return this.createToken(UUID.randomUUID().toString(), jwtProperty.getRefresh().getExpiration());

}

private String createToken(String payload, Long tokenExpiration) {
Expand Down Expand Up @@ -98,5 +96,4 @@ public boolean validateToken(String token) {
}
return false;
}

}
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
package net.teumteum.core.security.service;

import java.time.Duration;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;

import java.time.Duration;

@Service
@RequiredArgsConstructor
public class RedisService {

private final StringRedisTemplate stringRedisTemplate;

public String getData(String key) {
Expand All @@ -22,16 +22,16 @@ public void setData(String key, String value) {
valueOperations.set(key, value);
}

public void deleteData(String key) {
this.stringRedisTemplate.delete(key);
}

public void setDataExpire(String key, String value, Long duration) {
public void setDataWithExpiration(String key, String value, Long duration) {
ValueOperations<String, String> valueOperations = getStringStringValueOperations();
Duration expireDuration = Duration.ofSeconds(duration);
valueOperations.set(key, value, expireDuration);
}

public void deleteData(String key) {
this.stringRedisTemplate.delete(key);
}

private ValueOperations<String, String> getStringStringValueOperations() {
return this.stringRedisTemplate.opsForValue();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
@Service
@RequiredArgsConstructor
public class SecurityService {

private final UserConnector userConnector;

public static void clearSecurityContext() {
Expand All @@ -21,7 +22,8 @@ private UserAuthentication getUserAuthentication() {


public Long getCurrentUserId() {
return getUserAuthentication() == null ? userConnector.findAllUser().get(0).getId() : getUserAuthentication().getId();
return getUserAuthentication() == null ? userConnector.findAllUser().get(0).getId()
: getUserAuthentication().getId();
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import net.teumteum.user.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
Expand Down Expand Up @@ -73,6 +74,13 @@ public InterestQuestionResponse getInterestQuestion(@RequestParam("user-id") Lis
return userService.getInterestQuestionByUserIds(userIds);
}

@DeleteMapping("/withdraw")
@ResponseStatus(HttpStatus.OK)
public void withdraw() {
userService.withdraw(getCurrentUserId());
}


@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(IllegalArgumentException.class)
public ErrorResponse handleIllegalArgumentException(IllegalArgumentException illegalArgumentException) {
Expand Down
13 changes: 13 additions & 0 deletions src/main/java/net/teumteum/user/service/UserService.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import java.util.List;
import lombok.RequiredArgsConstructor;
import net.teumteum.core.security.service.RedisService;
import net.teumteum.user.domain.InterestQuestion;
import net.teumteum.user.domain.User;
import net.teumteum.user.domain.UserRepository;
Expand All @@ -21,6 +22,7 @@ public class UserService {

private final UserRepository userRepository;
private final InterestQuestion interestQuestion;
private final RedisService redisService;

public UserGetResponse getUserById(Long userId) {
var existUser = getUser(userId);
Expand Down Expand Up @@ -56,6 +58,13 @@ public void addFriends(Long myId, Long friendId) {
me.addFriend(friend);
}

@Transactional
public void withdraw(Long userId) {
var existUser = getUser(userId);
deleteUser(existUser);
redisService.deleteData(String.valueOf(userId));
}

public FriendsResponse findFriendsByUserId(Long userId) {
var user = getUser(userId);
var friends = userRepository.findAllById(user.getFriends());
Expand All @@ -78,4 +87,8 @@ public InterestQuestionResponse getInterestQuestionByUserIds(List<Long> userIds)

return interestQuestion.getQuestion(users);
}

private void deleteUser(User user) {
this.userRepository.delete(user);
}
}
7 changes: 7 additions & 0 deletions src/test/java/net/teumteum/integration/Api.java
Original file line number Diff line number Diff line change
Expand Up @@ -126,4 +126,11 @@ ResponseSpec reissueJwt(String accessToken, String refreshToken) {
.header("Authorization-refresh", refreshToken)
.exchange();
}

ResponseSpec withdrawUser(String accessToken) {
return webTestClient.delete()
.uri("/users/withdraw")
.header(HttpHeaders.AUTHORIZATION, accessToken)
.exchange();
}
}
20 changes: 20 additions & 0 deletions src/test/java/net/teumteum/integration/UserIntegrationTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -194,4 +194,24 @@ void Return_empty_friends_when_received_empty_friends_user_id() {
.usingRecursiveComparison().isEqualTo(expected);
}
}

@Nested
@DisplayName("회원 탈퇴 API는")
class Withdraw_user {

@Test
@DisplayName("현재 로그인한 회원의 회원 탈퇴를 정상적으로 진행한다.")
void Return_200_ok_when_withdraw_current_user() {
// given
var me = repository.saveAndGetUser();

loginContext.setUserId(me.getId());

// when
var result = api.withdrawUser(VALID_TOKEN);

// then
Assertions.assertThat(result.expectStatus().isOk());
}
}
}
3 changes: 3 additions & 0 deletions src/test/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ spring.security.oauth2.client.provider.kakao.authorization-uri=https://kauth.kak
spring.security.oauth2.client.provider.kakao.token-uri=https://kauth.kakao.com/oauth/authorize
spring.security.oauth2.client.provider.kakao.user-info-uri=https://kauth.kakao.com/oauth/authorize
spring.security.oauth2.client.provider.kakao.user-name-attribute=https://kauth.kakao.com/oauth/authorize
### Redis ###
spring.data.redis.host=localhost
spring.data.redis.port=6379



Loading