Skip to content

Commit 7b000e2

Browse files
authored
Merge pull request #55 from YAPP-Github/develop
[Feat] 로그인 기능 메인 서버 배포
2 parents 7b80128 + 1bab1ed commit 7b000e2

File tree

58 files changed

+1499
-120
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+1499
-120
lines changed

.github/workflows/sonarcloud.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@ on:
44
push:
55
branches:
66
- main
7+
- develop
78
pull_request:
89
branches:
910
- main
11+
- develop
1012
env:
1113
AWS_REGION: ap-northeast-2
1214

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
FROM eclipse-temurin:21
2-
ARG JAR_FILE=build/libs/time-eat-0.0.1-SNAPSHOT.jar
2+
ARG JAR_FILE=build/libs/eatda-0.0.1-SNAPSHOT.jar
33
COPY ${JAR_FILE} api.jar

build.gradle

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ plugins {
1212
id("org.sonarqube") version "6.2.0.5505"
1313
}
1414

15-
group = 'time-eat'
15+
group = 'net.eatda'
1616
version = '0.0.1-SNAPSHOT'
1717

1818
java {
@@ -124,14 +124,22 @@ generateSwaggerUI {
124124
}
125125

126126
openapi3 {
127-
servers = [ // 서버 상황에 맞춰 추가 예정
127+
servers = [
128+
{
129+
url = "https://api-dev.eatda.net"
130+
description = "Develop Server"
131+
},
132+
{
133+
url = "https://api.eatda.net"
134+
description = "Production Server"
135+
},
128136
{
129137
url = "http://localhost:8080"
130138
description = "Local Server"
131139
}
132140
]
133-
title = "Time Eat API"
134-
description = "Time Eat API 명세서"
141+
title = "EatDa API"
142+
description = "EatDa API 명세서"
135143
version = "0.0.1"
136144
format = "yaml"
137145
outputDirectory = "build/resources/main/static/docs"

settings.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
rootProject.name = 'time-eat'
1+
rootProject.name = 'eatda'

src/main/java/timeeat/client/oauth/OauthClient.java

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
import org.springframework.util.MultiValueMap;
1010
import org.springframework.web.client.RestClient;
1111
import org.springframework.web.util.UriComponentsBuilder;
12+
import timeeat.exception.BusinessErrorCode;
13+
import timeeat.exception.BusinessException;
1214

1315
@Component
1416
@EnableConfigurationProperties(OauthProperties.class)
@@ -17,27 +19,32 @@ public class OauthClient {
1719
private final RestClient restClient;
1820
private final OauthProperties properties;
1921

20-
public OauthClient(RestClient.Builder restClientBuilder, OauthProperties oauthProperties) {
22+
public OauthClient(RestClient.Builder restClientBuilder,
23+
OauthProperties oauthProperties) {
2124
this.restClient = restClientBuilder
2225
.defaultStatusHandler(HttpStatusCode::is5xxServerError, new OauthServerErrorHandler())
2326
.build();
2427
this.properties = oauthProperties;
2528
}
2629

27-
public URI getOauthLoginUrl() {
30+
public URI getOauthLoginUrl(String origin) {
31+
validateOrigin(origin);
32+
2833
return UriComponentsBuilder.fromUriString("https://kauth.kakao.com/oauth/authorize")
2934
.queryParam("client_id", properties.getClientId())
30-
.queryParam("redirect_uri", properties.getRedirectUri())
35+
.queryParam("redirect_uri", createRedirectUri(origin))
3136
.queryParam("response_type", "code")
3237
.build()
3338
.toUri();
3439
}
3540

36-
public OauthToken requestOauthToken(String code) {
41+
public OauthToken requestOauthToken(String code, String origin) {
42+
validateOrigin(origin);
43+
3744
MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
3845
body.add("grant_type", "authorization_code");
3946
body.add("client_id", properties.getClientId());
40-
body.add("redirect_uri", properties.getRedirectUri());
47+
body.add("redirect_uri", createRedirectUri(origin));
4148
body.add("code", code);
4249

4350
return restClient.post()
@@ -48,6 +55,19 @@ public OauthToken requestOauthToken(String code) {
4855
.body(OauthToken.class);
4956
}
5057

58+
private void validateOrigin(String origin) {
59+
if (!properties.isAllowedOrigin(origin)) {
60+
throw new BusinessException(BusinessErrorCode.UNAUTHORIZED_ORIGIN);
61+
}
62+
}
63+
64+
private String createRedirectUri(String origin) {
65+
return UriComponentsBuilder.fromUriString(origin)
66+
.path(properties.getRedirectPath())
67+
.build()
68+
.toString();
69+
}
70+
5171
public OauthMemberInformation requestMemberInformation(OauthToken token) {
5272
return restClient.get()
5373
.uri("https://kapi.kakao.com/v2/user/me")

src/main/java/timeeat/client/oauth/OauthMemberInformation.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,14 @@
22

33
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
44
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
5+
import timeeat.domain.member.Member;
56

67
@JsonDeserialize(using = OauthMemberInformationDeserializer.class)
78
@JsonIgnoreProperties(ignoreUnknown = true)
89
public record OauthMemberInformation(long socialId, String nickname) {
10+
11+
public Member toMember() {
12+
return new Member(Long.toString(socialId), nickname);
13+
}
914
}
1015

Lines changed: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,68 @@
11
package timeeat.client.oauth;
22

3-
import org.springframework.boot.context.properties.ConfigurationProperties;
4-
import lombok.AllArgsConstructor;
3+
import java.net.URI;
4+
import java.util.List;
55
import lombok.Getter;
6+
import org.springframework.boot.context.properties.ConfigurationProperties;
7+
import timeeat.exception.InitializeException;
68

79
@Getter
8-
@AllArgsConstructor
910
@ConfigurationProperties(prefix = "oauth")
1011
public class OauthProperties {
1112

1213
private final String clientId;
13-
private final String redirectUri;
14+
private final String redirectPath;
15+
private final List<String> allowedOrigins;
16+
17+
public OauthProperties(String clientId, String redirectPath, List<String> allowedOrigins) {
18+
validateClientId(clientId);
19+
validateRedirectPath(redirectPath);
20+
validateOrigins(allowedOrigins);
21+
22+
this.clientId = clientId;
23+
this.redirectPath = redirectPath;
24+
this.allowedOrigins = allowedOrigins;
25+
}
26+
27+
private void validateClientId(String clientId) {
28+
if (clientId == null || clientId.isBlank()) {
29+
throw new InitializeException("Client ID must not be null or empty");
30+
}
31+
}
32+
33+
private void validateRedirectPath(String redirectPath) {
34+
if (redirectPath == null || !redirectPath.startsWith("/")) {
35+
throw new InitializeException("Redirect path must not be null or start with '/'");
36+
}
37+
}
38+
39+
private void validateOrigins(List<String> origins) {
40+
if (origins == null || origins.isEmpty()) {
41+
throw new InitializeException("Allowed origins must not be null or empty");
42+
}
43+
origins.forEach(this::validateOrigin);
44+
}
45+
46+
private void validateOrigin(String origin) {
47+
URI uri;
48+
try {
49+
uri = new URI(origin);
50+
} catch (Exception e) {
51+
throw new InitializeException("Allowed origin must be a valid origin form: " + origin, e);
52+
}
53+
54+
if (uri.getScheme() == null || uri.getHost() == null || !uri.getPath().isBlank()) {
55+
throw new InitializeException("Allowed origin must be a valid origin form: " + origin);
56+
}
57+
}
58+
59+
public boolean isAllowedOrigin(String origin) {
60+
return allowedOrigins.stream()
61+
.anyMatch(allowedOrigin -> isMatchedOrigin(allowedOrigin, origin));
62+
}
63+
64+
private boolean isMatchedOrigin(String allowedOrigin, String origin) {
65+
return origin.trim().equals(allowedOrigin)
66+
|| origin.trim().equals(allowedOrigin + "/");
67+
}
1468
}

src/main/java/timeeat/client/oauth/OauthServerErrorHandler.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,14 @@
55
import org.springframework.http.client.ClientHttpResponse;
66
import org.springframework.stereotype.Component;
77
import org.springframework.web.client.RestClient.ResponseSpec.ErrorHandler;
8+
import timeeat.exception.BusinessErrorCode;
9+
import timeeat.exception.BusinessException;
810

911
@Component
1012
public class OauthServerErrorHandler implements ErrorHandler {
1113

1214
@Override
1315
public void handle(HttpRequest request, ClientHttpResponse response) throws IOException {
14-
// TODO : 500 에러 처리
15-
throw new RuntimeException("Oauth server error occurred");
16+
throw new BusinessException(BusinessErrorCode.OAUTH_SERVER_ERROR);
1617
}
1718
}

src/main/java/timeeat/config/CorsConfig.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import org.springframework.http.HttpMethod;
66
import org.springframework.web.servlet.config.annotation.CorsRegistry;
77
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
8+
import timeeat.exception.InitializeException;
89

910
@Configuration
1011
public class CorsConfig implements WebMvcConfigurer {
@@ -18,11 +19,11 @@ public CorsConfig(CorsProperties corsProperties) {
1819

1920
private void validate(List<String> corsOriginList) {
2021
if (corsOriginList == null || corsOriginList.isEmpty()) {
21-
throw new RuntimeException("Initialization Error: CORS origin cannot be empty.");
22+
throw new InitializeException("CORS origin cannot be empty.");
2223
}
2324
for (String origin : corsOriginList) {
2425
if (origin == null || origin.isBlank()) {
25-
throw new RuntimeException("Initialization Error: CORS origin string cannot be blank.");
26+
throw new InitializeException("CORS origin string cannot be blank.");
2627
}
2728
}
2829
}

src/main/java/timeeat/controller/auth/AuthController.java

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
package timeeat.controller.auth;
22

33
import java.net.URI;
4+
import lombok.RequiredArgsConstructor;
5+
import org.springframework.http.HttpHeaders;
46
import org.springframework.http.HttpStatus;
57
import org.springframework.http.ResponseEntity;
68
import org.springframework.web.bind.annotation.GetMapping;
79
import org.springframework.web.bind.annotation.PostMapping;
810
import org.springframework.web.bind.annotation.RequestBody;
11+
import org.springframework.web.bind.annotation.RequestHeader;
912
import org.springframework.web.bind.annotation.RestController;
10-
import lombok.RequiredArgsConstructor;
13+
import timeeat.controller.member.MemberResponse;
1114
import timeeat.controller.web.jwt.JwtManager;
1215
import timeeat.service.auth.AuthService;
1316

@@ -19,8 +22,8 @@ public class AuthController {
1922
private final AuthService authService;
2023

2124
@GetMapping("/api/auth/login/oauth")
22-
public ResponseEntity<Void> redirectOauthLoginPage() {
23-
URI oauthLoginUrl = authService.getOauthLoginUrl();
25+
public ResponseEntity<Void> redirectOauthLoginPage(@RequestHeader(HttpHeaders.REFERER) String origin) {
26+
URI oauthLoginUrl = authService.getOauthLoginUrl(origin);
2427

2528
return ResponseEntity
2629
.status(HttpStatus.FOUND)
@@ -29,15 +32,14 @@ public ResponseEntity<Void> redirectOauthLoginPage() {
2932
}
3033

3134
@PostMapping("/api/auth/login")
32-
public ResponseEntity<TokenResponse> login(@RequestBody MemberLoginRequest request) {
33-
authService.login(request);
34-
35-
TokenResponse response = new TokenResponse(
36-
jwtManager.issueAccessToken(1L),
37-
jwtManager.issueRefreshToken(1L));
35+
public ResponseEntity<LoginResponse> login(@RequestBody LoginRequest request) {
36+
MemberResponse member = authService.login(request);
37+
TokenResponse token = new TokenResponse(
38+
jwtManager.issueAccessToken(member.id()),
39+
jwtManager.issueRefreshToken(member.id()));
3840
return ResponseEntity
3941
.status(HttpStatus.CREATED)
40-
.body(response);
42+
.body(new LoginResponse(token, member));
4143
}
4244

4345
@PostMapping("/api/auth/reissue")

0 commit comments

Comments
 (0)