diff --git a/generators/spring-boot/templates/src/main/kotlin/_package_/security/jwt/JWTRelayGatewayFilterFactory.kt.ejs b/generators/spring-boot/templates/src/main/kotlin/_package_/security/jwt/JWTRelayGatewayFilterFactory.kt.ejs deleted file mode 100644 index 0b0446f4..00000000 --- a/generators/spring-boot/templates/src/main/kotlin/_package_/security/jwt/JWTRelayGatewayFilterFactory.kt.ejs +++ /dev/null @@ -1,58 +0,0 @@ -<%# - Copyright 2013-2024 the original author or authors from the JHipster project. - - This file is part of the JHipster project, see https://www.jhipster.tech/ - for more information. - - Licensed under the Apache License, Version 2.0 (the " - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. --%> -package <%= packageName %>.security.jwt - -import org.springframework.http.HttpHeaders.AUTHORIZATION - -import org.springframework.cloud.gateway.filter.GatewayFilter -import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory -import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder -import org.springframework.stereotype.Component -import org.springframework.util.StringUtils - -import org.springframework.web.server.ServerWebExchange - -@Component -class JWTRelayGatewayFilterFactory(private val jwtDecoder: ReactiveJwtDecoder) : AbstractGatewayFilterFactory() { - companion object { - const val BEARER: String = "Bearer " - } - - override fun apply(config: Any) = - GatewayFilter { exchange, chain -> - val bearerToken = exchange.request.headers.getFirst(AUTHORIZATION) - if (bearerToken == null) { - // Allow anonymous requests. - chain.filter(exchange) - } - val token = this.extractToken(bearerToken) - jwtDecoder.decode(token).thenReturn(withBearerAuth(exchange, token)).flatMap(chain::filter) - } - - private fun extractToken(bearerToken: String): String { - if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) { - return bearerToken.substring(7) - } - throw IllegalArgumentException("Invalid token in Authorization header") - } - - private fun withBearerAuth(exchange: ServerWebExchange, authorizeToken: String): ServerWebExchange{ - return exchange.mutate().request(r -> r.headers(headers -> headers.setBearerAuth(authorizeToken))).build() - } -} diff --git a/generators/spring-boot/templates/src/test/kotlin/_package_/config/JHipsterBlockHoundIntegration.kt.ejs b/generators/spring-boot/templates/src/test/kotlin/_package_/config/JHipsterBlockHoundIntegration.kt.ejs index 03831529..9717ddc1 100644 --- a/generators/spring-boot/templates/src/test/kotlin/_package_/config/JHipsterBlockHoundIntegration.kt.ejs +++ b/generators/spring-boot/templates/src/test/kotlin/_package_/config/JHipsterBlockHoundIntegration.kt.ejs @@ -57,5 +57,6 @@ class JHipsterBlockHoundIntegration: BlockHoundIntegration { builder.allowBlockingCallsInside("org.springframework.web.reactive.result.method.InvocableHandlerMethod", "invoke") builder.allowBlockingCallsInside("org.springdoc.core.service.OpenAPIService", "build") builder.allowBlockingCallsInside("org.springdoc.core.service.AbstractRequestService", "build") + // jhipster-needle-blockhound-integration - JHipster will add additional gradle plugins here } } diff --git a/generators/spring-boot/templates/src/test/kotlin/_package_/security/oauth2/AuthorizationHeaderUtilTest.kt.ejs b/generators/spring-boot/templates/src/test/kotlin/_package_/security/oauth2/AuthorizationHeaderUtilTest.kt.ejs deleted file mode 100644 index 3893ae2e..00000000 --- a/generators/spring-boot/templates/src/test/kotlin/_package_/security/oauth2/AuthorizationHeaderUtilTest.kt.ejs +++ /dev/null @@ -1,238 +0,0 @@ -<%# - Copyright 2013-2024 the original author or authors from the JHipster project. - -This file is part of the JHipster project, see https://jhipster.github.io/ - for more information. - - Licensed under the Apache License, Version 2.0 (the "License") - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. --%> -package <%= packageName %>.security.oauth2 - -import org.junit.jupiter.api.Assertions.fail -import org.mockito.ArgumentMatchers.* -import org.mockito.Mockito.* - -import java.time.Duration -import java.time.Instant -import java.util.List -import java.util.Map -import java.util.Optional -import org.assertj.core.api.Assertions -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.extension.ExtendWith -import org.mockito.InjectMocks -import org.mockito.Mock -import org.mockito.junit.jupiter.MockitoExtension -import org.springframework.boot.web.client.RestTemplateBuilder -import org.springframework.http.RequestEntity -import org.springframework.http.ResponseEntity -import org.springframework.http.converter.HttpMessageConverter -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken -import org.springframework.security.core.authority.SimpleGrantedAuthority -import org.springframework.security.core.context.SecurityContext -import org.springframework.security.core.context.SecurityContextHolder -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.client.registration.ClientRegistration -import org.springframework.security.oauth2.core.* -import org.springframework.security.oauth2.core.oidc.OidcIdToken -import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser -import org.springframework.security.oauth2.jwt.Jwt -import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken -import org.springframework.web.client.ResponseErrorHandler -import org.springframework.web.client.RestTemplate - -/** - * Test class for the [AuthorizationHeaderUtil] utility class. - */ -@ExtendWith(MockitoExtension::class) -class AuthorizationHeaderUtilTest { - - companion object { - const val VALID_REGISTRATION_ID: String = "OIDC" - const val SUB_VALUE: String = "123456" - } - - @Mock private lateinit var clientService: OAuth2AuthorizedClientService - @Mock private lateinit var restTemplateBuilder: RestTemplateBuilder - @Mock private lateinit var securityContext: SecurityContext - @InjectMocks private lateinit var authorizationHeaderUtil: AuthorizationHeaderUtil - - @BeforeEach - fun setup() { - SecurityContextHolder.setContext(securityContext) - } - - @Test - fun getAuthorizationHeader_Authentication() { - val authenticationToken = UsernamePasswordAuthenticationToken("principal", "credentials") - doReturn(authenticationToken).`when`(securityContext).authentication - - val header = authorizationHeaderUtil.getAuthorizationHeader() - - Assertions.assertThat(header).isNotNull().isEmpty() - } - - @Test - fun getAuthorizationHeader_JwtAuthentication() { - val jwtToken = JwtAuthenticationToken(Jwt("tokenVal", Instant.now(), - Instant.now().plus(Duration.ofMinutes(3)), hashMapOf("alg" to "HS256") as Map?, hashMapOf("sub" to SUB_VALUE) as Map? - )) - doReturn(jwtToken).`when`(securityContext).authentication - - val header = authorizationHeaderUtil.getAuthorizationHeader() - - Assertions.assertThat(header).isNotNull.isNotEmpty.get().isEqualTo("Bearer tokenVal") - } - - @Test - fun getAuthorizationHeader_OAuth2Authentication_InvalidClient() { - val oauth2Token = getTestOAuth2AuthenticationToken("INVALID") - - doReturn(oauth2Token).`when`(securityContext).authentication - - Assertions.assertThatThrownBy { - authorizationHeaderUtil.getAuthorizationHeader() - }.isInstanceOf(OAuth2AuthorizationException::class.java).hasMessageContaining("[access_denied] The token is expired") - } - - @Test - fun getAuthorizationHeader_OAuth2Authentication() { - val oauth2Token = getTestOAuth2AuthenticationToken(VALID_REGISTRATION_ID) - val authorizedClient = getTestOAuth2AuthorizedClient() - - doReturn(oauth2Token).`when`(securityContext).authentication - doReturn(authorizedClient).`when`(clientService).loadAuthorizedClient(eq(VALID_REGISTRATION_ID), eq(SUB_VALUE)) - - val header = authorizationHeaderUtil.getAuthorizationHeader() - Assertions.assertThat(header).isNotNull.isNotEmpty.get().isEqualTo("Bearer tokenVal") - } - - @Test - fun getAuthorizationHeader_OAuth2Authentication_RefreshToken() { - - doReturn(restTemplateBuilder).`when`(restTemplateBuilder).additionalMessageConverters(any(HttpMessageConverter::class.java)) - doReturn(restTemplateBuilder).`when`(restTemplateBuilder).errorHandler(any(ResponseErrorHandler::class.java)) - doReturn(restTemplateBuilder).`when`(restTemplateBuilder).basicAuthentication(anyString(), anyString()) - - val oauth2Token = getTestOAuth2AuthenticationToken(VALID_REGISTRATION_ID) - val authorizedClient = getTestOAuth2AuthorizedClient(true) - - doReturn(oauth2Token).`when`(securityContext).authentication - doReturn(authorizedClient).`when`(clientService).loadAuthorizedClient(eq(VALID_REGISTRATION_ID), eq(SUB_VALUE)) - - val restTemplate = mock(RestTemplate::class.java) - val refreshResponse = ResponseEntity.of(getTestOAuthIdpTokenResponseDTO(true)) - doReturn(refreshResponse).`when`(restTemplate).exchange(any(RequestEntity::class.java), eq(OAuthIdpTokenResponseDTO::class.java)) - doReturn(restTemplate).`when`(restTemplateBuilder).build() - - val header = authorizationHeaderUtil.getAuthorizationHeader() - Assertions.assertThat(header).isNotNull.isNotEmpty.get().isEqualTo("Bearer tokenVal") - } - - @Test - fun getAuthorizationHeader_OAuth2Authentication_RefreshToken_NoRefreshToken() { - - doReturn(restTemplateBuilder).`when`(restTemplateBuilder).additionalMessageConverters(any(HttpMessageConverter::class.java)) - doReturn(restTemplateBuilder).`when`(restTemplateBuilder).errorHandler(any(ResponseErrorHandler::class.java)) - doReturn(restTemplateBuilder).`when`(restTemplateBuilder).basicAuthentication(anyString(), anyString()) - - val oauth2Token = getTestOAuth2AuthenticationToken(VALID_REGISTRATION_ID) - val authorizedClient = getTestOAuth2AuthorizedClient(true) - - doReturn(oauth2Token).`when`(securityContext).authentication - doReturn(authorizedClient).`when`(clientService).loadAuthorizedClient(eq(VALID_REGISTRATION_ID), eq(SUB_VALUE)) - - val restTemplate = mock(RestTemplate::class.java) - val refreshResponse = ResponseEntity.of(getTestOAuthIdpTokenResponseDTO(false)) - doReturn(refreshResponse).`when`(restTemplate).exchange(any(RequestEntity::class.java), eq(OAuthIdpTokenResponseDTO::class.java)) - doReturn(restTemplate).`when`(restTemplateBuilder).build() - - val header = authorizationHeaderUtil.getAuthorizationHeader() - Assertions.assertThat(header).isNotNull.isNotEmpty.get().isEqualTo("Bearer tokenVal") - } - - @Test - fun getAuthorizationHeader_OAuth2Authentication_RefreshTokenFails() { - - doReturn(restTemplateBuilder).`when`(restTemplateBuilder).additionalMessageConverters(any(HttpMessageConverter::class.java)) - doReturn(restTemplateBuilder).`when`(restTemplateBuilder).errorHandler(any(ResponseErrorHandler::class.java)) - doReturn(restTemplateBuilder).`when`(restTemplateBuilder).basicAuthentication(anyString(), anyString()) - - val oauth2Token = getTestOAuth2AuthenticationToken(VALID_REGISTRATION_ID) - val authorizedClient = getTestOAuth2AuthorizedClient(true) - - doReturn(oauth2Token).`when`(securityContext).authentication - doReturn(authorizedClient).`when`(clientService).loadAuthorizedClient(eq(VALID_REGISTRATION_ID), eq(SUB_VALUE)) - - val restTemplate = mock(RestTemplate::class.java) - doThrow(OAuth2AuthorizationException(OAuth2Error("E"), "error")).`when`(restTemplate).exchange(any(RequestEntity::class.java), eq(OAuthIdpTokenResponseDTO::class.java)) - doReturn(restTemplate).`when`(restTemplateBuilder).build() - - Assertions.assertThatThrownBy { authorizationHeaderUtil.getAuthorizationHeader() }.isInstanceOf(OAuth2AuthenticationException::class.java) - .hasMessageContaining("error") - } - - private fun getTestOAuth2AuthorizedClient() = getTestOAuth2AuthorizedClient(false) - - private fun getTestOAuth2AuthorizedClient(accessTokenExpired: Boolean):OAuth2AuthorizedClient { - val issuedAt = Instant.now() - var expiresAt: Instant? = null - if (accessTokenExpired) { - expiresAt = issuedAt.plus(Duration.ofNanos(1)) - try { - Thread.sleep(1) - } catch (e: Exception) { - fail("Error in Thread.sleep(1) : " + e.message) - } - } else { - expiresAt = issuedAt.plus(Duration.ofMinutes(3)) - } - val token = OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, "tokenVal", issuedAt, expiresAt) - - return OAuth2AuthorizedClient( - ClientRegistration.withRegistrationId(VALID_REGISTRATION_ID) - .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) - .clientId("web-app") - .clientSecret("secret") - .redirectUri("/login/oauth2/code/oidc") - .authorizationUri("http://localhost:8080/auth/realms/master/protocol/openid-connect/auth") - .tokenUri("https://localhost:8080/auth/realms/master/protocol/openid-connect/token") - .build(), - "sub", - token, - OAuth2RefreshToken("refreshVal", Instant.now())) - } - - private fun getTestOAuth2AuthenticationToken(registrationId: String): OAuth2AuthenticationToken { - return OAuth2AuthenticationToken( - DefaultOidcUser(listOf(SimpleGrantedAuthority("USER")), - OidcIdToken.withTokenValue("tokenVal").claim("sub", SUB_VALUE).build()), - listOf(SimpleGrantedAuthority("USER")), registrationId) - } - - private fun getTestOAuthIdpTokenResponseDTO(hasRefreshToken: Boolean): Optional { - val dto = OAuthIdpTokenResponseDTO() - dto.accessToken = "tokenVal" - dto.idToken = "tokenVal" - dto.notBefore = 0L - dto.refreshExpiresIn = "1800" - dto.sessionState = "ccea4a55" - dto.expiresIn = 300L - dto.refreshToken = if (hasRefreshToken) "tokenVal" else null - dto.scope = "openid email profile offline_access" - return Optional.of(dto) - } -}