-
Notifications
You must be signed in to change notification settings - Fork 0
✨ User CRUD, 로컬 로그인, 구글 로그인 구현 #6
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
Merged
Merged
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
530dd82
feat: user, auth
tteokgook1 97ffcec
feat: local login
tteokgook1 a92cdcc
feat: google login
tteokgook1 9222011
feat: frontend url setting
tteokgook1 83a6bca
feat: frontend url setting
tteokgook1 efdac0b
fix: spring properties
tteokgook1 e08b0f4
fix: resolve some reviews
tteokgook1 6abaf58
fix: resolve reviews
tteokgook1 060c8fa
fix: cd
tteokgook1 bd1b5dd
fix: reviews
tteokgook1 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,7 +7,7 @@ on: | |
| branches: [ "main" ] | ||
|
|
||
| jobs: | ||
| build: | ||
| lint-and-test: | ||
|
|
||
| runs-on: ubuntu-latest | ||
|
|
||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -38,3 +38,4 @@ out/ | |
|
|
||
| ### Kotlin ### | ||
| .kotlin | ||
| /.env | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,2 +1,25 @@ | ||
| # 23-5-team2-server | ||
| 와플스튜디오 23.5기 2조 server | ||
| 와플스튜디오 23.5기 2조 server | ||
|
|
||
| # env | ||
|
|
||
| 루트 경로에 다음의 `.env` 파일을 작성합니다. | ||
|
|
||
| ```env | ||
| GOOGLE_CLIENT_ID=YOUR-GOOGLE-CLIENT-ID | ||
| GOOGLE_CLIENT_SECRET=YOUR-GOOGLE-CLIENT-SECRET | ||
| ``` | ||
|
|
||
| | Key Name | Description | | ||
| |------------------------|--------------------------| | ||
| | `GOOGLE_CLIENT_ID` | 구글 OAuth2 Client ID | | ||
| | `GOOGLE_CLIENT_SECRET` | 구글 OAuth2 Client Secret | | ||
|
|
||
| # Auth | ||
|
|
||
| - AUTH-TOKEN 쿠키를 사용합니다. | ||
|
|
||
| ## Google OAuth2 | ||
|
|
||
| - `/oauth2/authorization/google` 로 GET 요청을 보내면 구글 로그인 과정이 진행됩니다. | ||
| - 로그인 성공 / 실패 모두 프론트엔드 루트 경로로 리다이렉트됩니다. |
tteokgook1 marked this conversation as resolved.
Show resolved
Hide resolved
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
15 changes: 15 additions & 0 deletions
15
src/main/kotlin/com/wafflestudio/team2server/DomainException.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| package com.wafflestudio.team2server | ||
|
|
||
| import org.springframework.http.HttpStatus | ||
| import org.springframework.http.HttpStatusCode | ||
|
|
||
| open class DomainException( | ||
| // client 와 약속된 Application Error 에 대한 코드 필요 시 Enum 으로 관리하자. | ||
tteokgook1 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| val errorCode: Int, | ||
| // HTTP Status Code, 비어있다면 500 이다. | ||
| val httpErrorCode: HttpStatusCode = HttpStatus.INTERNAL_SERVER_ERROR, | ||
tteokgook1 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| val msg: String, | ||
tteokgook1 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| cause: Throwable? = null, | ||
| ) : RuntimeException(msg, cause) { | ||
| override fun toString(): String = "DomainException(msg='$msg', errorCode=$errorCode, httpErrorCode=$httpErrorCode)" | ||
| } | ||
14 changes: 14 additions & 0 deletions
14
src/main/kotlin/com/wafflestudio/team2server/GlobalControllerExceptionHandler.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| package com.wafflestudio.team2server | ||
|
|
||
| import org.springframework.http.ResponseEntity | ||
| import org.springframework.web.bind.annotation.ControllerAdvice | ||
| import org.springframework.web.bind.annotation.ExceptionHandler | ||
|
|
||
| @ControllerAdvice | ||
| class GlobalControllerExceptionHandler { | ||
| @ExceptionHandler(DomainException::class) | ||
| fun handle(exception: DomainException): ResponseEntity<Map<String, Any>> = | ||
| ResponseEntity | ||
| .status(exception.httpErrorCode) | ||
| .body(mapOf("error" to exception.msg, "errorCode" to exception.errorCode)) | ||
| } |
12 changes: 12 additions & 0 deletions
12
src/main/kotlin/com/wafflestudio/team2server/config/JacksonConfig.kt
tteokgook1 marked this conversation as resolved.
Show resolved
Hide resolved
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| package com.wafflestudio.team2server.config | ||
|
|
||
| import com.fasterxml.jackson.databind.ObjectMapper | ||
| import com.fasterxml.jackson.module.kotlin.registerKotlinModule | ||
| import org.springframework.context.annotation.Bean | ||
| import org.springframework.context.annotation.Configuration | ||
|
|
||
| @Configuration | ||
| class JacksonConfig { | ||
| @Bean | ||
| fun objectMapper(): ObjectMapper = ObjectMapper().registerKotlinModule() | ||
| } |
40 changes: 40 additions & 0 deletions
40
src/main/kotlin/com/wafflestudio/team2server/config/SecurityConfig.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| package com.wafflestudio.team2server.config | ||
|
|
||
| import com.wafflestudio.team2server.user.OAuth2SuccessHandler | ||
| import com.wafflestudio.team2server.user.service.GoogleOAuth2UserService | ||
| import org.springframework.beans.factory.annotation.Value | ||
| import org.springframework.context.annotation.Bean | ||
| import org.springframework.context.annotation.Configuration | ||
| import org.springframework.security.config.annotation.web.builders.HttpSecurity | ||
| import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity | ||
| import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder | ||
| import org.springframework.security.web.SecurityFilterChain | ||
|
|
||
| @EnableWebSecurity | ||
| @Configuration | ||
| class SecurityConfig( | ||
| private val googleOAuth2UserService: GoogleOAuth2UserService, | ||
| private val oAuth2SuccessHandler: OAuth2SuccessHandler, | ||
| @Value("\${app.frontend.url}") private val frontendUrl: String, | ||
| ) { | ||
| @Bean | ||
| fun filterChain(http: HttpSecurity): SecurityFilterChain { | ||
| // it does not use authorizeHttpRequests | ||
| // requiring authorization is handled at the controller level | ||
| // when use @LoggedInUser, if there is no authorization, UserArgumentResolver will return 401. | ||
| http | ||
| .csrf { it.disable() } | ||
| .httpBasic { it.disable() } | ||
| .formLogin { it.disable() } | ||
| .oauth2Login { oauth2 -> | ||
| oauth2 | ||
| .userInfoEndpoint { it.userService(googleOAuth2UserService) } | ||
| .successHandler(oAuth2SuccessHandler) | ||
| .failureUrl(frontendUrl) | ||
| } | ||
| return http.build() | ||
tteokgook1 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| @Bean | ||
| fun bcryptPasswordEncoder(): BCryptPasswordEncoder = BCryptPasswordEncoder() | ||
| } | ||
tteokgook1 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
28 changes: 28 additions & 0 deletions
28
src/main/kotlin/com/wafflestudio/team2server/config/WebConfig.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| package com.wafflestudio.team2server.config | ||
|
|
||
| import com.wafflestudio.team2server.user.UserArgumentResolver | ||
| import org.springframework.beans.factory.annotation.Value | ||
| import org.springframework.context.annotation.Configuration | ||
| import org.springframework.web.method.support.HandlerMethodArgumentResolver | ||
| import org.springframework.web.servlet.config.annotation.CorsRegistry | ||
| import org.springframework.web.servlet.config.annotation.WebMvcConfigurer | ||
|
|
||
| @Configuration | ||
| class WebConfig( | ||
| private val userArgumentResolver: UserArgumentResolver, | ||
| @Value("\${app.frontend.url}") private val frontendUrl: String, | ||
| ) : WebMvcConfigurer { | ||
| override fun addArgumentResolvers(resolvers: MutableList<HandlerMethodArgumentResolver>) { | ||
| resolvers.add(userArgumentResolver) | ||
| } | ||
|
|
||
| override fun addCorsMappings(registry: CorsRegistry) { | ||
| registry | ||
| .addMapping("/**") | ||
| .allowedOrigins(frontendUrl) | ||
| .allowedMethods("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS") | ||
| .allowedHeaders("*") | ||
| .allowCredentials(true) | ||
tteokgook1 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| .maxAge(3600) | ||
| } | ||
tteokgook1 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
26 changes: 26 additions & 0 deletions
26
src/main/kotlin/com/wafflestudio/team2server/user/JwtAuthenticationFilter.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| package com.wafflestudio.team2server.user | ||
|
|
||
| import jakarta.servlet.FilterChain | ||
| import jakarta.servlet.http.HttpServletRequest | ||
| import jakarta.servlet.http.HttpServletResponse | ||
| import org.springframework.stereotype.Component | ||
| import org.springframework.web.filter.OncePerRequestFilter | ||
|
|
||
| @Component | ||
| class JwtAuthenticationFilter( | ||
| private val jwtProvider: JwtProvider, | ||
| ) : OncePerRequestFilter() { | ||
| override fun doFilterInternal( | ||
| request: HttpServletRequest, | ||
| response: HttpServletResponse, | ||
| filterChain: FilterChain, | ||
| ) { | ||
| val token = resolveToken(request) | ||
| if (token != null && jwtProvider.validateToken(token)) { | ||
| request.setAttribute("userId", jwtProvider.getUserId(token)) | ||
| } | ||
| filterChain.doFilter(request, response) | ||
| } | ||
|
|
||
| private fun resolveToken(request: HttpServletRequest): String? = request.cookies?.find { it.name == "AUTH-TOKEN" }?.value | ||
| } |
78 changes: 78 additions & 0 deletions
78
src/main/kotlin/com/wafflestudio/team2server/user/JwtProvider.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,78 @@ | ||
| package com.wafflestudio.team2server.user | ||
|
|
||
| import io.jsonwebtoken.Jwts | ||
| import io.jsonwebtoken.SignatureAlgorithm | ||
| import io.jsonwebtoken.security.Keys | ||
| import org.springframework.beans.factory.annotation.Value | ||
| import org.springframework.http.ResponseCookie | ||
| import org.springframework.stereotype.Component | ||
| import java.util.Date | ||
|
|
||
| @Component | ||
| class JwtProvider( | ||
| @Value("\${jwt.secret}") | ||
| private val secretKey: String, | ||
| @Value("\${jwt.expiration-in-ms}") | ||
| private val expirationInMs: Long, | ||
| @Value("\${jwt.secure}") private val secure: Boolean, | ||
| ) { | ||
| private val key = Keys.hmacShaKeyFor(secretKey.toByteArray()) | ||
|
|
||
| fun createToken(userId: Long): String { | ||
| val now = Date() | ||
| val validity = Date(now.time + expirationInMs) | ||
|
|
||
| return Jwts | ||
| .builder() | ||
| .setSubject(userId.toString()) | ||
| .setIssuedAt(now) | ||
| .setExpiration(validity) | ||
| .signWith(key, SignatureAlgorithm.HS256) | ||
| .compact() | ||
| } | ||
|
|
||
| fun getUserId(token: String): Long = | ||
| Jwts | ||
| .parserBuilder() | ||
| .setSigningKey(key) | ||
| .build() | ||
| .parseClaimsJws(token) | ||
| .body | ||
| .subject | ||
| .toLong() | ||
|
|
||
| fun validateToken(token: String): Boolean { | ||
| try { | ||
| Jwts | ||
| .parserBuilder() | ||
| .setSigningKey(key) | ||
| .build() | ||
| .parseClaimsJws(token) | ||
| return true | ||
| } catch (_: Exception) { | ||
| // do nothing | ||
| } | ||
| return false | ||
tteokgook1 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| fun getExpiration(token: String): Long { | ||
| val claims = | ||
| Jwts | ||
| .parserBuilder() | ||
| .setSigningKey(key) | ||
| .build() | ||
| .parseClaimsJws(token) | ||
| .body | ||
| return claims.expiration.time | ||
| } | ||
|
|
||
| fun createJwtCookie(token: String): ResponseCookie = | ||
| ResponseCookie | ||
| .from("AUTH-TOKEN", token) | ||
| .httpOnly(true) // Prevents JS access (XSS protection) | ||
| .path("/") // Available for all routes | ||
| .maxAge((getExpiration(token) - System.currentTimeMillis()) / 1000) | ||
| .sameSite("Lax") | ||
| .secure(secure) | ||
| .build() | ||
| } | ||
5 changes: 5 additions & 0 deletions
5
src/main/kotlin/com/wafflestudio/team2server/user/LoggedInUser.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| package com.wafflestudio.team2server.user | ||
|
|
||
| @Target(AnnotationTarget.VALUE_PARAMETER) | ||
| @Retention(AnnotationRetention.RUNTIME) | ||
| annotation class LoggedInUser |
37 changes: 37 additions & 0 deletions
37
src/main/kotlin/com/wafflestudio/team2server/user/OAuth2SuccessHandler.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| package com.wafflestudio.team2server.user | ||
|
|
||
| import com.wafflestudio.team2server.user.repository.UserRepository | ||
| import jakarta.servlet.http.HttpServletRequest | ||
| import jakarta.servlet.http.HttpServletResponse | ||
| import org.springframework.beans.factory.annotation.Value | ||
| import org.springframework.http.HttpHeaders | ||
| import org.springframework.security.core.Authentication | ||
| import org.springframework.security.oauth2.core.user.OAuth2User | ||
| import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler | ||
| import org.springframework.stereotype.Component | ||
|
|
||
| @Component | ||
| class OAuth2SuccessHandler( | ||
| private val userRepository: UserRepository, | ||
| private val jwtProvider: JwtProvider, | ||
| @Value("\${app.frontend.url}") private val frontendUrl: String, | ||
| ) : SimpleUrlAuthenticationSuccessHandler() { | ||
| override fun onAuthenticationSuccess( | ||
| request: HttpServletRequest, | ||
| response: HttpServletResponse, | ||
| authentication: Authentication, | ||
| ) { | ||
| val oAuth2User = authentication.principal as OAuth2User | ||
| val email = | ||
| oAuth2User.getAttribute<String?>( | ||
| "email", | ||
| ) ?: throw IllegalStateException("OAuth2 provider did not supply a non-null 'email' attribute") | ||
| val user = userRepository.findByOauthId(email) ?: throw IllegalStateException("User not found for OAuth id (email=$email)") | ||
|
|
||
| val token = jwtProvider.createToken(user.id!!) | ||
| val jwtCookie = jwtProvider.createJwtCookie(token) | ||
| response.addHeader(HttpHeaders.SET_COOKIE, jwtCookie.toString()) | ||
|
|
||
| redirectStrategy.sendRedirect(request, response, frontendUrl) | ||
| } | ||
tteokgook1 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.