-
Notifications
You must be signed in to change notification settings - Fork 2
feat : 백엔드 core 서버 기본 세팅 #8
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
Changes from all commits
2c3a8a5
3a8db13
3acdb09
304f74a
28beb35
305b593
5bff197
a6cc883
d51df16
5ffaace
5546acb
4d378a1
91a09b5
56b73b9
ddb05f0
5f5a5d1
7fa2f09
9412f66
3362fa0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
| package com.stackup.stackup.auth.application; | ||
|
|
||
| import com.stackup.stackup.auth.infrastructure.GithubOAuthClient; | ||
| import com.stackup.stackup.auth.application.dto.GithubCallbackResult; | ||
| import com.stackup.stackup.auth.application.dto.GithubLoginResult; | ||
| import com.stackup.stackup.auth.application.dto.RefreshTokenResult; | ||
| import com.stackup.stackup.auth.application.dto.StreamTokenResult; | ||
| import com.stackup.stackup.common.security.JwtTokenProvider; | ||
| import com.stackup.stackup.common.security.StreamTokenProvider; | ||
| import java.util.UUID; | ||
| import org.springframework.security.authentication.BadCredentialsException; | ||
| import org.springframework.stereotype.Service; | ||
|
|
||
| @Service | ||
| public class AuthService { | ||
|
|
||
| private static final long SKELETON_USER_ID = 1L; | ||
| private static final String SKELETON_GITHUB_USERNAME = "stackup-user"; | ||
|
|
||
| private final GithubOAuthClient githubOAuthClient; | ||
| private final JwtTokenProvider jwtTokenProvider; | ||
| private final StreamTokenProvider streamTokenProvider; | ||
|
|
||
| public AuthService( | ||
| GithubOAuthClient githubOAuthClient, | ||
| JwtTokenProvider jwtTokenProvider, | ||
| StreamTokenProvider streamTokenProvider | ||
| ) { | ||
| this.githubOAuthClient = githubOAuthClient; | ||
| this.jwtTokenProvider = jwtTokenProvider; | ||
| this.streamTokenProvider = streamTokenProvider; | ||
| } | ||
|
|
||
| public GithubLoginResult startGithubLogin() { | ||
| String state = UUID.randomUUID().toString(); | ||
| return new GithubLoginResult(githubOAuthClient.buildAuthorizationUrl(state), state); | ||
| } | ||
|
|
||
| public GithubCallbackResult completeGithubLogin(String code, String state) { | ||
| return new GithubCallbackResult( | ||
| jwtTokenProvider.createAccessToken(SKELETON_USER_ID), | ||
| createRefreshTokenStub(), | ||
| SKELETON_USER_ID, | ||
| SKELETON_GITHUB_USERNAME | ||
| ); | ||
| } | ||
|
|
||
| public RefreshTokenResult refresh(String refreshToken) { | ||
| return new RefreshTokenResult(jwtTokenProvider.createAccessToken(SKELETON_USER_ID), createRefreshTokenStub()); | ||
| } | ||
|
|
||
| public void logout(String refreshToken) { | ||
| // Refresh token revocation is intentionally deferred to the auth implementation task. | ||
| } | ||
|
|
||
| public StreamTokenResult createStreamToken(Long userId) { | ||
| if (userId == null) { | ||
| throw new BadCredentialsException("Authentication is required to create stream token"); | ||
| } | ||
| return new StreamTokenResult(streamTokenProvider.createStreamToken(userId)); | ||
| } | ||
|
|
||
| private String createRefreshTokenStub() { | ||
| return "refresh-token-stub-" + UUID.randomUUID(); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| package com.stackup.stackup.auth.application.dto; | ||
|
|
||
| public record GithubCallbackResult( | ||
| String accessToken, | ||
| String refreshToken, | ||
| long userId, | ||
| String githubUsername | ||
| ) { | ||
| } | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. record 좋네요 코드 간결 굿 |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| package com.stackup.stackup.auth.application.dto; | ||
|
|
||
| public record GithubLoginResult( | ||
| String authorizationUrl, | ||
| String state | ||
| ) { | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| package com.stackup.stackup.auth.application.dto; | ||
|
|
||
| public record RefreshTokenResult( | ||
| String accessToken, | ||
| String refreshToken | ||
| ) { | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| package com.stackup.stackup.auth.application.dto; | ||
|
|
||
| public record StreamTokenResult( | ||
| String streamToken | ||
| ) { | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,7 +16,7 @@ | |
| import lombok.Getter; | ||
| import lombok.NoArgsConstructor; | ||
|
|
||
| import java.time.LocalDateTime; | ||
| import java.time.Instant; | ||
|
|
||
| @Getter | ||
| @Entity | ||
|
|
@@ -46,7 +46,7 @@ public class RefreshToken extends BaseTimeEntity { | |
| private String deviceInfo; | ||
|
|
||
| @Column(name = "expires_at", nullable = false) | ||
| private LocalDateTime expiresAt; | ||
| private Instant expiresAt; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [질문] 오 Instant는 어떤 차이가 있나요. 시간대 표현 시 저 방식이 더 효과적일까요?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 기존 LocalDatetime 자체가 상대적으로 표시가 될 수 있어서 |
||
|
|
||
| @Column(name = "is_revoked", nullable = false) | ||
| private boolean revoked = false; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| package com.stackup.stackup.auth.domain; | ||
|
|
||
| import java.util.Optional; | ||
| import org.springframework.data.jpa.repository.JpaRepository; | ||
|
|
||
| public interface RefreshTokenRepository extends JpaRepository<RefreshToken, Long> { | ||
|
|
||
| Optional<RefreshToken> findByTokenHash(String tokenHash); | ||
|
|
||
| void deleteByUser_Id(Long userId); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| package com.stackup.stackup.auth.infrastructure; | ||
|
|
||
| import com.stackup.stackup.common.config.properties.GithubOAuthProperties; | ||
| import org.springframework.stereotype.Component; | ||
| import org.springframework.web.util.UriComponentsBuilder; | ||
|
|
||
| @Component | ||
| public class GithubOAuthClient { | ||
|
|
||
| private final GithubOAuthProperties githubOAuthProperties; | ||
|
|
||
| public GithubOAuthClient(GithubOAuthProperties githubOAuthProperties) { | ||
| this.githubOAuthProperties = githubOAuthProperties; | ||
| } | ||
|
|
||
| public String buildAuthorizationUrl(String state) { | ||
| return UriComponentsBuilder.fromUriString("https://github.com/login/oauth/authorize") | ||
| .queryParam("client_id", githubOAuthProperties.clientId()) | ||
| .queryParam("redirect_uri", githubOAuthProperties.redirectUri()) | ||
| .queryParam("scope", "read:user user:email repo") | ||
| .queryParam("state", state) | ||
| .build() | ||
| .toUriString(); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,78 @@ | ||
| package com.stackup.stackup.auth.presentation; | ||
|
|
||
| import com.stackup.stackup.auth.application.AuthService; | ||
| import com.stackup.stackup.auth.application.dto.GithubCallbackResult; | ||
| import com.stackup.stackup.auth.application.dto.GithubLoginResult; | ||
| import com.stackup.stackup.auth.application.dto.RefreshTokenResult; | ||
| import com.stackup.stackup.auth.application.dto.StreamTokenResult; | ||
| import com.stackup.stackup.auth.presentation.dto.GithubAuthRequest; | ||
| import com.stackup.stackup.auth.presentation.dto.GithubCallbackResponse; | ||
| import com.stackup.stackup.auth.presentation.dto.GithubLoginResponse; | ||
| import com.stackup.stackup.auth.presentation.dto.LogoutRequest; | ||
| import com.stackup.stackup.auth.presentation.dto.RefreshTokenRequest; | ||
| import com.stackup.stackup.auth.presentation.dto.RefreshTokenResponse; | ||
| import com.stackup.stackup.auth.presentation.dto.StreamTokenResponse; | ||
| import com.stackup.stackup.common.security.UserPrincipal; | ||
| import org.springframework.http.ResponseEntity; | ||
| import org.springframework.security.core.annotation.AuthenticationPrincipal; | ||
| import org.springframework.web.bind.annotation.DeleteMapping; | ||
| 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.RequestMapping; | ||
| import org.springframework.web.bind.annotation.RequestParam; | ||
| import org.springframework.web.bind.annotation.RestController; | ||
|
|
||
| @RestController | ||
| @RequestMapping("/api/auth") | ||
| public class AuthController { | ||
|
|
||
| private final AuthService authService; | ||
|
|
||
| public AuthController(AuthService authService) { | ||
| this.authService = authService; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이 부분 보다는 자동 DI 사용하는편이 좋아보입니다.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 오 다른 방법이 있군요 한번 찾아보겠습니다.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 찾아본결과 record클래스로 변경하는거 같아서 controller를 record로 변경했습니다. |
||
| } | ||
|
|
||
| @PostMapping("/github") | ||
| public ResponseEntity<GithubLoginResponse> startGithubLogin(@RequestBody(required = false) GithubAuthRequest request) { | ||
| GithubLoginResult result = authService.startGithubLogin(); | ||
| return ResponseEntity.ok(new GithubLoginResponse(result.authorizationUrl(), result.state())); | ||
| } | ||
|
|
||
| @GetMapping("/github/callback") | ||
| public ResponseEntity<GithubCallbackResponse> githubCallback( | ||
| @RequestParam(required = false) String code, | ||
| @RequestParam(required = false) String state | ||
| ) { | ||
| GithubCallbackResult result = authService.completeGithubLogin(code, state); | ||
| return ResponseEntity.ok(new GithubCallbackResponse( | ||
| result.accessToken(), | ||
| result.refreshToken(), | ||
| result.userId(), | ||
| result.githubUsername() | ||
| )); | ||
| } | ||
|
|
||
| @PostMapping("/refresh") | ||
| public ResponseEntity<RefreshTokenResponse> refresh(@RequestBody(required = false) RefreshTokenRequest request) { | ||
| String refreshToken = request == null ? null : request.refreshToken(); | ||
| RefreshTokenResult result = authService.refresh(refreshToken); | ||
| return ResponseEntity.ok(new RefreshTokenResponse(result.accessToken(), result.refreshToken())); | ||
| } | ||
|
|
||
| @DeleteMapping("/logout") | ||
| public ResponseEntity<Void> logout(@RequestBody(required = false) LogoutRequest request) { | ||
| String refreshToken = request == null ? null : request.refreshToken(); | ||
| authService.logout(refreshToken); | ||
| return ResponseEntity.noContent().build(); | ||
| } | ||
|
|
||
| @PostMapping("/stream-token") | ||
| public ResponseEntity<StreamTokenResponse> createStreamToken( | ||
| @AuthenticationPrincipal UserPrincipal principal | ||
| ) { | ||
| Long userId = principal == null ? null : principal.userId(); | ||
| StreamTokenResult result = authService.createStreamToken(userId); | ||
| return ResponseEntity.ok(new StreamTokenResponse(result.streamToken())); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| package com.stackup.stackup.auth.presentation.dto; | ||
|
|
||
| public record GithubAuthRequest( | ||
| String code, | ||
| String state | ||
| ) { | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| package com.stackup.stackup.auth.presentation.dto; | ||
|
|
||
| public record GithubCallbackResponse( | ||
| String accessToken, | ||
| String refreshToken, | ||
| long userId, | ||
| String githubUsername | ||
| ) { | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| package com.stackup.stackup.auth.presentation.dto; | ||
|
|
||
| public record GithubLoginResponse( | ||
| String authorizationUrl, | ||
| String state | ||
| ) { | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| package com.stackup.stackup.auth.presentation.dto; | ||
|
|
||
| public record LogoutRequest( | ||
| String refreshToken | ||
| ) { | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| package com.stackup.stackup.auth.presentation.dto; | ||
|
|
||
| public record RefreshTokenRequest( | ||
| String refreshToken | ||
| ) { | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| package com.stackup.stackup.auth.presentation.dto; | ||
|
|
||
| public record RefreshTokenResponse( | ||
| String accessToken, | ||
| String refreshToken | ||
| ) { | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| package com.stackup.stackup.auth.presentation.dto; | ||
|
|
||
| public record StreamTokenResponse( | ||
| String streamToken | ||
| ) { | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| package com.stackup.stackup.common.config.properties; | ||
|
|
||
| import jakarta.validation.constraints.NotEmpty; | ||
| import java.util.List; | ||
| import org.springframework.boot.context.properties.ConfigurationProperties; | ||
| import org.springframework.validation.annotation.Validated; | ||
|
|
||
| @Validated | ||
| @ConfigurationProperties(prefix = "app.cors") | ||
| public record CorsProperties( | ||
| @NotEmpty List<String> allowedOrigins | ||
| ) { | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| package com.stackup.stackup.common.config.properties; | ||
|
|
||
| import jakarta.validation.constraints.NotBlank; | ||
| import jakarta.validation.constraints.NotNull; | ||
| import java.net.URI; | ||
| import org.springframework.boot.context.properties.ConfigurationProperties; | ||
| import org.springframework.validation.annotation.Validated; | ||
|
|
||
| @Validated | ||
| @ConfigurationProperties(prefix = "app.github") | ||
| public record GithubOAuthProperties( | ||
| @NotBlank String clientId, | ||
| @NotBlank String clientSecret, | ||
| @NotNull | ||
| URI redirectUri | ||
| ) { | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이부분은 임시로 작성한거겠죠?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
흐름 테스트를 위해서 임시 값 작성하고 나중에 로직 구현시에 없앨 예정입니다.