Т.к. тема с примерами кода закрыта, отправлю сюда вариант на Kotlin + Ktor (gist):
import kotlinx.serialization.Serializable
import io.ktor.http.*
import java.security.MessageDigest
import java.util.*
import io.ktor.client.*
import io.ktor.client.engine.cio.*
import io.ktor.client.features.*
import io.ktor.client.features.cookies.*
import io.ktor.client.features.json.*
import io.ktor.client.features.json.serializer.*
import io.ktor.client.features.logging.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
import kotlinx.coroutines.runBlocking
private const val LOGIN = "login"
private const val PASSWORD = "password"
data class KeeneticAuthHeaderValues(
val map: Map<KeeneticAuthHeaderEnum, String>
) {
val xndmChallenge: String by lazy { map.getValue(KeeneticAuthHeaderEnum.XNDMChallenge) }
val xndmRealm: String by lazy { map.getValue(KeeneticAuthHeaderEnum.XNDMRealm) }
}
@Serializable
data class UserCredentials(val login: String, val password: String)
fun Headers.asMap(): EnumMap<KeeneticAuthHeaderEnum, String> = EnumMap(
entries().asSequence()
.filter { it -> it.key in KeeneticAuthHeaderEnum.values().map { it.title } }
.associate { header -> KeeneticAuthHeaderEnum.from(header.key) to header.value.first() }
)
// However, here we have to use a custom byte to hex converter to get the hashed value in hexadecimal
private fun printHexBinary(hash: ByteArray): String {
val hexString = StringBuilder(2 * hash.size)
for (i in hash.indices) {
val hex = Integer.toHexString(0xff and hash[i].toInt())
if (hex.length == 1) {
hexString.append('0')
}
hexString.append(hex)
}
return hexString.toString()
}
fun String.encodeMd5(): String {
return printHexBinary(MessageDigest.getInstance("MD5").digest(toByteArray()))
}
// see more https://www.baeldung.com/sha-256-hashing-java
fun String.encodeSha256(): String {
// Java provides inbuilt MessageDigest class for SHA-256 hashing
return printHexBinary(MessageDigest.getInstance("SHA-256").digest(toByteArray()))
}
fun main() = runBlocking {
// HttpClientEngineFactory using a Coroutine based I/O implementation
val client = HttpClient(CIO) {
install(JsonFeature) {
serializer = KotlinxSerializer(kotlinx.serialization.json.Json {
ignoreUnknownKeys = true
prettyPrint = true
isLenient = true
})
}
//log all requests
install(Logging) {
logger = Logger.DEFAULT
level = LogLevel.ALL
}
install(HttpCookies) {
// Will keep an in-memory map with all the cookies from previous requests.
storage = AcceptAllCookiesStorage()
}
}
// try with resource kotlin way
client.use {
// do auth
authorized(client) {
// here we have authorize
val interfaceInfo: HttpResponse = client.get("http://192.168.1.1/rci/show/interface?name=ISP")
}
}
}
private suspend fun authorized(client: HttpClient, body: suspend () -> Unit) {
val unauthorizedResponse = client.get<HttpResponse>("http://192.168.1.1/auth") { expectSuccess = false }
if (unauthorizedResponse.status == HttpStatusCode.Unauthorized) {
val tokenAndRealm = KeeneticAuthHeaderValues(unauthorizedResponse.headers.asMap())
val authPostResponse = authRequest(client, tokenAndRealm, LOGIN, PASSWORD)
if (authPostResponse.status == HttpStatusCode.OK) {
body()
}
} else if(unauthorizedResponse.status == HttpStatusCode.OK){
body()
}
}
private suspend fun authRequest(
client: HttpClient,
tokenAndRealm: KeeneticAuthHeaderValues,
): HttpResponse {
val authPostResponse = client.post<HttpResponse>("http://192.168.1.1/auth") {
contentType(ContentType.Application.Json)
expectSuccess = false
val md5 = (LOGIN + ":" + tokenAndRealm.xndmRealm + ':' + PASSWORD).encodeMd5()
body = UserCredentials(
login = LOGIN,
password = (tokenAndRealm.xndmChallenge + md5).encodeSha256()
)
}
return authPostResponse
}