Skip to content

Commit

Permalink
Merge pull request #73 from OnAndOff-UMC/feat/#66
Browse files Browse the repository at this point in the history
[feat]: 회원탈퇴 API 구현
  • Loading branch information
wu-seong authored Feb 11, 2024
2 parents bf5ef9d + 9fd17d7 commit 29177da
Show file tree
Hide file tree
Showing 21 changed files with 223 additions and 48 deletions.
28 changes: 28 additions & 0 deletions src/main/java/com/onnoff/onnoff/auth/config/FilterConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.onnoff.onnoff.auth.config;

import com.onnoff.onnoff.auth.jwt.filter.JwtAuthFilter;
import com.onnoff.onnoff.auth.jwt.filter.UserInterceptor;
import com.onnoff.onnoff.auth.jwt.service.JwtUtil;
import com.onnoff.onnoff.auth.jwt.service.TokenProvider;
import com.onnoff.onnoff.domain.user.service.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@RequiredArgsConstructor
public class FilterConfig {
private final TokenProvider tokenProvider;
private final UserService userService;
private final JwtUtil jwtUtil;

@Bean
public JwtAuthFilter jwtAuthFilter() {
return new JwtAuthFilter(tokenProvider);
}

@Bean
public UserInterceptor userInterceptor() {
return new UserInterceptor(userService, jwtUtil);
}
}
12 changes: 4 additions & 8 deletions src/main/java/com/onnoff/onnoff/auth/config/WebConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,6 @@

import com.onnoff.onnoff.auth.jwt.filter.JwtAuthFilter;
import com.onnoff.onnoff.auth.jwt.filter.UserInterceptor;
import com.onnoff.onnoff.auth.jwt.service.JwtTokenProvider;
import com.onnoff.onnoff.auth.jwt.service.JwtUtil;
import com.onnoff.onnoff.domain.user.service.UserService;
import jakarta.persistence.EntityManagerFactory;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
Expand All @@ -18,14 +15,13 @@
@Configuration
@RequiredArgsConstructor
public class WebConfig implements WebMvcConfigurer {
private final JwtTokenProvider jwtTokenProvider;
private final JwtUtil jwtUtil;
private final UserService userService;
private final JwtAuthFilter jwtAuthFilter;
private final EntityManagerFactory entityManagerFactory;
private final UserInterceptor userInterceptor;
@Bean
public FilterRegistrationBean<JwtAuthFilter> jwtFilterRegistration() {
FilterRegistrationBean<JwtAuthFilter> registration = new FilterRegistrationBean<>();
registration.setFilter(new JwtAuthFilter(jwtTokenProvider)); // 필터 인스턴스 설정
registration.setFilter(jwtAuthFilter); // 필터 인스턴스 설정
registration.addUrlPatterns("/*"); //서블릿 컨택스트에서 /*는 모든 요청, /**는 인식되지 않음
registration.setOrder(1); // 필터의 순서 설정. 값이 낮을수록 먼저 실행
return registration;
Expand All @@ -37,7 +33,7 @@ public void addInterceptors(InterceptorRegistry registry) {
openEntityManagerInViewInterceptor.setEntityManagerFactory(entityManagerFactory);
registry.addWebRequestInterceptor(openEntityManagerInViewInterceptor);

registry.addInterceptor(new UserInterceptor(userService, jwtUtil))
registry.addInterceptor(userInterceptor)
.addPathPatterns("/**") // 스프링 경로는 /*와 /**이 다름
.excludePathPatterns("/swagger-ui/**", "/v3/api-docs/**", "/oauth2/**", "/health", "/token/**" ,
"/message/**", "/enums/**", "/users/nickname");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
import com.onnoff.onnoff.auth.feignClient.dto.TokenResponse;
import com.onnoff.onnoff.auth.feignClient.dto.kakao.KakaoOauth2DTO;
import com.onnoff.onnoff.auth.jwt.dto.JwtToken;
import com.onnoff.onnoff.auth.jwt.service.JwtTokenProvider;
import com.onnoff.onnoff.auth.jwt.service.JwtUtil;
import com.onnoff.onnoff.auth.jwt.service.TokenProvider;
import com.onnoff.onnoff.auth.service.AppleLoginService;
import com.onnoff.onnoff.auth.service.KakaoLoginService;
import com.onnoff.onnoff.domain.user.User;
Expand All @@ -34,7 +34,7 @@ public class LoginController {
private final KakaoLoginService kakaoLoginService;
private final AppleLoginService appleLoginService;
private final UserService userService;
private final JwtTokenProvider jwtTokenProvider;
private final TokenProvider tokenProvider;
private final JwtUtil jwtUtil;

@Value("${kakao.redirect-uri}")
Expand Down Expand Up @@ -135,14 +135,14 @@ public ApiResponse<UserResponseDTO.LoginDTO> validateAppleToken(@RequestBody Log
public ApiResponse<UserResponseDTO.LoginDTO> validateServerToken(@RequestBody JwtToken tokenDTO){
String accessToken = tokenDTO.getAccessToken();
String refreshToken = tokenDTO.getRefreshToken();
if( jwtTokenProvider.verifyToken(accessToken) ){
if( tokenProvider.verifyToken(accessToken) ){
// accessToken 유효
String userId = jwtUtil.getUserId(accessToken);
User user = userService.getUser(Long.valueOf(userId));
UserResponseDTO.LoginDTO loginDTO = UserConverter.toLoginDTO(accessToken, refreshToken);
return ApiResponse.onSuccess(loginDTO);
}
if (jwtTokenProvider.verifyToken(refreshToken)) {
if ( tokenProvider.verifyToken(refreshToken)) {
//refreshToken 유효
String userId = jwtUtil.getUserId(refreshToken);
User user = userService.getUser(Long.valueOf(userId));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public static class KakaoTokenValidateDTO{

@Getter
public static class AdditionalInfo{
private String nickname;
private String fieldOfWork;
private String job;
private String experienceYear;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
package com.onnoff.onnoff.auth.feignClient.client;


import com.onnoff.onnoff.auth.feignClient.config.FeignConfig;
import com.onnoff.onnoff.auth.feignClient.dto.JwkResponse;
import com.onnoff.onnoff.auth.feignClient.dto.TokenResponse;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;

import java.util.Map;

@FeignClient(name = "apple-auth-client",url = "https://appleid.apple.com/auth")
@FeignClient(name = "apple-auth-client",url = "https://appleid.apple.com/auth", configuration = FeignConfig.class)
public interface AppleAuthClient{
@GetMapping("/keys")
JwkResponse.JwkSet getKeys();
Expand All @@ -20,6 +20,6 @@ public interface AppleAuthClient{
TokenResponse getToken(@RequestBody Map<String, ?> requestBody);

//회원 탈퇴 메서드
// @GetMapping("/revoke")
// KakaoOauth2DTO.TokenValidateResponseDTO getTokenValidate(@RequestHeader("Authorization") String accessToken);
@PostMapping(value ="/oauth2/v2/revoke", consumes = "application/x-www-form-urlencoded")
void revokeTokens(@RequestBody Map<String, ?> requestBody);
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@
import com.onnoff.onnoff.auth.feignClient.config.FeignConfig;
import com.onnoff.onnoff.auth.feignClient.dto.kakao.KakaoOauth2DTO;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.Map;

/*
토큰 유효성 검증 하고 사용자 정보 가져오는 client
*/
Expand All @@ -15,5 +18,7 @@ public interface KakaoApiClient {
KakaoOauth2DTO.TokenValidateResponseDTO getTokenValidate(@RequestHeader("Authorization") String accessToken);
@GetMapping(value = "/v1/oidc/userinfo")
KakaoOauth2DTO.UserInfoResponseDTO getUserInfo(@RequestHeader("Authorization") String accessToken);
@PostMapping(value = "/v1/user/unlink", consumes = "application/x-www-form-urlencoded")
ResponseEntity unlink(@RequestHeader("Authorization") String adminKey, @RequestBody Map<String, ?> requestBody);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.onnoff.onnoff.auth.feignClient.dto.apple;


import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;

@Getter
@Builder
@AllArgsConstructor
public class RevokeTokenReqeust {
private String clientId;
private String clientSecret;
private String token;
private String tokenTypeHint;

public MultiValueMap<String, String> toUrlEncoded(){
LinkedMultiValueMap<String, String> urlEncoded = new LinkedMultiValueMap<>();
urlEncoded.add("client_id", clientId);
urlEncoded.add("client_secret", clientSecret);
urlEncoded.add("token", token);
urlEncoded.add("token_type_hint", "refresh_token");
return urlEncoded;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.onnoff.onnoff.auth.feignClient.dto.kakao;


import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;

@Builder
@AllArgsConstructor
public class UnlinkRequest {
private String targetIdType;
private String targetId;

public MultiValueMap<String, String> toUrlEncoded(){
LinkedMultiValueMap<String, String> urlEncoded = new LinkedMultiValueMap<>();
urlEncoded.add("target_id_type", targetIdType);
urlEncoded.add("target_id", targetId);
return urlEncoded;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,22 @@
*/


import com.onnoff.onnoff.auth.jwt.service.JwtTokenProvider;
import com.onnoff.onnoff.auth.jwt.service.TokenProvider;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;

@Slf4j
@RequiredArgsConstructor
public class JwtAuthFilter extends OncePerRequestFilter {
private final JwtTokenProvider jwtTokenProvider;
private final TokenProvider tokenProvider;
private final static String[] ignorePrefix = {"/swagger-ui", "/v3/api-docs", "/oauth2", "/health", "/token/validate" , "/message", "/enums", "/users/nickname"};
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
Expand All @@ -43,7 +42,7 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse
if (authHeader != null && authHeader.startsWith("Bearer ")) {
accessToken = authHeader.substring(7);
}
if (jwtTokenProvider.verifyToken(accessToken)){
if (tokenProvider.verifyToken(accessToken)){
log.info("인증성공");
filterChain.doFilter(request, response);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
@Slf4j
@Service
@RequiredArgsConstructor
public class JwtTokenProvider {
public class JwtTokenProvider implements TokenProvider {
@Value("${spring.jwt.secret}")
private String secret;
private SecretKey secretkey;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.onnoff.onnoff.auth.jwt.service;

public interface TokenProvider {
boolean verifyToken(String token);
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import com.onnoff.onnoff.auth.UserContext;
import com.onnoff.onnoff.auth.feignClient.client.AppleAuthClient;
import com.onnoff.onnoff.auth.feignClient.dto.apple.RevokeTokenReqeust;
import com.onnoff.onnoff.auth.feignClient.dto.apple.TokenRequest;
import com.onnoff.onnoff.auth.feignClient.dto.TokenResponse;
import com.onnoff.onnoff.auth.service.tokenValidator.SocialTokenValidator;
Expand All @@ -27,8 +28,6 @@
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

@Service
@RequiredArgsConstructor
Expand Down Expand Up @@ -116,4 +115,15 @@ public void validate(String identityToken){
String cleanedIdentityToken = cleanToken(identityToken);
validator.validate(cleanedIdentityToken, SocialType.APPLE);
}

public void revokeTokens(String refreshToken) {
String clientSecret = createClientSecret();
MultiValueMap<String, String> urlEncoded = RevokeTokenReqeust.builder()
.clientId(clientId)
.clientSecret(clientSecret)
.token(refreshToken)
.build().toUrlEncoded();
appleAuthClient.revokeTokens(urlEncoded);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,15 @@
import com.onnoff.onnoff.auth.feignClient.client.KakaoOauth2Client;
import com.onnoff.onnoff.auth.feignClient.dto.TokenResponse;
import com.onnoff.onnoff.auth.feignClient.dto.kakao.KakaoOauth2DTO;
import com.onnoff.onnoff.auth.feignClient.dto.kakao.UnlinkRequest;
import com.onnoff.onnoff.auth.service.tokenValidator.SocialTokenValidator;
import com.onnoff.onnoff.domain.user.enums.SocialType;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.util.MultiValueMap;


@Service
Expand All @@ -25,6 +28,8 @@ public class KakaoLoginService implements LoginService{
private String clientId;
@Value("${kakao.redirect-uri}")
private String redirectUri;
@Value("${kakao.admin-key}")
private String adminKey;
/*
테스트 용으로 만든거, 실제로는 프론트에서 처리해서 액세스 토큰만 가져다 줌
*/
Expand All @@ -41,9 +46,22 @@ public void validate(String idToken){
String cleanedAccessToken = cleanToken(idToken);
validator.validate(cleanedAccessToken, SocialType.KAKAO);
}
/*
토큰으로 유저정보를 가져오는 메서드
*/

public void revokeTokens(String oauthId) {
MultiValueMap<String, String> urlEncoded = UnlinkRequest.builder()
.targetIdType("user_id")
.targetId(oauthId)
.build()
.toUrlEncoded();
adminKey = "KakaoAK " + adminKey;
log.info("adminkey = {}", adminKey);
ResponseEntity responseEntity = kakaoApiClient.unlink(adminKey, urlEncoded);
log.info("삭제된 회원 정보 = {}", responseEntity.getBody());
}

/*
토큰으로 유저정보를 가져오는 메서드
*/
public KakaoOauth2DTO.UserInfoResponseDTO getUserInfo(String accessToken) throws JsonProcessingException {
String cleanedAccessToken = cleanToken(accessToken);
accessToken = "bearer " + cleanedAccessToken;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,14 @@ public ApiResponse<UserResponseDTO.UserDetailDTO> modifyUser(@RequestBody UserRe
return ApiResponse.onSuccess(UserConverter.toUserDetailDTO(userService.modifyUser(modifyUserDTO)));
}

//테스트용
@PutMapping("/hard-delete")
@Operation(summary = "회원 완전 탈퇴 테스트 API",description = "30일 뒤에 자동 완전삭제 수동 테스트")
public ApiResponse<String> hardDeleteTest(){
userService.deleteInactiveUsersTest();
return ApiResponse.onSuccess("삭제완");
}

@PostMapping("/nickname")
@Operation(summary = "닉네임 중복 체크 API")
public ApiResponse<String> checkNickname(@Valid @RequestBody UserRequestDTO.getNicknameDTO nicknameDTO){
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,34 +14,28 @@

public class UserConverter {
public static User toUser(KakaoOauth2DTO.UserInfoResponseDTO response, LoginRequestDTO.AdditionalInfo additionalInfo){
String fieldOfWork = additionalInfo.getFieldOfWork();
try{
FieldOfWork.valueOf(fieldOfWork);
}
catch (IllegalArgumentException e){
throw new GeneralException(ErrorStatus.INVALID_ENUM_VALUE);
}

FieldOfWork fieldOfWork = FieldOfWork.fromValue(additionalInfo.getFieldOfWork());
ExperienceYear experienceYear = ExperienceYear.fromValue(additionalInfo.getExperienceYear());
return User.builder()
.oauthId(response.getSub())
.email(response.getEmail())
.name(response.getNickname())
.socialType(SocialType.KAKAO)
.fieldOfWork(Enum.valueOf(FieldOfWork.class ,additionalInfo.getFieldOfWork() ) )
.fieldOfWork(fieldOfWork)
.job(additionalInfo.getJob())
.experienceYear(experienceYear)
.build();
}
public static User toUser(LoginRequestDTO.AppleTokenValidateDTO request, LoginRequestDTO.AdditionalInfo additionalInfo){
FieldOfWork fieldOfWork = FieldOfWork.fromValue(additionalInfo.getFieldOfWork());
ExperienceYear experienceYear = ExperienceYear.fromValue(additionalInfo.getExperienceYear());
String fullName = request.getFullName().getFamilyName() + request.getFullName().getGivenName();
return User.builder()
.oauthId(request.getOauthId())
.email(request.getEmail())
.name(fullName)
.socialType(SocialType.APPLE)
.fieldOfWork(Enum.valueOf(FieldOfWork.class ,additionalInfo.getFieldOfWork() ) )
.fieldOfWork(fieldOfWork)
.job(additionalInfo.getJob())
.experienceYear(experienceYear)
.build();
Expand Down
Loading

0 comments on commit 29177da

Please sign in to comment.