diff --git a/spring/pom.xml b/spring/pom.xml index ee1a27be..6cfb9da0 100644 --- a/spring/pom.xml +++ b/spring/pom.xml @@ -54,6 +54,10 @@ org.springframework.boot spring-boot-starter-oauth2-client + + org.springframework.boot + spring-boot-starter-oauth2-resource-server + org.springframework.security spring-security-test diff --git a/spring/src/main/kotlin/tw/waterballsa/gaas/spring/configs/securities/CustomOAuthorizationRequestResolver.kt b/spring/src/main/kotlin/tw/waterballsa/gaas/spring/configs/securities/CustomOAuthorizationRequestResolver.kt index acd716a2..4b0020cc 100644 --- a/spring/src/main/kotlin/tw/waterballsa/gaas/spring/configs/securities/CustomOAuthorizationRequestResolver.kt +++ b/spring/src/main/kotlin/tw/waterballsa/gaas/spring/configs/securities/CustomOAuthorizationRequestResolver.kt @@ -114,7 +114,11 @@ class CustomOAuthorizationRequestResolver( val identityProviders = IdentityProvider.values().map { it.queryParam } val targetIdentityProvider = originalRequest.parameterMap["type"]?.find { it in identityProviders } authorizationRequestCustomizer = Consumer { - it.parameters { params -> params["connection"] = targetIdentityProvider ?: "google-oauth2" } + it.parameters { params -> + params["connection"] = targetIdentityProvider ?: "google-oauth2" + //To obtain a JWT token from Auth0, it is necessary to configure the audience for the access token. + params["audience"] = "https://api.gaas.waterballsa.tw" + } } return resolve(request, registrationId, redirectUriAction)!! diff --git a/spring/src/main/kotlin/tw/waterballsa/gaas/spring/configs/securities/CustomSuccessHandler.kt b/spring/src/main/kotlin/tw/waterballsa/gaas/spring/configs/securities/CustomSuccessHandler.kt new file mode 100644 index 00000000..e6be1aaf --- /dev/null +++ b/spring/src/main/kotlin/tw/waterballsa/gaas/spring/configs/securities/CustomSuccessHandler.kt @@ -0,0 +1,38 @@ +package tw.waterballsa.gaas.spring.configs.securities + +import org.springframework.beans.factory.annotation.Value +import org.springframework.security.core.Authentication +import org.springframework.security.oauth2.client.OAuth2AuthorizedClient +import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService +import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken +import org.springframework.security.oauth2.core.oidc.user.OidcUser +import org.springframework.security.web.authentication.AuthenticationSuccessHandler +import tw.waterballsa.gaas.application.usecases.CreateUserUseCase +import javax.servlet.http.HttpServletRequest +import javax.servlet.http.HttpServletResponse + +class CustomSuccessHandler( + private val authorizedClientService: OAuth2AuthorizedClientService, + private val createUserUseCase: CreateUserUseCase +) : AuthenticationSuccessHandler { + @Value("\${frontend}") + private lateinit var frontendUrl: String + + override fun onAuthenticationSuccess( + request: HttpServletRequest, + response: HttpServletResponse, + authentication: Authentication + ) { + authentication as OAuth2AuthenticationToken + + val email = authentication.principal.let { it as OidcUser }.email + createUserUseCase.execute(CreateUserUseCase.Request(email)) + + val accessTokenValue = authorizedClientService.loadAuthorizedClient( + authentication.authorizedClientRegistrationId, + authentication.name + ) + .accessToken.tokenValue + response.sendRedirect("$frontendUrl/auth/token/$accessTokenValue") + } +} \ No newline at end of file diff --git a/spring/src/main/kotlin/tw/waterballsa/gaas/spring/configs/securities/IdTokenAuthenticationFilter.kt b/spring/src/main/kotlin/tw/waterballsa/gaas/spring/configs/securities/IdTokenAuthenticationFilter.kt deleted file mode 100644 index 926d8930..00000000 --- a/spring/src/main/kotlin/tw/waterballsa/gaas/spring/configs/securities/IdTokenAuthenticationFilter.kt +++ /dev/null @@ -1,56 +0,0 @@ -package tw.waterballsa.gaas.spring.configs.securities - -import org.springframework.http.HttpHeaders.AUTHORIZATION -import org.springframework.security.core.GrantedAuthority -import org.springframework.security.core.context.SecurityContextHolder -import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken -import org.springframework.security.oauth2.client.oidc.authentication.OidcIdTokenDecoderFactory -import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository -import org.springframework.security.oauth2.core.oidc.OidcIdToken -import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser -import org.springframework.security.oauth2.core.oidc.user.OidcUser -import org.springframework.security.oauth2.jwt.JwtException -import org.springframework.web.filter.OncePerRequestFilter -import javax.servlet.FilterChain -import javax.servlet.http.HttpServletRequest -import javax.servlet.http.HttpServletResponse - -class IdTokenAuthenticationFilter( - clientRegistrationRepository: ClientRegistrationRepository -) : OncePerRequestFilter() { - companion object { - private const val REGISTRATION_ID = "auth0" - } - - private val registration by lazy { clientRegistrationRepository.findByRegistrationId(REGISTRATION_ID) } - private val jwtDecoder by lazy { OidcIdTokenDecoderFactory().createDecoder(registration) } - - override fun doFilterInternal( - request: HttpServletRequest, response: HttpServletResponse, filterChain: FilterChain - ) { - request.bearerToken() - ?.toOidcUser() - ?.run { - SecurityContextHolder.getContext().authentication = toOAuth2AuthenticationToken(REGISTRATION_ID) - } - filterChain.doFilter(request, response) - } - - private fun String.toOidcUser(): OidcUser? = try { - jwtDecoder.decode(this) - ?.let { OidcIdToken(it.tokenValue, it.issuedAt, it.expiresAt, it.claims) } - ?.let { DefaultOidcUser(emptyList(), it) } - } catch (e: JwtException) { - // id token not accept - null - } - - private fun OidcUser.toOAuth2AuthenticationToken(registrationId: String) = OAuth2AuthenticationToken( - this, emptyList(), registrationId - ) -} - -private fun HttpServletRequest.bearerToken(): String? = this.getHeader(AUTHORIZATION) - ?.takeIf { it.startsWith("Bearer ") } - ?.split(" ") - ?.last() \ No newline at end of file diff --git a/spring/src/main/kotlin/tw/waterballsa/gaas/spring/configs/securities/SecurityConfig.kt b/spring/src/main/kotlin/tw/waterballsa/gaas/spring/configs/securities/SecurityConfig.kt index 70090979..31de0837 100644 --- a/spring/src/main/kotlin/tw/waterballsa/gaas/spring/configs/securities/SecurityConfig.kt +++ b/spring/src/main/kotlin/tw/waterballsa/gaas/spring/configs/securities/SecurityConfig.kt @@ -3,6 +3,7 @@ package tw.waterballsa.gaas.spring.configs.securities import org.springframework.context.annotation.Bean import org.springframework.security.config.annotation.web.builders.HttpSecurity import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity +import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserService import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository @@ -11,12 +12,15 @@ import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser import org.springframework.security.oauth2.core.oidc.user.OidcUser import org.springframework.security.web.AuthenticationEntryPoint import org.springframework.security.web.SecurityFilterChain -import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter +import org.springframework.security.web.authentication.AuthenticationSuccessHandler +import tw.waterballsa.gaas.application.usecases.CreateUserUseCase import javax.servlet.http.HttpServletResponse.SC_UNAUTHORIZED @EnableWebSecurity class SecurityConfig( private val clientRegistrationRepository: ClientRegistrationRepository, + private val authorizedClientService: OAuth2AuthorizedClientService, + private val createUserUseCase: CreateUserUseCase ) { @Bean @@ -29,7 +33,7 @@ class SecurityConfig( .anyRequest().authenticated() .and() .oauth2Login() - .defaultSuccessUrl("/login-successfully", true) + .successHandler(successHandler()) .authorizationEndpoint() .authorizationRequestResolver( CustomOAuthorizationRequestResolver( @@ -41,14 +45,19 @@ class SecurityConfig( .userInfoEndpoint().oidcUserService(oidcUserService()) .and() .and() + .oauth2ResourceServer().jwt().and() + .and() .exceptionHandling() .authenticationEntryPoint(redirectToLoginEndPoint()) - .and() - .addFilterBefore(IdTokenAuthenticationFilter(clientRegistrationRepository), UsernamePasswordAuthenticationFilter::class.java) return http.build() } + @Bean + fun successHandler(): AuthenticationSuccessHandler{ + return CustomSuccessHandler(authorizedClientService, createUserUseCase); + } + private fun oidcUserService(): OAuth2UserService { val userService = OidcUserService() return OAuth2UserService { request: OidcUserRequest? -> diff --git a/spring/src/main/resources/application-dev.yaml b/spring/src/main/resources/application-dev.yaml index 8c0c2a4e..ec57257e 100644 --- a/spring/src/main/resources/application-dev.yaml +++ b/spring/src/main/resources/application-dev.yaml @@ -27,6 +27,9 @@ spring: authorization-uri: https://dev-1l0ixjw8yohsluoi.us.auth0.com/authorize token-uri: https://dev-1l0ixjw8yohsluoi.us.auth0.com/oauth/token user-info-uri: https://dev-1l0ixjw8yohsluoi.us.auth0.com/oauth/userinfo + resourceserver: + jwt: + issuer-uri: https://dev-1l0ixjw8yohsluoi.us.auth0.com/ server: port: 8087 diff --git a/spring/src/main/resources/application.yaml b/spring/src/main/resources/application.yaml index cb9b552f..c8d2a21d 100644 --- a/spring/src/main/resources/application.yaml +++ b/spring/src/main/resources/application.yaml @@ -19,6 +19,9 @@ spring: auth0: # trailing slash is important! issuer-uri: https://dev-1l0ixjw8yohsluoi.us.auth0.com/ + resourceserver: + jwt: + issuer-uri: https://dev-1l0ixjw8yohsluoi.us.auth0.com/ frontend: https://lobby.gaas.waterballsa.tw