Skip to content

Commit

Permalink
fjernet støtte for tokens in cookies (#835)
Browse files Browse the repository at this point in the history
  • Loading branch information
jan-olaveide authored Jan 13, 2024
1 parent 3ebfbeb commit a53f0ce
Show file tree
Hide file tree
Showing 35 changed files with 67 additions and 378 deletions.
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,6 @@ Add the modules that you need as Maven dependencies.
- **`no.nav.security.jwt.issuer.[issuer shortname]`** - all properties relevant for a particular issuer must be listed under a short name for that issuer (not the actual issuer value from the token, but a chosen name to represent config for the actual issuer) you trust, e.g. **`citizen`** or **`employee`**
- **`no.nav.security.jwt.issuer.[issuer shortname].discoveryurl`** - The identity provider configuration/discovery endpoint (metadata)
- **`no.nav.security.jwt.issuer.[issuer shortname].accepted_audience`** - The value of the audience (aud) claim in the JWT token. For OIDC it is the client ID of the client responsible for acquiring the token, in OAuth 2.0 it should be the identifier for you api.
- **`no.nav.security.jwt.issuer.[issuer shortname].cookiename`** - The value of the cookie containing a ID token (not required, only necessary if your api receives calls directly from a browser)
- **`no.nav.security.jwt.issuer.[issuer shortname].validation.optional-claims`** - A comma separated list of optional claims that will be excluded from default claims.
- **`no.nav.security.jwt.issuer.[issuer shortname].jwks-cache.lifespan`** - Cache the retrieved JWK keys to speed up subsequent look-ups. A non-negative lifespan expressed in minutes. (Default 15 min)
- **`no.nav.security.jwt.issuer.[issuer shortname].jwks-cache.refreshtime`** - A non-negative refresh time expressed in minutes. (Default 5 min)
Expand Down Expand Up @@ -368,4 +367,4 @@ before eventually (a couple of minutes later) it is synced to [Maven Central](ht

If you have any questions, please open an issue on the Github issue tracker.

For NAV employees, you can ask questions at the Slack channel [#token-support](https://nav-it.slack.com/archives/C01381BAT62)
For NAV employees, you can ask questions at the Slack channel [#token-support](https://nav-it.slack.com/archives/C01381BAT62)
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ import java.util.UUID
import kotlin.DeprecationLevel.WARNING
import no.nav.security.token.support.client.core.ClientAuthenticationProperties

class ClientAssertion(private val tokenEndpointUrl : URI?, private val clientId : String, private val rsaKey : RSAKey, private val expiryInSeconds : Int) {
constructor(tokenEndpointUrl: URI?, auth : ClientAuthenticationProperties) : this(tokenEndpointUrl, auth.clientId, auth.clientRsaKey!!, EXPIRY_IN_SECONDS)
class ClientAssertion(private val tokenEndpointUrl : URI, private val clientId : String, private val rsaKey : RSAKey, private val expiryInSeconds : Int) {
constructor(tokenEndpointUrl: URI, auth : ClientAuthenticationProperties) : this(tokenEndpointUrl, auth.clientId, auth.clientRsaKey!!, EXPIRY_IN_SECONDS)

fun assertion() =
now().run {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ class OAuth2HttpHeaders (val headers : Map<String, List<String>>) {
}

companion object {

@JvmField
val NONE = OAuth2HttpHeaders(emptyMap())
@JvmStatic
fun of(headers : Map<String, List<String>>) = OAuth2HttpHeaders(headers)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,17 @@ package no.nav.security.token.support.client.core.http

import java.net.URI
import java.util.Collections.unmodifiableMap
import no.nav.security.token.support.client.core.http.OAuth2HttpHeaders.Companion.NONE

class OAuth2HttpRequest (val tokenEndpointUrl : URI?, val oAuth2HttpHeaders : OAuth2HttpHeaders?, val formParameters : Map<String, String>) {
class OAuth2HttpRequest(val tokenEndpointUrl : URI, val oAuth2HttpHeaders : OAuth2HttpHeaders = NONE, val formParameters : Map<String, String>) {


class OAuth2HttpRequestBuilder @JvmOverloads constructor(private var tokenEndpointUrl: URI?,
private var oAuth2HttpHeaders : OAuth2HttpHeaders? = null,
class OAuth2HttpRequestBuilder @JvmOverloads constructor(private var tokenEndpointUrl: URI,
private var oAuth2HttpHeaders : OAuth2HttpHeaders = NONE,
private var formParameters: MutableMap<String,String> = mutableMapOf()) {
fun tokenEndpointUrl(tokenEndpointUrl : URI?) = this.also { it.tokenEndpointUrl = tokenEndpointUrl }
fun tokenEndpointUrl(tokenEndpointUrl : URI) = this.also { it.tokenEndpointUrl = tokenEndpointUrl }

fun oAuth2HttpHeaders(oAuth2HttpHeaders : OAuth2HttpHeaders?) = this.also { it.oAuth2HttpHeaders = oAuth2HttpHeaders }
fun oAuth2HttpHeaders(oAuth2HttpHeaders : OAuth2HttpHeaders) = this.also { it.oAuth2HttpHeaders = oAuth2HttpHeaders }

fun formParameter(key : String, value : String) = this.also { formParameters[key] = value }

Expand All @@ -25,7 +26,7 @@ class OAuth2HttpRequest (val tokenEndpointUrl : URI?, val oAuth2HttpHeaders : OA

}
companion object {
fun builder( tokenEndpointUrl: URI?) = OAuth2HttpRequestBuilder(tokenEndpointUrl)
fun builder( tokenEndpointUrl: URI) = OAuth2HttpRequestBuilder(tokenEndpointUrl)

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ abstract class AbstractOAuth2TokenClient<T : AbstractOAuth2GrantRequest?> intern
fun getTokenResponse(grantRequest : T) =
grantRequest?.clientProperties?.let {
runCatching {
oAuth2HttpClient.post(OAuth2HttpRequest.builder(it.tokenEndpointUrl)
oAuth2HttpClient.post(OAuth2HttpRequest.builder(it.tokenEndpointUrl!!)
.oAuth2HttpHeaders(OAuth2HttpHeaders.of(tokenRequestHeaders(it)))
.formParameters(defaultFormParameters(grantRequest).apply {
putAll(formParameters(grantRequest))
Expand Down Expand Up @@ -77,7 +77,7 @@ abstract class AbstractOAuth2TokenClient<T : AbstractOAuth2GrantRequest?> intern
PRIVATE_KEY_JWT -> LinkedHashMap<String, String>().apply {
put(CLIENT_ID, authentication.clientId)
put(CLIENT_ASSERTION_TYPE, JWTAuthentication.CLIENT_ASSERTION_TYPE)
put(CLIENT_ASSERTION, ClientAssertion(tokenEndpointUrl, authentication).assertion())
put(CLIENT_ASSERTION, ClientAssertion(tokenEndpointUrl!!, authentication).assertion())
}
else -> mutableMapOf()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ internal class ClientAssertionTest {
fun testCreateAssertion() {
val clientAuth = builder("client1", PRIVATE_KEY_JWT).clientJwk("src/test/resources/jwk.json").build()
val p = builder(CLIENT_CREDENTIALS, clientAuth).tokenEndpointUrl(URI.create("http://token")).build()
val signedJWT = SignedJWT.parse(ClientAssertion(p.tokenEndpointUrl, p.authentication).assertion())
val signedJWT = SignedJWT.parse(ClientAssertion(p.tokenEndpointUrl!!, p.authentication).assertion())
assertThat(signedJWT.header.keyID).isEqualTo(p.authentication.clientRsaKey?.keyID)
assertThat(signedJWT.header.type).isEqualTo(JWT)
assertThat(signedJWT.header.algorithm).isEqualTo(RS256)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class SimpleOAuth2HttpClient : OAuth2HttpClient {
.processResponse()

private fun HttpRequest.Builder.configureRequest(request: OAuth2HttpRequest): HttpRequest.Builder {
request.oAuth2HttpHeaders?.headers?.forEach { (key, values) -> values.forEach { header(key, it) } }
request.oAuth2HttpHeaders.headers.forEach { (key, values) -> values.forEach { header(key, it) } }
uri(request.tokenEndpointUrl)
POST(BodyPublishers.ofString(request.formParameters.toUrlEncodedString()))
return this
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ no.nav.security.jwt {
issuer_name = someshortname
discoveryurl = "http://localhost:1111/default/.well-known/oauth-authorization-server"
accepted_audience = debugger
cookie_name = localhost-idtoken
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ no.nav.security.jwt:
someshortname:
discovery-url: http://metadata
accepted_audience: aud-localhost
cookie_name: localhost-idtoken

client:
registration:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@ import no.nav.security.token.support.client.core.oauth2.OAuth2AccessTokenRespons
open class DefaultOAuth2HttpClient(val restClient: RestClient) : OAuth2HttpClient {


override fun post(oAuth2HttpRequest: OAuth2HttpRequest) =
override fun post(request: OAuth2HttpRequest) =
restClient.post()
.uri(oAuth2HttpRequest.tokenEndpointUrl!!)
.headers { it.addAll(headers(oAuth2HttpRequest)) }
.uri(request.tokenEndpointUrl)
.headers { it.addAll(headers(request)) }
.body(LinkedMultiValueMap<String, String>().apply {
setAll(oAuth2HttpRequest.formParameters)
setAll(request.formParameters)
}).retrieve()
.onStatus({ it.isError }) { _, response ->
throw OAuth2ClientException("Received $response.statusCode from $oAuth2HttpRequest.tokenEndpointUrl")
throw OAuth2ClientException("Received $response.statusCode from $request.tokenEndpointUrl")
}
.body(OAuth2AccessTokenResponse::class.java)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class OAuth2ClientRequestInterceptor(private val properties: ClientConfiguration
private val matcher: ClientConfigurationPropertiesMatcher) : ClientHttpRequestInterceptor {
override fun intercept(req: HttpRequest, body: ByteArray, execution: ClientHttpRequestExecution): ClientHttpResponse {
matcher.findProperties(properties, req.uri)?.let {
service.getAccessToken(it)?.accessToken?.let { it1 -> req.headers.setBearerAuth(it1) }
service.getAccessToken(it)?.accessToken?.let { token -> req.headers.setBearerAuth(token) }
}
return execution.execute(req, body)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,7 @@ import no.nav.security.token.support.core.context.TokenValidationContextHolder
@ActiveProfiles("test-withresourceurl")
internal class ClientConfigurationPropertiesTestWithResourceUrl {

private val matcher = object: ClientConfigurationPropertiesMatcher {

}
private val matcher = object: ClientConfigurationPropertiesMatcher {}
@MockBean
private val tokenValidationContextHolder: TokenValidationContextHolder? = null

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,9 @@ internal class DefaultOAuth2HttpClientTest {
val body = recordedRequest.body.readUtf8()
assertThat(recordedRequest.headers["header1"]).isEqualTo("headervalue1")
assertThat(recordedRequest.headers["header2"]).isEqualTo("headervalue2")
assertThat(body).contains("param1=value1")
assertThat(body).contains("param2=value2")
assertThat(body)
.contains("param1=value1")
.contains("param2=value2")
}

companion object {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ open class IssuerConfiguration(val name : String, properties : IssuerProperties,

val metadata : AuthorizationServerMetadata
val acceptedAudience = properties.acceptedAudience
val cookieName = properties.cookieName
val headerName = properties.headerName
val tokenValidator : JwtTokenValidator

Expand All @@ -20,7 +19,7 @@ open class IssuerConfiguration(val name : String, properties : IssuerProperties,
tokenValidator = tokenValidator(properties, metadata, resourceRetriever)
}

override fun toString() = ("${javaClass.simpleName} [name=$name, metaData=$metadata, acceptedAudience=$acceptedAudience, cookieName=$cookieName, headerName=$headerName, tokenValidator=$tokenValidator, resourceRetriever=$resourceRetriever]")
override fun toString() = ("${javaClass.simpleName} [name=$name, metaData=$metadata, acceptedAudience=$acceptedAudience, headerName=$headerName, tokenValidator=$tokenValidator, resourceRetriever=$resourceRetriever]")

companion object {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,18 @@ import no.nav.security.token.support.core.configuration.IssuerProperties.Validat

class IssuerProperties @JvmOverloads constructor(val discoveryUrl : URL,
val acceptedAudience : List<String> = listOf(),
val cookieName : String? = null,
cookieName : String? = null,
val headerName : String = AUTHORIZATION_HEADER,
val validation : Validation = EMPTY,
val jwksCache : JwksCache = EMPTY_CACHE,
val proxyUrl: URL? = null,
val usePlaintextForHttps: Boolean = false) {

private val LOG : Logger = LoggerFactory.getLogger(IssuerProperties::class.java)

init {
cookieName?.let { LOG.warn("Cookie-support will be discontinued in future versions, please consider changing your configuration now") }
cookieName?.let { throw IllegalArgumentException("Cookie-support is discontinued, please remove $it from ypur configuration now") }
}

override fun toString() = "IssuerProperties(discoveryUrl=$discoveryUrl, acceptedAudience=$acceptedAudience, cookieName=$cookieName, headerName=$headerName, proxyUrl=$proxyUrl, usePlaintextForHttps=$usePlaintextForHttps, validation=$validation, jwksCache=$jwksCache)"
override fun toString() = "IssuerProperties(discoveryUrl=$discoveryUrl, acceptedAudience=$acceptedAudience, headerName=$headerName, proxyUrl=$proxyUrl, usePlaintextForHttps=$usePlaintextForHttps, validation=$validation, jwksCache=$jwksCache)"

class Validation(val optionalClaims : List<String> = emptyList()) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,4 @@ package no.nav.security.token.support.core.http
*/
interface HttpRequest {
fun getHeader(headerName: String): String?
fun getCookies(): Array<out NameValue>?

interface NameValue {
fun getName(): String
fun getValue(): String
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ object JwtTokenRetriever {

@JvmStatic
fun retrieveUnvalidatedTokens(config: MultiIssuerConfiguration, request: HttpRequest) =
getTokensFromHeader(config, request) + getTokensFromCookies(config, request)
getTokensFromHeader(config, request)

private fun getTokensFromHeader(config: MultiIssuerConfiguration, request: HttpRequest): List<JwtToken> = try {
LOG.debug("Checking authorization header for tokens using config {}", config)
Expand All @@ -32,22 +32,6 @@ object JwtTokenRetriever {
LOG.warn("Received exception when attempting to extract and parse token from Authorization header", e)
}
}
private fun getTokensFromCookies(config: MultiIssuerConfiguration, request: HttpRequest) = try {
request.getCookies()?.asList()
?.filter { containsCookieName(config, it.getName()) }
?.map { JwtToken(it.getValue()) }
?: emptyList<JwtToken>().also {
LOG.debug("No tokens found in cookies")
}
} catch (e: Exception) {
LOG.warn("Received exception when attempting to extract and parse token from cookie", e)
listOf()
}

private fun containsCookieName(configuration: MultiIssuerConfiguration, cookieName: String) =
configuration.issuers.values.any {
cookieName.equals(it.cookieName, ignoreCase = true)
}

private fun extractBearerTokens(headerValues: List<String>) =
headerValues
Expand Down
Loading

0 comments on commit a53f0ce

Please sign in to comment.