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:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -39,5 +39,5 @@ out/
|
|||||||
### Kotlin ###
|
### Kotlin ###
|
||||||
.kotlin
|
.kotlin
|
||||||
|
|
||||||
### cert ###
|
#### Hlæja ###
|
||||||
cert/
|
/cert/
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ Classes and endpoints, to shape and to steer, Devices and sensors, their purpose
|
|||||||
| spring.data.redis.host | ✓ | Redis host |
|
| spring.data.redis.host | ✓ | Redis host |
|
||||||
| spring.data.redis.port | | Redis port |
|
| spring.data.redis.port | | Redis port |
|
||||||
| spring.data.redis.database | ✓ | Redis database |
|
| spring.data.redis.database | ✓ | Redis database |
|
||||||
|
| spring.data.redis.password | ✗ | Redis password |
|
||||||
| cache.time-to-live | | Cache time to live (minutes) |
|
| cache.time-to-live | | Cache time to live (minutes) |
|
||||||
| jwt.public-key | ✓ | JWT public key |
|
| jwt.public-key | ✓ | JWT public key |
|
||||||
| device-registry.url | ✓ | Device Register URL |
|
| device-registry.url | ✓ | Device Register URL |
|
||||||
|
|||||||
@@ -17,14 +17,12 @@ dependencies {
|
|||||||
implementation(hlaeja.kotlinx.coroutines)
|
implementation(hlaeja.kotlinx.coroutines)
|
||||||
implementation(hlaeja.micrometer.registry.influx)
|
implementation(hlaeja.micrometer.registry.influx)
|
||||||
implementation(hlaeja.library.hlaeja.common.messages)
|
implementation(hlaeja.library.hlaeja.common.messages)
|
||||||
|
implementation(hlaeja.library.hlaeja.jwt)
|
||||||
implementation(hlaeja.springboot.starter.actuator)
|
implementation(hlaeja.springboot.starter.actuator)
|
||||||
implementation(hlaeja.springboot.starter.cache)
|
implementation(hlaeja.springboot.starter.cache)
|
||||||
implementation(hlaeja.springboot.starter.redis)
|
implementation(hlaeja.springboot.starter.redis)
|
||||||
implementation(hlaeja.springboot.starter.webflux)
|
implementation(hlaeja.springboot.starter.webflux)
|
||||||
|
|
||||||
runtimeOnly(hlaeja.jjwt.impl)
|
|
||||||
runtimeOnly(hlaeja.jjwt.jackson)
|
|
||||||
|
|
||||||
testImplementation(hlaeja.kotlin.test.junit5)
|
testImplementation(hlaeja.kotlin.test.junit5)
|
||||||
testImplementation(hlaeja.kotlinx.coroutines.test)
|
testImplementation(hlaeja.kotlinx.coroutines.test)
|
||||||
testImplementation(hlaeja.mockk)
|
testImplementation(hlaeja.mockk)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
kotlin.code.style=official
|
kotlin.code.style=official
|
||||||
org.gradle.jvmargs=-Xmx1g
|
org.gradle.jvmargs=-Xmx1g
|
||||||
version=0.4.0-SNAPSHOT
|
version=0.4.0-SNAPSHOT
|
||||||
catalog=0.7.0
|
catalog=0.8.0-SNAPSHOT
|
||||||
docker.port.expose=8443
|
docker.port.expose=8443
|
||||||
container.port.expose=8443
|
container.port.expose=8443
|
||||||
container.port.host=9000
|
container.port.host=9000
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
package ltd.hlaeja.controller
|
package ltd.hlaeja.controller
|
||||||
|
|
||||||
|
import java.util.UUID
|
||||||
|
import ltd.hlaeja.jwt.service.PublicJwtService
|
||||||
import ltd.hlaeja.service.DeviceConfigurationService
|
import ltd.hlaeja.service.DeviceConfigurationService
|
||||||
import ltd.hlaeja.service.JwtService
|
import ltd.hlaeja.service.DeviceRegistryService
|
||||||
import ltd.hlaeja.util.toDeviceResponse
|
import ltd.hlaeja.util.toDeviceResponse
|
||||||
import org.springframework.web.bind.annotation.GetMapping
|
import org.springframework.web.bind.annotation.GetMapping
|
||||||
import org.springframework.web.bind.annotation.RequestHeader
|
import org.springframework.web.bind.annotation.RequestHeader
|
||||||
@@ -12,12 +14,17 @@ import org.springframework.web.bind.annotation.RestController
|
|||||||
@RequestMapping("/configuration")
|
@RequestMapping("/configuration")
|
||||||
class ConfigurationController(
|
class ConfigurationController(
|
||||||
private val configurationService: DeviceConfigurationService,
|
private val configurationService: DeviceConfigurationService,
|
||||||
private val jwtService: JwtService,
|
private val deviceRegistry: DeviceRegistryService,
|
||||||
|
private val publicJwtService: PublicJwtService,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
@GetMapping
|
@GetMapping
|
||||||
suspend fun getNodeConfiguration(
|
suspend fun getNodeConfiguration(
|
||||||
@RequestHeader("Identity") identityToken: String,
|
@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() }
|
.let { configurationService.getConfiguration(it.node).toDeviceResponse() }
|
||||||
|
|
||||||
|
private fun readIdentityToken(identityToken: String): UUID = publicJwtService
|
||||||
|
.verify(identityToken) { claims -> UUID.fromString(claims.payload["device"] as String) }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
package ltd.hlaeja.controller
|
package ltd.hlaeja.controller
|
||||||
|
|
||||||
|
import java.util.UUID
|
||||||
|
import ltd.hlaeja.jwt.service.PublicJwtService
|
||||||
import ltd.hlaeja.library.deviceData.MeasurementData
|
import ltd.hlaeja.library.deviceData.MeasurementData
|
||||||
|
import ltd.hlaeja.library.deviceRegistry.Identity
|
||||||
import ltd.hlaeja.service.DeviceDataService
|
import ltd.hlaeja.service.DeviceDataService
|
||||||
import ltd.hlaeja.service.JwtService
|
import ltd.hlaeja.service.DeviceRegistryService
|
||||||
import org.springframework.http.HttpStatus.CREATED
|
import org.springframework.http.HttpStatus.CREATED
|
||||||
import org.springframework.web.bind.annotation.GetMapping
|
import org.springframework.web.bind.annotation.GetMapping
|
||||||
import org.springframework.web.bind.annotation.PostMapping
|
import org.springframework.web.bind.annotation.PostMapping
|
||||||
@@ -16,13 +19,14 @@ import org.springframework.web.bind.annotation.RestController
|
|||||||
@RequestMapping("/measurement")
|
@RequestMapping("/measurement")
|
||||||
class MeasurementController(
|
class MeasurementController(
|
||||||
private val dataService: DeviceDataService,
|
private val dataService: DeviceDataService,
|
||||||
private val jwtService: JwtService,
|
private val deviceRegistry: DeviceRegistryService,
|
||||||
|
private val publicJwtService: PublicJwtService,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
@GetMapping
|
@GetMapping
|
||||||
suspend fun getNodeMeasurement(
|
suspend fun getNodeMeasurement(
|
||||||
@RequestHeader("Identity") identityToken: String,
|
@RequestHeader("Identity") identityToken: String,
|
||||||
): Map<String, Number> = jwtService.getIdentity(identityToken)
|
): Map<String, Number> = readIdentityToken(identityToken)
|
||||||
.let { dataService.getMeasurement(it.client, it.node).fields }
|
.let { dataService.getMeasurement(it.client, it.node).fields }
|
||||||
|
|
||||||
@PostMapping
|
@PostMapping
|
||||||
@@ -31,7 +35,7 @@ class MeasurementController(
|
|||||||
@RequestHeader("Identity") identityToken: String,
|
@RequestHeader("Identity") identityToken: String,
|
||||||
@RequestBody measurement: Map<String, Number>,
|
@RequestBody measurement: Map<String, Number>,
|
||||||
) {
|
) {
|
||||||
return jwtService.getIdentity(identityToken)
|
return readIdentityToken(identityToken)
|
||||||
.let {
|
.let {
|
||||||
dataService.addMeasurement(
|
dataService.addMeasurement(
|
||||||
it.client,
|
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) }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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" } }
|
|
||||||
}
|
|
||||||
@@ -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) }
|
|
||||||
}
|
|
||||||
@@ -20,11 +20,6 @@
|
|||||||
"type": "java.lang.String",
|
"type": "java.lang.String",
|
||||||
"description": "Application build os version."
|
"description": "Application build os version."
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "jwt.public-key",
|
|
||||||
"type": "java.lang.String",
|
|
||||||
"description": "Jwt public key file."
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "device-registry.url",
|
"name": "device-registry.url",
|
||||||
"type": "java.lang.String",
|
"type": "java.lang.String",
|
||||||
|
|||||||
9
src/test/resources/cert/valid-public-key.pem
Normal file
9
src/test/resources/cert/valid-public-key.pem
Normal 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-----
|
||||||
Reference in New Issue
Block a user