Skip to content

Instantly share code, notes, and snippets.

@foo4u
Last active August 26, 2024 17:15
Show Gist options
  • Save foo4u/45f6b9d16dd5739d342be5e889521117 to your computer and use it in GitHub Desktop.
Save foo4u/45f6b9d16dd5739d342be5e889521117 to your computer and use it in GitHub Desktop.
Spring Security Resource Server with multiple issuers
spring:
security:
oauth2:
resource-server:
jwt:
issuer-uri: https://token-exchange.example.com/
jwk-set-uri: https://token-exchange.example.com/.well-known/jwks.json
web:
resources:
add-mappings: false
example:
security:
oauth2:
resource-server:
jwt-set:
- issuer-uri: https://auth.example.com/
jwk-set-uri: https://auth.example.com/.well-known/jwks.json
package com.example.service.config
import org.springframework.boot.autoconfigure.security.oauth2.resource.OAuth2ResourceServerProperties
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.boot.context.properties.ConstructorBinding
import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.authentication.AuthenticationManager
import org.springframework.security.authentication.AuthenticationManagerResolver
import org.springframework.security.authentication.ProviderManager
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationProvider
import org.springframework.security.oauth2.server.resource.authentication.JwtIssuerAuthenticationManagerResolver
import org.springframework.security.web.SecurityFilterChain
import java.net.URL
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
@EnableConfigurationProperties(AdditionalResourceServerProperties::class)
class SecurityConfig(
private val resourceServerProperties: OAuth2ResourceServerProperties,
private val additionalResourceServerProperties: AdditionalResourceServerProperties
) {
@Bean
fun httpSecurity(http: HttpSecurity): SecurityFilterChain =
http
.authorizeRequests { it.anyRequest().authenticated() }
.csrf { it.disable() }
.oauth2ResourceServer {
it.authenticationManagerResolver(
JwtIssuerAuthenticationManagerResolver(authResolver())
)
}
.build()
private fun primaryProviderManager(): ProviderManager =
buildProviderManager(jwkSetUri = resourceServerProperties.jwt.jwkSetUri)
private fun buildProviderManager(jwkSetUri: String): ProviderManager {
val decoder = NimbusJwtDecoder.withJwkSetUri(jwkSetUri)
.jwsAlgorithm(SignatureAlgorithm.RS256)
.jwsAlgorithm(SignatureAlgorithm.ES256)
.jwsAlgorithm(SignatureAlgorithm.ES384)
.build()
return ProviderManager(JwtAuthenticationProvider(decoder))
}
private fun buildAuthResolverMap(): Map<String, ProviderManager> =
mutableMapOf(Pair(resourceServerProperties.jwt.issuerUri, primaryProviderManager())).apply {
additionalResourceServerProperties.jwtSet.forEach { issuer ->
put(issuer.issuerUri.toString(), buildProviderManager(issuer.jwkSetUri.toString()))
}
}
private fun authResolver(): MultiIssuerAuthenticationManagerResolver =
MultiIssuerAuthenticationManagerResolver(buildAuthResolverMap())
}
class MultiIssuerAuthenticationManagerResolver(
private val trustedIssuers: Map<String, AuthenticationManager>
) : AuthenticationManagerResolver<String> {
override fun resolve(context: String?): AuthenticationManager? =
trustedIssuers[context]
}
@ConfigurationProperties(prefix = "example.security.oauth2.resource-server")
@ConstructorBinding
data class AdditionalResourceServerProperties(
val jwtSet: List<JwtIssuerProperties>
) {
companion object {
@ConstructorBinding
data class JwtIssuerProperties(
val issuerUri: URL,
val jwkSetUri: URL
)
}
}
@juntzou
Copy link

juntzou commented Aug 26, 2024

@foo4u Is there an example of a unit test using WebTestClient that tests this? I've tried on my end using mockJwt() and did not provide an issuer but the tests still pass by returning 200 OK (whereas it should return 401)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment