MOB-42 Fixed token authentication issues (wrong library version, cache getting recreated every request, origin in wrong form)

This commit is contained in:
TanelOrumaa 2021-11-11 21:47:27 +02:00
parent 9b0cb1a22d
commit 636beeb7f3
15 changed files with 64 additions and 21 deletions

View File

@ -90,7 +90,7 @@ class AuthFragment : Fragment() {
if (args.auth) { if (args.auth) {
val jws = Authenticator(comms).authenticate( val jws = Authenticator(comms).authenticate(
intentParameters.challenge, intentParameters.challenge,
intentParameters.authUrl, intentParameters.origin,
viewModel.userPin viewModel.userPin
) )
intentParameters.setToken(jws) intentParameters.setToken(jws)

View File

@ -14,6 +14,7 @@ import com.tarkvaraprojekt.mobileauthapp.databinding.FragmentHomeBinding
import com.tarkvaraprojekt.mobileauthapp.model.ParametersViewModel import com.tarkvaraprojekt.mobileauthapp.model.ParametersViewModel
import com.tarkvaraprojekt.mobileauthapp.model.SmartCardViewModel import com.tarkvaraprojekt.mobileauthapp.model.SmartCardViewModel
import java.lang.Exception import java.lang.Exception
import java.net.URLDecoder
/** /**
* HomeFragment is only shown to the user when then the user launches the application. When the application * HomeFragment is only shown to the user when then the user launches the application. When the application
@ -30,6 +31,7 @@ class HomeFragment : Fragment() {
private var binding: FragmentHomeBinding? = null private var binding: FragmentHomeBinding? = null
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup?, container: ViewGroup?,
@ -56,11 +58,16 @@ class HomeFragment : Fragment() {
// We use !! because we want an exception when something is not right. // We use !! because we want an exception when something is not right.
intentParams.setChallenge(requireActivity().intent.getStringExtra("challenge")!!) intentParams.setChallenge(requireActivity().intent.getStringExtra("challenge")!!)
intentParams.setAuthUrl(requireActivity().intent.getStringExtra("authUrl")!!) intentParams.setAuthUrl(requireActivity().intent.getStringExtra("authUrl")!!)
intentParams.setOrigin(requireActivity().intent.getStringExtra("originUrl")!!)
} else { //Website } else { //Website
// Currently the test website won't send the authUrl parameter // Currently the test website won't send the authUrl parameter
//Log.i("intentDebugging", requireActivity().intent.data.toString()) //Log.i("intentDebugging", requireActivity().intent.data.toString())
intentParams.setChallenge(requireActivity().intent.data!!.getQueryParameter("challenge")!!) var challenge = requireActivity().intent.data!!.getQueryParameter("challenge")!!
// TODO: Since due to encoding plus gets converted to space, temporary solution is to replace it back.
challenge = challenge.replace(" ", "+")
intentParams.setChallenge(challenge)
intentParams.setAuthUrl(requireActivity().intent.data!!.getQueryParameter("authUrl")!!) intentParams.setAuthUrl(requireActivity().intent.data!!.getQueryParameter("authUrl")!!)
intentParams.setOrigin(requireActivity().intent.data!!.getQueryParameter("originUrl")!!)
} }
} catch (e: Exception) { } catch (e: Exception) {
// There was a problem with parameters, which means that authentication is not possible. // There was a problem with parameters, which means that authentication is not possible.

View File

@ -67,7 +67,7 @@ class ResultFragment : Fragment() {
Ion.getDefault(activity).getConscryptMiddleware().enable(false) Ion.getDefault(activity).getConscryptMiddleware().enable(false)
Ion.with(activity) Ion.with(activity)
.load("https://6bb0-85-253-195-252.ngrok.io/auth/authentication") .load(paramsModel.origin + paramsModel.authUrl)
.setJsonObjectBody(json) .setJsonObjectBody(json)
.asJsonObject() .asJsonObject()
.setCallback { e, result -> .setCallback { e, result ->

View File

@ -8,7 +8,7 @@ import java.security.MessageDigest
import java.time.LocalDateTime import java.time.LocalDateTime
import java.time.ZoneOffset import java.time.ZoneOffset
class Authenticator(val comms : Comms) { class Authenticator(val comms: Comms) {
val type = "JWT" val type = "JWT"
val algorithm = "ES384" val algorithm = "ES384"
@ -36,7 +36,7 @@ class Authenticator(val comms : Comms) {
// Get header and claims. // Get header and claims.
val header = """{"typ":"$type","alg":"$algorithm","x5c":["$base64cert"]}""" val header = """{"typ":"$type","alg":"$algorithm","x5c":["$base64cert"]}"""
val claims = val claims =
"""{"iat":"$epoch","exp":"$exp","aud":"$originUrl","iss":"$iss","sub":"$sub","nonce":"$challenge","cnf":{"tbh":""}}""" """{"iat":"$epoch","exp":"$exp","aud":["$originUrl"],"iss":"$iss","sub":"$sub","nonce":"$challenge","cnf":{"tbh":""}}"""
val jwt = base64Encode(header.toByteArray(Charsets.UTF_8)) + "." + base64Encode( val jwt = base64Encode(header.toByteArray(Charsets.UTF_8)) + "." + base64Encode(
claims.toByteArray(Charsets.UTF_8) claims.toByteArray(Charsets.UTF_8)
@ -51,7 +51,7 @@ class Authenticator(val comms : Comms) {
return jwt + "." + base64Encode(signed) return jwt + "." + base64Encode(signed)
} }
fun base64Encode(bytes: ByteArray) : String? { fun base64Encode(bytes: ByteArray): String? {
val encoded = java.util.Base64.getUrlEncoder().encodeToString(bytes) val encoded = java.util.Base64.getUrlEncoder().encodeToString(bytes)
return encoded.replace("=", "") return encoded.replace("=", "")
} }

View File

@ -13,6 +13,9 @@ class ParametersViewModel: ViewModel() {
private var _token: String = "" private var _token: String = ""
val token get() = _token val token get() = _token
private var _origin: String = ""
val origin get() = _origin
fun setChallenge(newChallenge: String) { fun setChallenge(newChallenge: String) {
_challenge = newChallenge _challenge = newChallenge
} }
@ -24,4 +27,8 @@ class ParametersViewModel: ViewModel() {
fun setToken(newToken: String) { fun setToken(newToken: String) {
_token = newToken _token = newToken
} }
fun setOrigin(newOrigin: String) {
_origin = newOrigin
}
} }

View File

@ -42,6 +42,11 @@
<artifactId>spring-boot-starter-test</artifactId> <artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.9.0</version>
</dependency>
<dependency> <dependency>
<groupId>org.webeid.security</groupId> <groupId>org.webeid.security</groupId>
<artifactId>authtoken-validation</artifactId> <artifactId>authtoken-validation</artifactId>

View File

@ -10,7 +10,7 @@ class ApplicationConfiguration {
// Endpoint for authentication // Endpoint for authentication
val AUTHENTICATION_ENDPOINT_URL = "/auth/authentication" val AUTHENTICATION_ENDPOINT_URL = "/auth/authentication"
// URL for application. Use ngrok for HTTPS (or a tool of your own choice) and put the HTTPS link here. // URL for application. Use ngrok for HTTPS (or a tool of your own choice) and put the HTTPS link here.
val WEBSITE_ORIGIN_URL = "https://6bb0-85-253-195-252.ngrok.io" val WEBSITE_ORIGIN_URL = "https://2c2c-85-253-195-252.ngrok.io"
} }
} }

View File

@ -1,6 +1,8 @@
package com.tarkvaratehnika.demobackend.config package com.tarkvaratehnika.demobackend.config
import com.github.benmanes.caffeine.jcache.spi.CaffeineCachingProvider import com.github.benmanes.caffeine.jcache.spi.CaffeineCachingProvider
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration import org.springframework.context.annotation.Configuration
import org.springframework.core.io.support.PathMatchingResourcePatternResolver import org.springframework.core.io.support.PathMatchingResourcePatternResolver
@ -28,14 +30,25 @@ import javax.cache.configuration.MutableConfiguration
import javax.cache.expiry.CreatedExpiryPolicy import javax.cache.expiry.CreatedExpiryPolicy
import javax.cache.expiry.Duration import javax.cache.expiry.Duration
import javax.cache.configuration.FactoryBuilder.factoryOf
@Configuration @Configuration
class ValidationConfiguration { class ValidationConfiguration {
private val LOG: Logger = LoggerFactory.getLogger(ValidationConfiguration::class.java)
private val NONCE_TTL_MINUTES: Long = 5 private val NONCE_TTL_MINUTES: Long = 5
private val CACHE_NAME = "nonceCache" private val CACHE_NAME = "nonceCache"
private val CERTS_RESOURCE_PATH = "/certs/" private val CERTS_RESOURCE_PATH = "/certs/"
private val TRUSTED_CERTIFICATES_JKS = "trusted_certificates.jks" private val TRUSTED_CERTIFICATES_JKS = "trusted_certificates.jks"
private val TRUSTSTORE_PASSWORD = "changeit" private val TRUSTSTORE_PASSWORD = "changeit"
companion object {
const val ROLE_USER : String = "ROLE_USER"
}
init {
LOG.warn("Creating new ValidationConfiguration.")
}
@Bean @Bean
fun cacheManager(): CacheManager { fun cacheManager(): CacheManager {
@ -47,7 +60,9 @@ class ValidationConfiguration {
val cacheManager: CacheManager = cacheManager() val cacheManager: CacheManager = cacheManager()
var cache = var cache =
cacheManager.getCache<String?, ZonedDateTime?>(CACHE_NAME) cacheManager.getCache<String?, ZonedDateTime?>(CACHE_NAME)
if (cache == null) { if (cache == null) {
LOG.warn("Creating new cache.")
cache = createNonceCache(cacheManager) cache = createNonceCache(cacheManager)
} }
return cache return cache
@ -65,7 +80,7 @@ class ValidationConfiguration {
val cacheConfig: CompleteConfiguration<String, ZonedDateTime> = MutableConfiguration<String, ZonedDateTime>() val cacheConfig: CompleteConfiguration<String, ZonedDateTime> = MutableConfiguration<String, ZonedDateTime>()
.setTypes(String::class.java, ZonedDateTime::class.java) .setTypes(String::class.java, ZonedDateTime::class.java)
.setExpiryPolicyFactory( .setExpiryPolicyFactory(
FactoryBuilder.factoryOf( factoryOf(
CreatedExpiryPolicy( CreatedExpiryPolicy(
Duration( Duration(
TimeUnit.MINUTES, TimeUnit.MINUTES,

View File

@ -23,6 +23,9 @@
package com.tarkvaratehnika.demobackend.security package com.tarkvaratehnika.demobackend.security
import com.tarkvaratehnika.demobackend.config.ValidationConfiguration import com.tarkvaratehnika.demobackend.config.ValidationConfiguration
import com.tarkvaratehnika.demobackend.config.ValidationConfiguration.Companion.ROLE_USER
import com.tarkvaratehnika.demobackend.web.rest.AuthenticationController
import org.slf4j.LoggerFactory
import org.springframework.security.authentication.AuthenticationServiceException import org.springframework.security.authentication.AuthenticationServiceException
import org.springframework.security.core.Authentication import org.springframework.security.core.Authentication
import org.springframework.security.core.AuthenticationException import org.springframework.security.core.AuthenticationException
@ -37,11 +40,11 @@ import java.security.cert.X509Certificate
@Component @Component
class AuthTokenDTOAuthenticationProvider { object AuthTokenDTOAuthenticationProvider {
private val LOG = LoggerFactory.getLogger(AuthTokenDTOAuthenticationProvider::class.java)
companion object {
const val ROLE_USER : String = "ROLE_USER"
}
private val USER_ROLE: GrantedAuthority = SimpleGrantedAuthority(ROLE_USER) private val USER_ROLE: GrantedAuthority = SimpleGrantedAuthority(ROLE_USER)
@ -52,7 +55,6 @@ class AuthTokenDTOAuthenticationProvider {
val authentication = auth as PreAuthenticatedAuthenticationToken val authentication = auth as PreAuthenticatedAuthenticationToken
val token = (authentication.credentials as AuthTokenDTO).token val token = (authentication.credentials as AuthTokenDTO).token
val challenge = (authentication.credentials as AuthTokenDTO).challenge val challenge = (authentication.credentials as AuthTokenDTO).challenge
val authorities = arrayListOf<GrantedAuthority>() val authorities = arrayListOf<GrantedAuthority>()
authorities.add(USER_ROLE) authorities.add(USER_ROLE)

View File

@ -1,6 +1,6 @@
package com.tarkvaratehnika.demobackend.web package com.tarkvaratehnika.demobackend.web
import com.tarkvaratehnika.demobackend.security.AuthTokenDTOAuthenticationProvider.Companion.ROLE_USER import com.tarkvaratehnika.demobackend.config.ValidationConfiguration.Companion.ROLE_USER
import org.springframework.security.access.prepost.PreAuthorize import org.springframework.security.access.prepost.PreAuthorize
import org.springframework.stereotype.Controller import org.springframework.stereotype.Controller
import org.springframework.ui.Model import org.springframework.ui.Model

View File

@ -26,7 +26,7 @@ class AuthenticationController {
val auth = PreAuthenticatedAuthenticationToken(null, authToken) val auth = PreAuthenticatedAuthenticationToken(null, authToken)
// Return authentication object if success. // Return authentication object if success.
return AuthTokenDTOAuthenticationProvider().authenticate(auth) return AuthTokenDTOAuthenticationProvider.authenticate(auth)
} }
@GetMapping("authentication", produces = [MediaType.APPLICATION_JSON_VALUE]) @GetMapping("authentication", produces = [MediaType.APPLICATION_JSON_VALUE])

View File

@ -24,6 +24,7 @@ package com.tarkvaratehnika.demobackend.web.rest
import com.tarkvaratehnika.demobackend.dto.ChallengeDto import com.tarkvaratehnika.demobackend.dto.ChallengeDto
import com.tarkvaratehnika.demobackend.security.WebEidAuthentication import com.tarkvaratehnika.demobackend.security.WebEidAuthentication
import org.slf4j.LoggerFactory
import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController import org.springframework.web.bind.annotation.RestController
@ -33,10 +34,12 @@ import org.webeid.security.nonce.NonceGenerator
@RequestMapping("auth") @RequestMapping("auth")
class ChallengeController (val nonceGenerator: NonceGenerator) { class ChallengeController (val nonceGenerator: NonceGenerator) {
private val LOG = LoggerFactory.getLogger(ChallengeController::class.java)
@GetMapping("challenge") @GetMapping("challenge")
fun challenge(): ChallengeDto { fun challenge(): ChallengeDto {
val challengeDto = ChallengeDto(nonceGenerator.generateAndStoreNonce()) val challengeDto = ChallengeDto(nonceGenerator.generateAndStoreNonce())
LOG.warn(challengeDto.nonce)
// WebEidAuthentication.addAuth(challengeDto.nonce) // For testing. // WebEidAuthentication.addAuth(challengeDto.nonce) // For testing.
return challengeDto return challengeDto
} }

View File

@ -1,3 +1,7 @@
html {
font-size: 4vw;
}
.cont { .cont {
display: grid; display: grid;
width: 80%; width: 80%;

View File

@ -11,7 +11,7 @@ function launchAuthApp(action) {
httpGetAsync(originUrl + challengeUrl, (body) => { httpGetAsync(originUrl + challengeUrl, (body) => {
let data = JSON.parse(body); let data = JSON.parse(body);
let challenge = data.nonce; let challenge = data.nonce;
let intent = createParametrizedIntentUrl(challenge, action); // TODO: Error handling. let intent = createParametrizedIntentUrl(challenge, action, originUrl); // TODO: Error handling.
console.log(intent); console.log(intent);
window.location.href = intent; window.location.href = intent;
pollForAuth(POLLING_INTERVAL, challenge); pollForAuth(POLLING_INTERVAL, challenge);
@ -20,8 +20,8 @@ function launchAuthApp(action) {
function pollForAuth(timeout, challenge) { function pollForAuth(timeout, challenge) {
console.log("Polling for auth"); console.log("Polling for auth");
let requestUrl = originUrl + authenticationRequestUrl + "?challenge=" + challenge; let encodedChallenge = encodeURIComponent(challenge);
let requestUrl = originUrl + authenticationRequestUrl + "?challenge=" + encodedChallenge;
let counter = 0; let counter = 0;
let timer = setInterval(() => { let timer = setInterval(() => {
// Fetch authentication object. // Fetch authentication object.
@ -48,7 +48,7 @@ function createParametrizedIntentUrl(challenge, action) {
else if (challenge == null) { else if (challenge == null) {
console.error("Challenge missing, can't authenticate without it.") console.error("Challenge missing, can't authenticate without it.")
} else { } else {
return intentUrl + "?" + "action=" + action + "&challenge=" + challenge + "&authUrl=" + originUrl + authenticationRequestUrl; return intentUrl + "?" + "action=" + action + "&challenge=" + encodeURIComponent(challenge) + "&authUrl=" + authenticationRequestUrl + "&originUrl=" + originUrl;
} }
} }

View File

@ -21,7 +21,7 @@
<body> <body>
<nav class="navbar navbar-dark bg-dark"> <nav class="navbar navbar-dark bg-dark">
<div class="container-fluid"> <div class="container-fluid">
<a class="navbar-brand" href="#">Auth demo web application</a> <a class="navbar-brand" href="#">Auth demo webapp</a>
</div> </div>
</nav> </nav>
<div class="cont"> <div class="cont">
@ -29,7 +29,7 @@
website using your ID card by using the button below.</h4> website using your ID card by using the button below.</h4>
<h5>Make sure you've installed the authentication app from: <a <h5>Make sure you've installed the authentication app from: <a
href="https://github.com/TanelOrumaa/Estonian-ID-card-mobile-authenticator-POC">GitHub</a></h5> href="https://github.com/TanelOrumaa/Estonian-ID-card-mobile-authenticator-POC">GitHub</a></h5>
<button type="button" class="btn btn-secondary" id="loginButton" data-action="auth">Log in</button> <button type="button" class="btn btn-lg btn-secondary" id="loginButton" data-action="auth">Log in</button>
<div class="alert alert-danger d-none" role="alert" id="loginErrorAlert"> <div class="alert alert-danger d-none" role="alert" id="loginErrorAlert">
Login failed. Refresh the page to try again. Login failed. Refresh the page to try again.
</div> </div>