I've been looking for a solution in many questions and forums but nothing worked for me.
I've a use case, in which we register Idp configuration in another service, and then we retrieve that particular configuration providing an orgId
query param. Somehow I could built something that works on localhost with Okta as Idp, based on these sites:
Here I could delegate Idp selection before real saml auth gets triggered:
Spring Security SAML2 dynamic selection of IDPs or dynamic URLs for them
It works on localhost even SLO, but when we deploy the service to a EKR cluster, for all backend services, at first we can load the login redirection like:
http://dev.org.com/services/sso/auth-sso?orgId=<id>
It goes to Okta, and we can login, when it tries to redirect back to our service, the "/" path doesn't get triggered and somehow the url it's redirected to
http://dev.org.com/auth-sso?error
We have tried, context-path
setting in application.yml
with no success, and lastly I read this reference
Also referenced to this questions: Spring SAML 2.0 behind Nginx
but this spring saml version, doesn't recognize SAMLContextProviderLB
or SAMLContextProviderImpl
Here some code:
build.gradle.kt
:
constraints {
implementation("org.opensaml:opensaml-core:4.1.1")
implementation("org.opensaml:opensaml-saml-api:4.1.1")
implementation("org.opensaml:opensaml-saml-impl:4.1.1")
}
// spring
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-security")
// saml2
implementation("org.springframework.security.extensions:spring-security-saml2-core:2.0.0.M31")
implementation("org.springframework.security:spring-security-saml2-service-provider:5.7.4")
In my SamlConfig class, I define these beans:
securityFilterChain
:
@Bean
fun securityWebFilterChain(http: HttpSecurity): SecurityFilterChain {
http
.authorizeRequests {
it
.mvcMatchers("/auth-sso").permitAll()
.mvcMatchers("/actuator/*").permitAll()
.anyRequest().authenticated()
}
.saml2Login {
it.loginPage("/auth-sso")
}
.saml2Logout{}
return http.build()
}
and a custom implementation of RelyingPartyRegistrationRepository
, as LazyRelyingPartyRegistrationRepository
:
@Bean
protected fun relyingPartyRegistrations(): RelyingPartyRegistrationRepository? {
return LazyRelyingPartyRegistrationRepository()
}
And this is my /auth-sso
@GetMapping(value = ["/auth-sso"])
@ResponseBody
fun login(
@RequestParam(name = "orgId", required = true) orgId: String,
request: HttpServletRequest,
response: HttpServletResponse
) {
try {
val id = runBlocking {
findIdpConfigurationUseCase.execute(object : UserOrganizationLogin {
override val organizationId = organizationId
override val token: String? = null
})
}
val spInitiateUrl = "saml2/authenticate/$id"
log.debug("Redirecting to {}", spInitiateUrl)
response.sendRedirect(spInitiateUrl)
}
catch (e: Exception) {
log.error("Error preparing assertion info: {}", e)
response.sendRedirect(MessageFormat.format(environment.getRequiredProperty(HOME_URL), ""))
}
}
My "auth entry point" /
which recieves the Principal crendentials and login the user calling a use case with internally create user and generates token:
@RequestMapping("/")
fun index(model: Model, @AuthenticationPrincipal principal: Saml2AuthenticatedPrincipal, response: HttpServletResponse) {
log.debug("User is authenticated!!!")
val loginInfo = runBlocking {
loginUserUseCase.execute(principal)
}
response.sendRedirect(
environment.getRequiredProperty(HOME_URL) +
MessageFormat.format(environment.getRequiredProperty(SSO_HOME_PARAMS), loginInfo.token, loginInfo.organizationId)
)
}