Skip to content

Commit

Permalink
Merge branch 'develop' into fix/#79-seed-lock-scheduling
Browse files Browse the repository at this point in the history
  • Loading branch information
yeseul106 authored Jan 23, 2024
2 parents d4741a4 + bd39087 commit 9b3b58b
Show file tree
Hide file tree
Showing 30 changed files with 772 additions and 26 deletions.
2 changes: 2 additions & 0 deletions growthookServer/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ dependencies {

testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'

implementation 'com.google.code.gson:gson:2.10.1'
}

tasks.named('bootBuildImage') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import com.example.growthookserver.common.response.ApiResponse;
import com.example.growthookserver.common.response.SuccessStatus;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
Expand All @@ -18,6 +19,7 @@
@RestController
@RequiredArgsConstructor
@RequestMapping("api/v1")
@SecurityRequirement(name = "JWT Authentication")
@Tag(name = "AciontPlan - 액션플랜 관련 API",description = "AcitonPlan APi Documnet")
public class ActionPlanController {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,6 @@ public class ActionPlanGetResponseDto {

private Boolean isFinished;

private Boolean isReviewed;
private Boolean hasReview;

}
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@ public class DoingActionPlanGetResponseDto {

private Long seedId;

private Boolean isReviewed;
private Boolean hasReview;

}
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@ public class FinishedActionPlanGetResponseDto {

private Long seedId;

private Boolean isReviewed;
private Boolean hasReview;

}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import com.example.growthookserver.common.response.ApiResponse;
import com.example.growthookserver.common.response.SuccessStatus;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
Expand All @@ -18,6 +19,7 @@
@RestController
@RequiredArgsConstructor
@RequestMapping("api/v1")
@SecurityRequirement(name = "JWT Authentication")
@Tag(name = "Cave - 동굴 관련 API", description = "Cave API Document")
public class CaveController {
private final CaveService caveService;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package com.example.growthookserver.api.member.auth.controller;

import com.example.growthookserver.api.member.auth.dto.Request.AuthRequestDto;
import com.example.growthookserver.api.member.auth.dto.Response.AuthResponseDto;
import com.example.growthookserver.api.member.auth.dto.Response.AuthTokenResponseDto;
import com.example.growthookserver.api.member.auth.service.AuthService;
import com.example.growthookserver.common.config.jwt.JwtTokenProvider;
import com.example.growthookserver.common.response.ApiResponse;
import com.example.growthookserver.common.response.SuccessStatus;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;

import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/v1/auth")
@RequiredArgsConstructor
@Tag(name = "Auth - 인증/인가 관련 API", description = "Auth API Document")
public class AuthController {
private final AuthService authService;
private final JwtTokenProvider jwtTokenProvider;

@PostMapping()
@ResponseStatus(HttpStatus.OK)
@Operation(summary = "SocialLogin", description = "소셜 로그인 API입니다.")
public ApiResponse<AuthResponseDto> socialLogin(@RequestBody AuthRequestDto authRequestDto) throws NoSuchAlgorithmException, InvalidKeySpecException {

AuthResponseDto responseDto = authService.socialLogin(authRequestDto);
return ApiResponse.success(SuccessStatus.SIGNIN_SUCCESS, responseDto);

}

@GetMapping("/token")
@ResponseStatus(HttpStatus.OK)
@Operation(summary = "TokenRefresh", description = "토큰 재발급 API입니다.")
public ApiResponse<AuthTokenResponseDto> getNewToken(HttpServletRequest request) {
String accessToken = (String) request.getAttribute("newAccessToken");
String refreshToken = jwtTokenProvider.resolveRefreshToken(request);

return ApiResponse.success(SuccessStatus.GET_NEW_TOKEN_SUCCESS, authService.getNewToken(accessToken, refreshToken));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.example.growthookserver.api.member.auth.dto.Request;

import static lombok.AccessLevel.PROTECTED;

import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor(access = PROTECTED)
public class AuthRequestDto {
private String socialPlatform;
private String socialToken;
private String userName;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.example.growthookserver.api.member.auth.dto.Response;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
@AllArgsConstructor(staticName = "of")
public class AuthResponseDto {

private String nickname;

private Long memberId;

private String accessToken;

private String refreshToken;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.example.growthookserver.api.member.auth.dto.Response;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
@AllArgsConstructor(staticName = "of")
public class AuthTokenResponseDto {
private String accessToken;

private String refreshToken;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.example.growthookserver.api.member.auth.dto;

import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public class SocialInfoDto {
private String id;
private String nickname;
private String email;
private String profileImage;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package com.example.growthookserver.api.member.auth.service;

import com.example.growthookserver.api.member.auth.dto.SocialInfoDto;
import com.example.growthookserver.common.exception.BaseException;
import com.google.gson.*;
import com.example.growthookserver.common.exception.UnAuthorizedException;
import com.example.growthookserver.common.response.ErrorStatus;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.math.BigInteger;
import java.net.HttpURLConnection;
import java.net.URL;

import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.RSAPublicKeySpec;
import java.util.Base64;
import java.util.Objects;
import lombok.RequiredArgsConstructor;

import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;

@Component
@RequiredArgsConstructor
public class AppleAuthService {
/**
* 1. apple로 부터 공개키 3개 가져옴
* 2. 내가 클라에서 가져온 token String과 비교해서 써야할 공개키 확인 (kid,alg 값 같은 것)
* 3. 그 공개키 재료들로 공개키 만들고, 이 공개키로 JWT토큰 부분의 바디 부분의 decode하면 유저 정보
*/
public SocialInfoDto login(String socialAccessToken, String userName) {
return getAppleSocialData(socialAccessToken, userName);
}

private JsonArray getApplePublicKeys() {
StringBuffer result = new StringBuffer();
try {
URL url = new URL("https://appleid.apple.com/auth/keys");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));

String line = "";

while ((line = br.readLine()) != null) {
result.append(line);
}
JsonObject keys = (JsonObject) JsonParser.parseString(result.toString());
return (JsonArray) keys.get("keys"); // 1. 공개키 가져오기
} catch (IOException e) {
throw new UnAuthorizedException(ErrorStatus.FAILED_TO_VALIDATE_APPLE_LOGIN.getMessage());
}
}

private SocialInfoDto getAppleSocialData(String socialAccessToken, String userName) {
try {
JsonArray publicKeyList = getApplePublicKeys();
PublicKey publicKey = makePublicKey(socialAccessToken, publicKeyList);

Claims userInfo = Jwts.parserBuilder()
.setSigningKey(publicKey)
.build()
.parseClaimsJws(socialAccessToken.substring(7))
.getBody();

JsonObject userInfoObject = (JsonObject) JsonParser.parseString(new Gson().toJson(userInfo));
String appleId = userInfoObject.get("sub").getAsString();
String email = userInfoObject.get("email").getAsString();

return new SocialInfoDto(appleId, userName, email, null);
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
throw new BaseException(HttpStatus.INTERNAL_SERVER_ERROR, "애플 계정 데이터 가공 실패");
}
}

private PublicKey makePublicKey(String identityToken, JsonArray publicKeyList) throws NoSuchAlgorithmException, InvalidKeySpecException {
JsonObject selectedObject = null;

String[] decodeArray = identityToken.split("\\.");
String header = new String(Base64.getDecoder().decode(decodeArray[0].substring(7)));

JsonElement kid = ((JsonObject) JsonParser.parseString(header)).get("kid");
JsonElement alg = ((JsonObject) JsonParser.parseString(header)).get("alg");

for (JsonElement publicKey : publicKeyList) {
JsonObject publicKeyObject = publicKey.getAsJsonObject();
JsonElement publicKid = publicKeyObject.get("kid");
JsonElement publicAlg = publicKeyObject.get("alg");

if (Objects.equals(kid, publicKid) && Objects.equals(alg, publicAlg)) {
selectedObject = publicKeyObject;
break;
}
}

if (selectedObject == null) {
throw new InvalidKeySpecException("공개키를 찾을 수 없습니다.");
}

return getPublicKey(selectedObject);
}

private PublicKey getPublicKey(JsonObject object) throws NoSuchAlgorithmException, InvalidKeySpecException {
String nStr = object.get("n").toString();
String eStr = object.get("e").toString();

byte[] nBytes = Base64.getUrlDecoder().decode(nStr.substring(1, nStr.length() - 1));
byte[] eBytes = Base64.getUrlDecoder().decode(eStr.substring(1, eStr.length() - 1));

BigInteger n = new BigInteger(1, nBytes);
BigInteger e = new BigInteger(1, eBytes);


RSAPublicKeySpec publicKeySpec = new RSAPublicKeySpec(n, e);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey publicKey = keyFactory.generatePublic(publicKeySpec);
return publicKey;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.example.growthookserver.api.member.auth.service;

import com.example.growthookserver.api.member.auth.dto.Request.AuthRequestDto;
import com.example.growthookserver.api.member.auth.dto.Response.AuthResponseDto;
import com.example.growthookserver.api.member.auth.dto.Response.AuthTokenResponseDto;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;

public interface AuthService {
AuthResponseDto socialLogin(AuthRequestDto authRequestDto) throws NoSuchAlgorithmException, InvalidKeySpecException;

AuthTokenResponseDto getNewToken(String accessToken, String refreshToken);
}
Loading

0 comments on commit 9b3b58b

Please sign in to comment.