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]: 통계 API 구현 #47

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -315,5 +315,4 @@ gradle-app.setting
# Java heap dump
*.hprof

**/resources/*
# End of https://www.toptal.com/developers/gitignore/api/macos,intellij,intellij+iml,intellij+all,gradle,java
2 changes: 0 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,6 @@ dependencies {
implementation 'io.jsonwebtoken:jjwt-api:0.12.3'
implementation 'io.jsonwebtoken:jjwt-jackson:0.12.3'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.3'
//FCM
implementation 'com.google.firebase:firebase-admin:9.2.0'
}

tasks.named('bootBuildImage') {
Expand Down
2 changes: 0 additions & 2 deletions src/main/java/com/onnoff/onnoff/OnnoffApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,10 @@
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.scheduling.annotation.EnableScheduling;

@EnableFeignClients(defaultConfiguration = FeignConfig.class)
@SpringBootApplication
@EnableJpaAuditing
@EnableScheduling
public class OnnoffApplication {

public static void main(String[] args) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,6 @@ public enum ErrorStatus implements BaseErrorCode {
ANSWER_NOT_FOUND(HttpStatus.BAD_REQUEST, "ANSWER4001", "해당하는 회고 답변이 없습니다."),
ANSWER_BAD_MATCH(HttpStatus.BAD_REQUEST, "ANSWER4002", "해당하는 회고에 속하는 회고 답변이 아닙니다."),

// 이모티콘 관련 에러
EMOTICON_NOT_FOUND(HttpStatus.BAD_REQUEST, "EMOTICON4001", "해당하는 이모티콘이 없습니다."),

// 피드 관련 에러
FEED_NOT_FOUND(HttpStatus.BAD_REQUEST, "FEED4001", "해당하는 워라벨 피드가 없습니다."),
FEED_NOT_BLANK(HttpStatus.BAD_REQUEST, "FEED4002", "워라벨 피드 내용은 공백일 수 없습니다."),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@
@AllArgsConstructor
public enum SuccessStatus implements BaseCode {
// 일반적인 응답
_OK(HttpStatus.OK, "COMMON200", "성공입니다.");
_OK(HttpStatus.OK, "COMMON200", "성공입니다."),

// 로그인 응답
NEED_USER_DETAIL(HttpStatus.OK, "LOGIN200", "토큰이 유효하고 유저의 추가정보가 필요합니다.");
private final HttpStatus httpStatus;
private final String code;
private final String message;
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/com/onnoff/onnoff/auth/config/WebConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,6 @@ public void addInterceptors(InterceptorRegistry registry) {

registry.addInterceptor(new UserInterceptor(userService, jwtUtil))
.addPathPatterns("/**") // 스프링 경로는 /*와 /**이 다름
.excludePathPatterns("/swagger-ui/**", "/v3/api-docs/**", "/oauth2/**", "/on/**", "/health", "/token/**" , "/message/**");
.excludePathPatterns("/swagger-ui/**", "/v3/api-docs/**", "/oauth2/**", "/on/**", "/health", "/stats/**", "/feeds", "/memoirs");
}
}
132 changes: 42 additions & 90 deletions src/main/java/com/onnoff/onnoff/auth/controller/LoginController.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,23 @@

import com.fasterxml.jackson.core.JsonProcessingException;
import com.onnoff.onnoff.apiPayload.ApiResponse;
import com.onnoff.onnoff.apiPayload.code.status.ErrorStatus;
import com.onnoff.onnoff.apiPayload.exception.GeneralException;
import com.onnoff.onnoff.apiPayload.code.status.SuccessStatus;
import com.onnoff.onnoff.auth.UserContext;
import com.onnoff.onnoff.auth.dto.LoginRequestDTO;
import com.onnoff.onnoff.auth.feignClient.dto.TokenResponse;
import com.onnoff.onnoff.auth.feignClient.dto.kakao.KakaoOauth2DTO;
import com.onnoff.onnoff.auth.feignClient.dto.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.service.AppleLoginService;
import com.onnoff.onnoff.auth.service.KakaoLoginService;
import com.onnoff.onnoff.auth.service.LoginService;
import com.onnoff.onnoff.domain.user.User;
import com.onnoff.onnoff.domain.user.converter.UserConverter;
import com.onnoff.onnoff.domain.user.dto.UserResponseDTO;
import com.onnoff.onnoff.domain.user.service.UserService;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.InvalidClaimException;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.UnsupportedJwtException;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
Expand All @@ -36,123 +29,82 @@
@Controller
@RequiredArgsConstructor
public class LoginController {
private final KakaoLoginService kakaoLoginService;
private final AppleLoginService appleLoginService;
private final LoginService loginService;
private final UserService userService;
private final JwtTokenProvider jwtTokenProvider;
private final JwtUtil jwtUtil;

@Value("${kakao.redirect-uri}")
private String redirectUri;



/*
테스트용 API, CORS 때문에 직접 호출하지 않고 redirect
*/
@GetMapping("/oauth2/authorize/kakao")
public String login(){
String toRedirectUri = UriComponentsBuilder.fromUriString("https://kauth.kakao.com/oauth/authorize")
String redirectUri = UriComponentsBuilder.fromUriString("https://kauth.kakao.com/oauth/authorize")
.queryParam("response_type", "code")
.queryParam("client_id", "32c0787d1b1e9fcabcc24af247903ba8")
.queryParam("redirect_uri", redirectUri)
.queryParam("redirect_uri", "http://localhost:8080/oauth2/login/kakao")
.toUriString();
return "redirect:" + toRedirectUri;
return "redirect:" + redirectUri;
}
/*
테스트용 API
*/
@GetMapping("/oauth2/login/kakao")
public ResponseEntity<String> getAccessToken(@RequestParam(name = "code") String code){
TokenResponse tokenResponse = kakaoLoginService.getAccessTokenByCode(code);
return ResponseEntity.ok("accessToken="+ tokenResponse.getAccessToken() +
"\n\nidToken=" + tokenResponse.getIdToken());
String accessToken = loginService.getAccessToken(code);
return ResponseEntity.ok("http://localhost:8080/oauth2/kakao/token/validate?accessToken="+accessToken);
}
/*
1. ID 토큰 유효성 검증
1. 토큰 유효성 검증
2. 사용자 정보 얻어오기
3. DB 조회 및 추가
4. 응답 헤더에 Jwt 토큰 추가
*/

@Operation(summary = "소셜 토큰 검증 API",description = "토큰을 검증 하고 이에 대한 결과를 응답합니다. 추가 정보 입력 여부도 같이 응답 합니다.")
@Operation(summary = "토큰 검증 API",description = "토큰을 검증 하고 이에 대한 결과를 응답합니다. 추가 정보 입력 여부도 같이 응답 합니다.")
@ApiResponses(value = {
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "202", description = "토큰 검증 성공," + " 추가 정보 기입이 필요합니다.",
content = @Content(schema = @Schema(implementation = UserResponseDTO.ApiResponseLoginDTO.class))),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "토큰 검증 성공",
content = @Content(schema = @Schema(implementation = UserResponseDTO.ApiResponseUserDetailDTO.class)))
})
@ResponseBody
@PostMapping("/oauth2/kakao/token/validate")
public ApiResponse<UserResponseDTO.LoginDTO> validateKakoToken(HttpServletResponse response, @RequestBody LoginRequestDTO.KakaoTokenValidateDTO requestDTO) {
// identity 토큰 검증
kakaoLoginService.validate(requestDTO.getIdentityToken());
public ApiResponse<?> validateToken(HttpServletResponse response, @RequestBody String accessToken) {
// 토큰 검증
loginService.validate(accessToken);
// ok -> 유저 정보 가져오기
KakaoOauth2DTO.UserInfoResponseDTO userInfo;
KakaoOauth2DTO.UserInfoResponseDTO userInfoResponseDTO = null;
try {
userInfo = kakaoLoginService.getUserInfo(requestDTO.getAccessToken());
userInfoResponseDTO = loginService.getUserInfo(accessToken);
} catch (JsonProcessingException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
// 유저 정보에 DB 조회하고 정보 있으면 응답만, 없으면 저장까지, 추가정보 입력 여부에 따라서 응답 다르게
String oauthId = userInfo.getSub();
User user;
if( userService.isExistByOauthId(oauthId)){
user = userService.getUserByOauthId(oauthId);
}
else{
user = UserConverter.toUser(userInfo);
user = userService.create(user);
}
// 응답본문에 토큰 추가
JwtToken token = jwtUtil.generateToken(String.valueOf(user.getId()));
return ApiResponse.onSuccess(UserConverter.toLoginDTO(user, token.getAccessToken(), token.getRefreshToken()));
}

@ResponseBody
@PostMapping("/oauth2/apple/token/validate")
public ApiResponse<UserResponseDTO.LoginDTO> validateAppleToken(HttpServletResponse response, @RequestBody LoginRequestDTO.AppleTokenValidateDTO requestDTO) {
// 검증하기
appleLoginService.validate(requestDTO.getIdentityToken());
// 검증 성공 시 리프레시 토큰 발급받아 저장(기한 무제한, 회원탈퇴 시 필요)
TokenResponse tokenResponse = appleLoginService.getAccessTokenByCode(requestDTO.getAuthorizationCode());
// 유저 정보 조회 및 저장
String oauthId = requestDTO.getOauthId();
User user;
Long oauthId = userInfoResponseDTO.getId();
if( userService.isExistByOauthId(oauthId)){
user = userService.getUserByOauthId(oauthId);
User user = userService.getUserByOauthId(oauthId);
// 응답헤더에 토큰 추가
JwtToken token = jwtUtil.generateToken(String.valueOf(user.getId()));
response.addHeader("Access-Token", token.getAccessToken());
response.addHeader("Refresh-Token", token.getRefreshToken());
if(user.isInfoSet()){
return ApiResponse.onSuccess(UserConverter.toUserDetailDTO(user));
}
return ApiResponse.of(SuccessStatus.NEED_USER_DETAIL, UserConverter.toLoginDTO(user));
}
else{
user = UserConverter.toUser(requestDTO);
user.setAppleRefreshToken(tokenResponse.getRefreshToken());
user = userService.create(user);
User user = UserConverter.toUser(userInfoResponseDTO);
Long id = userService.create(user);
// 응답헤더에 토큰 추가
JwtToken token = jwtUtil.generateToken(String.valueOf(id));
response.addHeader("Access-Token", token.getAccessToken());
response.addHeader("Refresh-Token", token.getRefreshToken());
return ApiResponse.of(SuccessStatus.NEED_USER_DETAIL, UserConverter.toLoginDTO(user));
}
// 응답헤더에 토큰 추가
JwtToken token = jwtUtil.generateToken(String.valueOf(user.getId()));
return ApiResponse.onSuccess(UserConverter.toLoginDTO(user, token.getAccessToken(), token.getRefreshToken()));
}

@Operation(summary = "서버 토큰 검증 API",description = "토큰의 유효성을 검증하고 액세스 토큰이 만료되었으면" +
"재발급, 리프레시 토큰까지 만료되었으면 오류 응답 추가. 정보 입력 여부도 같이 응답 합니다.")
@ResponseBody
@PostMapping("/token/validate")
public ApiResponse<UserResponseDTO.LoginDTO> validateServerToken(@RequestBody JwtToken tokenDTO){
String accessToken = tokenDTO.getAccessToken();
String refreshToken = tokenDTO.getRefreshToken();
if( jwtTokenProvider.verifyToken(accessToken) ){
// accessToken 유효
String userId = jwtUtil.getUserId(accessToken);
User user = userService.getUser(Long.valueOf(userId));
UserResponseDTO.LoginDTO loginDTO = UserConverter.toLoginDTO(user, accessToken, refreshToken);
return ApiResponse.onSuccess(loginDTO);
}
if (jwtTokenProvider.verifyToken(refreshToken)) {
//refreshToken 유효
String userId = jwtUtil.getUserId(refreshToken);
User user = userService.getUser(Long.valueOf(userId));

String newRefreshToken = jwtUtil.generateRefreshToken(jwtUtil.getUserId(refreshToken));
String newAccessToken = jwtUtil.generateAccessToken(jwtUtil.getUserId(refreshToken));
return ApiResponse.onSuccess(UserConverter.toLoginDTO(user, newAccessToken, newRefreshToken));
}
// 둘 다 유효하지 않은 경우
throw new GeneralException(ErrorStatus.INVALID_TOKEN_ERROR);
}
/*
테스트용 API
*/
Expand Down
21 changes: 0 additions & 21 deletions src/main/java/com/onnoff/onnoff/auth/dto/LoginRequestDTO.java

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import com.onnoff.onnoff.apiPayload.code.status.ErrorStatus;
import com.onnoff.onnoff.apiPayload.exception.GeneralException;
import com.onnoff.onnoff.auth.feignClient.dto.kakao.ErrorResponseDTO;
import com.onnoff.onnoff.auth.feignClient.dto.ErrorResponseDTO;
import feign.Response;
import feign.codec.ErrorDecoder;
import lombok.extern.slf4j.Slf4j;
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
package com.onnoff.onnoff.auth.feignClient.client;

import com.onnoff.onnoff.auth.feignClient.config.FeignConfig;
import com.onnoff.onnoff.auth.feignClient.dto.kakao.KakaoOauth2DTO;
import com.onnoff.onnoff.auth.feignClient.dto.KakaoOauth2DTO;
import feign.Headers;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;

/*
토큰 유효성 검증 하고 사용자 정보 가져오는 client
토큰 유효성 검증하고 사용자 정보 가져오는 client
*/

@FeignClient(name = "kakao-api-client", url = "https://kapi.kakao.com", configuration = FeignConfig.class)
public interface KakaoApiClient {
@GetMapping("v1/user/access_token_info")
KakaoOauth2DTO.TokenValidateResponseDTO getTokenValidate(@RequestHeader("Authorization") String accessToken);
@GetMapping(value = "/v1/oidc/userinfo")
KakaoOauth2DTO.UserInfoResponseDTO getUserInfo(@RequestHeader("Authorization") String accessToken);
@GetMapping(value = "/v2/user/me")
KakaoOauth2DTO.UserInfoResponseDTO getUserInfo(@RequestHeader("Authorization") String accessToken, @RequestParam(name = "property_keys") String propertyKeys);

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@


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 com.onnoff.onnoff.auth.feignClient.dto.kakao.KakaoOauth2DTO;
import com.onnoff.onnoff.auth.feignClient.dto.KakaoOauth2DTO;
import feign.Headers;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;
Expand All @@ -13,11 +11,10 @@
public interface KakaoOauth2Client {
@Headers("Content-Type: application/x-www-form-urlencoded")
@PostMapping("/oauth/token")
TokenResponse getAccessToken(@RequestParam(name = "grant_type") String grantType,
@RequestParam(name = "client_id") String clientId,
@RequestParam(name = "redirect_uri") String redirectUri,
@RequestParam(name = "code") String code
KakaoOauth2DTO.TokenResponseDTO getAccessToken(@RequestParam(name = "grant_type") String grantType,
@RequestParam(name = "client_id") String clientId,
@RequestParam(name = "redirect_uri") String redirectUri,
@RequestParam(name = "code") String code
);
@GetMapping("/.well-known/jwks.json")
JwkResponse.JwkSet getKeys();

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


import lombok.Getter;
Expand Down

This file was deleted.

Loading