replace local jwt with library version

- update ConfigurationController to handle hlaeja jwt instead of jwtService
- update MeasurementController to handle hlaeja jwt instead of jwtService
- add dependency for hlaeja jwt
- remove dependencies for jjwt
- remove JwtService.kt
- remove PublicKeyProvider.kt
- remove jwt key property explanation from additional-spring-configuration-metadata.json
This commit is contained in:
2025-01-02 05:55:35 +01:00
parent c5ff6e555a
commit 84d09f6dbb
10 changed files with 36 additions and 95 deletions

View File

@@ -1,7 +1,9 @@
package ltd.hlaeja.controller
import java.util.UUID
import ltd.hlaeja.jwt.service.PublicJwtService
import ltd.hlaeja.service.DeviceConfigurationService
import ltd.hlaeja.service.JwtService
import ltd.hlaeja.service.DeviceRegistryService
import ltd.hlaeja.util.toDeviceResponse
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestHeader
@@ -12,12 +14,17 @@ import org.springframework.web.bind.annotation.RestController
@RequestMapping("/configuration")
class ConfigurationController(
private val configurationService: DeviceConfigurationService,
private val jwtService: JwtService,
private val deviceRegistry: DeviceRegistryService,
private val publicJwtService: PublicJwtService,
) {
@GetMapping
suspend fun getNodeConfiguration(
@RequestHeader("Identity") identityToken: String,
): Map<String, String> = jwtService.getIdentity(identityToken)
): Map<String, String> = readIdentityToken(identityToken)
.let { deviceRegistry.getIdentityFromDevice(it) }
.let { configurationService.getConfiguration(it.node).toDeviceResponse() }
private fun readIdentityToken(identityToken: String): UUID = publicJwtService
.verify(identityToken) { claims -> UUID.fromString(claims.payload["device"] as String) }
}

View File

@@ -1,8 +1,11 @@
package ltd.hlaeja.controller
import java.util.UUID
import ltd.hlaeja.jwt.service.PublicJwtService
import ltd.hlaeja.library.deviceData.MeasurementData
import ltd.hlaeja.library.deviceRegistry.Identity
import ltd.hlaeja.service.DeviceDataService
import ltd.hlaeja.service.JwtService
import ltd.hlaeja.service.DeviceRegistryService
import org.springframework.http.HttpStatus.CREATED
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PostMapping
@@ -16,13 +19,14 @@ import org.springframework.web.bind.annotation.RestController
@RequestMapping("/measurement")
class MeasurementController(
private val dataService: DeviceDataService,
private val jwtService: JwtService,
private val deviceRegistry: DeviceRegistryService,
private val publicJwtService: PublicJwtService,
) {
@GetMapping
suspend fun getNodeMeasurement(
@RequestHeader("Identity") identityToken: String,
): Map<String, Number> = jwtService.getIdentity(identityToken)
): Map<String, Number> = readIdentityToken(identityToken)
.let { dataService.getMeasurement(it.client, it.node).fields }
@PostMapping
@@ -31,7 +35,7 @@ class MeasurementController(
@RequestHeader("Identity") identityToken: String,
@RequestBody measurement: Map<String, Number>,
) {
return jwtService.getIdentity(identityToken)
return readIdentityToken(identityToken)
.let {
dataService.addMeasurement(
it.client,
@@ -45,4 +49,8 @@ class MeasurementController(
)
}
}
private suspend fun readIdentityToken(identityToken: String): Identity.Response = publicJwtService
.verify(identityToken) { claims -> UUID.fromString(claims.payload["device"] as String) }
.let { deviceRegistry.getIdentityFromDevice(it) }
}

View File

@@ -1,42 +0,0 @@
package ltd.hlaeja.service
import io.github.oshai.kotlinlogging.KotlinLogging
import io.jsonwebtoken.JwtException
import io.jsonwebtoken.JwtParser
import io.jsonwebtoken.Jwts
import java.util.UUID
import ltd.hlaeja.library.deviceRegistry.Identity
import ltd.hlaeja.property.JwtProperty
import ltd.hlaeja.util.PublicKeyProvider
import org.springframework.http.HttpStatus
import org.springframework.stereotype.Service
import org.springframework.web.server.ResponseStatusException
private val log = KotlinLogging.logger {}
@Service
class JwtService(
jwtProperty: JwtProperty,
private val deviceRegistry: DeviceRegistryService,
) {
private val parser: JwtParser = Jwts.parser()
.verifyWith(PublicKeyProvider.load(jwtProperty.publicKey))
.build()
suspend fun getIdentity(
identityToken: String,
): Identity.Response = try {
readIdentity(identityToken)
.let { deviceRegistry.getIdentityFromDevice(it) }
} catch (e: JwtException) {
log.warn { e.localizedMessage }
throw ResponseStatusException(HttpStatus.UNAUTHORIZED)
}
private suspend fun readIdentity(
identity: String,
): UUID = parser.parseSignedClaims(identity)
.let { UUID.fromString(it.payload["device"] as String) }
.also { log.debug { "Identified client device: $it" } }
}

View File

@@ -1,35 +0,0 @@
package ltd.hlaeja.util
import java.security.KeyFactory
import java.security.interfaces.RSAPublicKey
import java.security.spec.X509EncodedKeySpec
import java.util.Base64.getDecoder
import ltd.hlaeja.exception.KeyProviderException
object PublicKeyProvider {
fun load(
pemFile: String,
): RSAPublicKey = readPublicPemFile(pemFile)
.let(::makePublicKey)
private fun makePublicKey(
publicKeyBytes: ByteArray,
): RSAPublicKey = KeyFactory.getInstance("RSA")
.generatePublic(X509EncodedKeySpec(publicKeyBytes)) as RSAPublicKey
private fun readPublicPemFile(
publicKey: String,
): ByteArray = javaClass.classLoader
.getResource(publicKey)
?.readText()
?.let(::getPublicKeyByteArray)
?: throw KeyProviderException("Could not load public key")
private fun getPublicKeyByteArray(
keyText: String,
): ByteArray = keyText.replace(Regex("[\r\n]+"), "")
.removePrefix("-----BEGIN PUBLIC KEY-----")
.removeSuffix("-----END PUBLIC KEY-----")
.let { getDecoder().decode(it) }
}

View File

@@ -20,11 +20,6 @@
"type": "java.lang.String",
"description": "Application build os version."
},
{
"name": "jwt.public-key",
"type": "java.lang.String",
"description": "Jwt public key file."
},
{
"name": "device-registry.url",
"type": "java.lang.String",

View File

@@ -0,0 +1,9 @@
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3ZdlbISX729m5Ur1pVhg
XIvazcgUt0T0G32ML0tfwQ4aWTfwPII0SQRThaN6eiiBMRa0V8JMih1LT8JmGgst
dEx2nhMbVs/Osu8MhmP86c+HB/jPa1+0IR1TZKXoZoF52D2ZtoVf+mOWggAcm1R+
V0Fj2cR/pgLkVt3GKUE2OokFC1iFUQFjThd1EzKcOv53TUek8FY8t66npQ4t3unD
bXZKoGXMuXCqZVykMbGTUQFRuT3NAOXRrJP+UDeY2uM2Yk98J+8FtLDYD6jpmyi0
ghv6k8pK1w1n5NI3atVv5ZMUeQZ36AXL8SZi1105mamhLVQ0e0JixoMOPh7ziFyv
uwIDAQAB
-----END PUBLIC KEY-----