Skip to content

Commit 26fce2f

Browse files
committed
[#73]; chore: swagger oauth2 추가
1 parent 96738e3 commit 26fce2f

5 files changed

Lines changed: 99 additions & 11 deletions

File tree

src/main/kotlin/com/yourssu/scouter/common/application/domain/authentication/AuthenticationController.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import com.yourssu.scouter.common.implement.domain.authentication.TokenType
99
import io.swagger.v3.oas.annotations.Operation
1010
import io.swagger.v3.oas.annotations.Parameter
1111
import io.swagger.v3.oas.annotations.responses.ApiResponse
12+
import io.swagger.v3.oas.annotations.security.SecurityRequirements
1213
import io.swagger.v3.oas.annotations.tags.Tag
1314
import jakarta.servlet.http.HttpServletRequest
1415
import jakarta.validation.Valid
@@ -34,6 +35,7 @@ class AuthenticationController(
3435

3536
@Tag(name = "OAUTH2")
3637
@Operation(summary = "회원가입/로그인", description = "/oauth2/{oauth2Type}에서 얻은 code를 이용해 회원가입/로그인을 진행합니다.")
38+
@SecurityRequirements // 로그인을 필요로 하지 않는 곳은 전역 인증을 사용하지않도록 초기화
3739
@PostMapping("oauth2/login/{oauth2Type}")
3840
fun login(
3941
@PathVariable oauth2Type: OAuth2Type,
@@ -60,6 +62,7 @@ class AuthenticationController(
6062
@ApiResponse(description = "NO_CONTENT", responseCode = "204")
6163
@PostMapping("/logout")
6264
fun logout(
65+
@Parameter(hidden = true)
6366
@RequestHeader(HttpHeaders.AUTHORIZATION) accessToken: String,
6467
@RequestBody @Valid request: LogoutRequest,
6568
): ResponseEntity<Unit> {
@@ -68,9 +71,11 @@ class AuthenticationController(
6871
return ResponseEntity.noContent().build()
6972
}
7073

74+
@SecurityRequirements // 로그인을 필요로 하지 않는 곳은 전역 인증을 사용하지않도록 초기화
7175
@Operation(summary = "AccessToken 유효성 검사")
7276
@GetMapping("/validate-token")
7377
fun validateToken(
78+
@Parameter(hidden = true)
7479
@RequestHeader(HttpHeaders.AUTHORIZATION) accessToken: String,
7580
): ResponseEntity<ValidateTokenResponse> {
7681
val validated: Boolean = authenticationService.isValidToken(TokenType.ACCESS, accessToken)
@@ -79,10 +84,12 @@ class AuthenticationController(
7984
return ResponseEntity.ok(response)
8085
}
8186

87+
@SecurityRequirements // 로그인을 필요로 하지 않는 곳은 전역 인증을 사용하지않도록 초기화
8288
@Operation(summary = "토큰 재발급")
8389
@PostMapping("/refresh-token")
8490
fun refreshToken(
8591
@RequestBody @Valid request: TokenRefreshRequest,
92+
@Parameter(hidden = true)
8693
@RequestHeader(HttpHeaders.AUTHORIZATION, required = false) bearerRefreshHeader: String?,
8794
): ResponseEntity<TokenRefreshResponse> {
8895
logger.info("[Auth] POST /refresh-token | hasAuthHeader={} | bodyPresent={}", !bearerRefreshHeader.isNullOrBlank(), !request.refreshToken.isNullOrBlank())
@@ -98,6 +105,7 @@ class AuthenticationController(
98105
@ApiResponse(description = "NO_CONTENT", responseCode = "204")
99106
@PostMapping("/withdrawal")
100107
fun withdraw(
108+
@Parameter(hidden = true)
101109
@RequestHeader(HttpHeaders.AUTHORIZATION) accessToken: String,
102110
@RequestBody @Valid request: WithdrawalRequest,
103111
): ResponseEntity<Unit> {

src/main/kotlin/com/yourssu/scouter/common/application/domain/authentication/OAuth2Controller.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ package com.yourssu.scouter.common.application.domain.authentication
22

33
import com.yourssu.scouter.common.business.domain.authentication.OAuth2Service
44
import com.yourssu.scouter.common.implement.domain.authentication.OAuth2Type
5+
import io.swagger.v3.oas.annotations.Operation
6+
import io.swagger.v3.oas.annotations.security.SecurityRequirements
7+
import io.swagger.v3.oas.annotations.tags.Tag
58
import jakarta.servlet.http.HttpServletRequest
69
import jakarta.servlet.http.HttpServletResponse
710
import org.springframework.http.HttpHeaders
@@ -12,11 +15,15 @@ import org.springframework.web.bind.annotation.RequestParam
1215
import org.springframework.web.bind.annotation.RestController
1316
import org.springframework.web.util.UriComponentsBuilder
1417

18+
@Tag(name = "인증/인가")
19+
@Tag(name = "OAUTH2")
1520
@RestController
1621
class OAuth2Controller(
1722
private val oauth2Service: OAuth2Service
1823
) {
1924

25+
@Operation(summary = "OAuth2 로그인 페이지 리다이렉트", description = "여기에서는 리다이렉트가 되지 않습니다. Authorize 이용.")
26+
@SecurityRequirements // 로그인을 필요로 하지 않는 곳은 전역 인증을 사용하지않도록 초기화
2027
@GetMapping("/oauth2/{oauth2Type}")
2128
fun redirectAuthCodeRequestUrl(
2229
@PathVariable oauth2Type: OAuth2Type,
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package com.yourssu.scouter.common.application.domain.authentication
2+
3+
import com.fasterxml.jackson.databind.PropertyNamingStrategies.SnakeCaseStrategy
4+
import com.fasterxml.jackson.databind.annotation.JsonNaming
5+
import com.yourssu.scouter.common.business.domain.authentication.LoginResult
6+
import com.yourssu.scouter.common.business.domain.authentication.OAuth2Service
7+
import com.yourssu.scouter.common.implement.domain.authentication.OAuth2Type
8+
import io.swagger.v3.oas.annotations.Hidden
9+
import org.springframework.beans.factory.annotation.Value
10+
import org.springframework.http.ResponseEntity
11+
import org.springframework.web.bind.annotation.PostMapping
12+
import org.springframework.web.bind.annotation.RequestParam
13+
import org.springframework.web.bind.annotation.RestController
14+
15+
@Hidden
16+
@RestController
17+
class SwaggerOAuth2Controller(private val oauth2Service: OAuth2Service) {
18+
19+
@Value("\${springdoc.swagger-ui.oauth2-redirect-url}")
20+
private lateinit var redirectUri: String
21+
22+
@PostMapping("/oauth2/swagger/callback")
23+
fun callback(
24+
@RequestParam code: String,
25+
@RequestParam(required = false) error: String?
26+
): ResponseEntity<AuthResponse> {
27+
if (error != null) throw RuntimeException(
28+
"OAuth2 authorization code flow failed: $error"
29+
)
30+
31+
val referer = redirectUri.substringBefore("/")
32+
val loginResult: LoginResult = oauth2Service.login(
33+
oauth2Type = OAuth2Type.GOOGLE, // 인증, 인가 로직을 시험하기 위함이 아니기 때문에 google로 고정해뒀습니다.
34+
oauth2AuthorizationCode = code,
35+
referer = referer,
36+
redirectUriOverride = redirectUri,
37+
)
38+
val accessToken = loginResult.accessToken.substringAfter(" ")
39+
return ResponseEntity.ok(AuthResponse(accessToken))
40+
}
41+
}
42+
43+
@JsonNaming(SnakeCaseStrategy::class)
44+
data class AuthResponse(val accessToken: String)
45+
46+

src/main/kotlin/com/yourssu/scouter/common/application/support/configuration/SwaggerConfiguration.kt

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ package com.yourssu.scouter.common.application.support.configuration
33
import io.swagger.v3.oas.models.Components
44
import io.swagger.v3.oas.models.OpenAPI
55
import io.swagger.v3.oas.models.info.Info
6+
import io.swagger.v3.oas.models.security.OAuthFlow
7+
import io.swagger.v3.oas.models.security.OAuthFlows
8+
import io.swagger.v3.oas.models.security.Scopes
69
import io.swagger.v3.oas.models.security.SecurityRequirement
710
import io.swagger.v3.oas.models.security.SecurityScheme
811
import org.springframework.context.annotation.Bean
@@ -12,27 +15,46 @@ import org.springframework.context.annotation.Configuration
1215
class SwaggerConfiguration {
1316

1417
companion object {
15-
const val JWT = "JWT"
16-
const val BEARER = "Bearer"
18+
const val OAUTH2 = "oauth2"
1719
}
1820

1921
@Bean
2022
fun customOpenAPI() : OpenAPI {
21-
val securityRequirement = SecurityRequirement().addList(JWT)
22-
val components = Components().addSecuritySchemes(
23-
JWT, SecurityScheme()
24-
.name(JWT)
25-
.type(SecurityScheme.Type.HTTP)
26-
.scheme(BEARER)
27-
.bearerFormat(JWT)
28-
)
23+
val securityRequirement = SecurityRequirement().addList(OAUTH2)
24+
val components = Components()
25+
.addSecuritySchemes(OAUTH2, createGoogleOAuth2Scheme())
2926
return OpenAPI()
3027
.components(Components())
3128
.info(info())
3229
.addSecurityItem(securityRequirement)
3330
.components(components)
3431
}
3532

33+
private fun createGoogleOAuth2Scheme() : SecurityScheme {
34+
return SecurityScheme()
35+
.type(SecurityScheme.Type.OAUTH2)
36+
.flows(
37+
OAuthFlows()
38+
.authorizationCode(
39+
OAuthFlow()
40+
.authorizationUrl("https://accounts.google.com/o/oauth2/auth")
41+
.tokenUrl("http://localhost:8080/oauth2/swagger/callback")
42+
.refreshUrl("http://localhost:8080/oauth2/swagger/callback")
43+
.scopes(createGoogleScopes())
44+
)
45+
)
46+
}
47+
48+
private fun createGoogleScopes() : Scopes {
49+
return Scopes()
50+
.addString("openid", "OpenId Connect")
51+
.addString("profile", "기본 프로필 정보")
52+
.addString("email", "이메일 주소")
53+
.addString("https://www.googleapis.com/auth/drive.readonly", "드라이브 읽기 권한")
54+
.addString("https://www.googleapis.com/auth/forms.responses.readonly", "구글 폼 응답 읽기 권한")
55+
.addString("https://www.googleapis.com/auth/gmail.send", "메일 전송 권한")
56+
}
57+
3658
private fun info() : Info {
3759
return Info()
3860
.title("Scouter API")

src/main/resources/application-local.yml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ oauth2:
3838
allowed_redirect_uris:
3939
- ${ALLOWED_REDIRECT_URI_LOCAL}
4040
- ${ALLOWED_REDIRECT_URI_DEV}
41+
- ${SWAGGER_OAUTH2_REDIRECT_URL}
4142
scope:
4243
- openid
4344
- https://www.googleapis.com/auth/userinfo.email
@@ -52,4 +53,8 @@ springdoc:
5253
theme: arta # syntax highlight theme
5354
doc-expansion: none # 기본으로 접기
5455
operations-sorter: method # 정렬 방식
55-
tags-sorter: alpha
56+
tags-sorter: alpha
57+
oauth:
58+
client-id: ${GOOGLE_OAUTH_CLIENT_ID}
59+
client-secret: ${GOOGLE_OAUTH_CLIENT_SECRET}
60+
oauth2-redirect-url: ${SWAGGER_OAUTH2_REDIRECT_URL}

0 commit comments

Comments
 (0)