0

I'm trying to implement a client for spring-security's SwitchUserFilter (server-side). As client I'm using KTOR (with OKHttp inside).

SwitchUserFilter requires me to log in, then drop the Authorization header and use the Cookie tinstead. If I send the Authorization header together with the Cookie header, spring's SecurityContext coming from SwitchUserFilter will be overwritten with my admin user again.

Is there something I can configure in KTOR, so that the [Authorization] header is removed, once I have switched the user?

Frischling
  • 2,100
  • 14
  • 34
  • This might answer your question: https://stackoverflow.com/questions/69991824/how-to-clear-bearer-tokens-in-ktor-client-for-android?rq=1 – Delta George Apr 20 '22 at 15:12
  • 1
    Alsmost, I did `client.feature(Auth)!!.providers.removeAll { true }` (since I only have the BasicAuth provider there, that worked). Thanks! Do you want to put this in the answer? The solution is similar, but the question is different, so I think its worth it. – Frischling Apr 20 '22 at 15:38
  • You are allowed to answer your own question. – Delta George Apr 20 '22 at 15:43

1 Answers1

0

KTOR has to be setup with two things:

  1. SwitchUserFilter will send a redirect (HTTP 302) that we need to ignore. For this a HttpResponseValidator needs to be configured.

  2. Auth needs to be removed similar to the comment from @Delta_George

    HttpClient(OkHttp) {
     HttpResponseValidator {
         // for 302 don't react - so we can switch user successfully. If we follow, this doesn't work anymore.
         validateResponse { response ->
             val statusCode = response.status.value
             val originCall = response.call
             if (statusCode < 300 || originCall.attributes.contains(ValidateMark)) {
                 return@validateResponse
             }
    
             val exceptionCall = originCall.save().apply {
                 attributes.put(ValidateMark, Unit)
             }
             val excResp = exceptionCall.response
             val excRespTxt = excResp.readText()
             when (statusCode) {
                 302 -> {} // do nothing on "Found" statuscode
                 in 300..399 -> throw RedirectResponseException(excResp, excRespTxt)
                 in 400..499 -> throw ClientRequestException(excResp, excRespTxt)
                 in 500..599 -> throw ServerResponseException(excResp, excRespTxt)
                 else -> throw ResponseException(excResp, excRespTxt)
             }
         }
     }
     ... // other configurations
    }
    

and impersonate(...):

    suspend fun impersonate(impersonateWithUser: PersonEntity): Impersonation<PersonEntity> {
        return runCatching {
                val toImpersonate = impersonateWithUser.login.replace(Regex("^\\+"), "%2B")
                client.get<HttpResponse>("$BASE_URL/login/impersonate?username=${toImpersonate}") // with baseauth again
            }.map {
                when (it.status) {
                    HttpStatusCode.Found -> {
                        client.feature(Auth)!!.providers.removeAll { true }
                        Impersonation.ok(impersonateWithUser)
                    }
                    else -> Impersonation.failure(impersonateWithUser, it)
                }
            }.getOrElse {
                Log.e(TAG, "impersonate: ", it)
                Impersonation.communicationError(impersonateWithUser, it)
            }
    }

to end the impersonation you call the respective endpoint given in SwitchUserFilter on the serverside.

Frischling
  • 2,100
  • 14
  • 34