mirror of
https://github.com/OpenBankProject/OBP-API.git
synced 2026-02-06 15:27:01 +00:00
docfix and extra debug around oauth2.jwk_set.ul
This commit is contained in:
parent
1b5a36e1e8
commit
8143f6449a
53
README.md
53
README.md
@ -666,6 +666,59 @@ allow_oauth2_login=true
|
||||
oauth2.jwk_set.url=https://www.googleapis.com/oauth2/v3/certs
|
||||
```
|
||||
|
||||
### OAuth2 JWKS URI Configuration
|
||||
|
||||
The `oauth2.jwk_set.url` property is critical for OAuth2 JWT token validation. OBP-API uses this to verify the authenticity of JWT tokens by fetching the JSON Web Key Set (JWKS) from the specified URI(s).
|
||||
|
||||
#### Configuration Methods
|
||||
|
||||
The `oauth2.jwk_set.url` property is resolved in the following order of priority:
|
||||
|
||||
1. **Environment Variable**
|
||||
|
||||
```bash
|
||||
export OBP_OAUTH2_JWK_SET_URL="https://your-oidc-server.com/jwks"
|
||||
```
|
||||
|
||||
2. **Properties Files** (located in `obp-api/src/main/resources/props/`)
|
||||
- `production.default.props` (for production deployments)
|
||||
- `default.props` (for development)
|
||||
- `test.default.props` (for testing)
|
||||
|
||||
#### Supported Formats
|
||||
|
||||
- **Single URL**: `oauth2.jwk_set.url=http://localhost:9000/obp-oidc/jwks`
|
||||
- **Multiple URLs**: `oauth2.jwk_set.url=http://localhost:8080/jwk.json,https://www.googleapis.com/oauth2/v3/certs`
|
||||
|
||||
#### Common OAuth2 Provider Examples
|
||||
|
||||
- **Google**: `https://www.googleapis.com/oauth2/v3/certs`
|
||||
- **OBP-OIDC**: `http://localhost:9000/obp-oidc/jwks`
|
||||
- **Keycloak**: `http://localhost:7070/realms/master/protocol/openid-connect/certs`
|
||||
- **Azure AD**: `https://login.microsoftonline.com/common/discovery/v2.0/keys`
|
||||
|
||||
#### Troubleshooting OBP-20208 Error
|
||||
|
||||
If you encounter the error "OBP-20208: Cannot match the issuer and JWKS URI at this server instance", check the following:
|
||||
|
||||
1. **Verify JWT Issuer Claim**: The JWT token's `iss` (issuer) claim must match one of the configured identity providers
|
||||
2. **Check JWKS URL Configuration**: Ensure `oauth2.jwk_set.url` contains URLs that correspond to your JWT issuer
|
||||
3. **Case-Insensitive Matching**: OBP-API performs case-insensitive substring matching between the issuer and JWKS URLs
|
||||
4. **URL Format Consistency**: Check for trailing slashes or URL formatting differences
|
||||
|
||||
**Debug Logging**: Enable debug logging to see detailed information about the matching process:
|
||||
|
||||
```properties
|
||||
# Add to your logging configuration
|
||||
logger.code.api.OAuth2=DEBUG
|
||||
```
|
||||
|
||||
The debug logs will show:
|
||||
|
||||
- Expected identity provider vs actual JWT issuer claim
|
||||
- Available JWKS URIs from configuration
|
||||
- Matching logic results
|
||||
|
||||
---
|
||||
|
||||
## Frozen APIs
|
||||
|
||||
@ -231,14 +231,65 @@ object OAuth2Login extends RestHelper with MdcLoggable {
|
||||
def checkUrlOfJwkSets(identityProvider: String) = {
|
||||
val url: List[String] = Constant.oauth2JwkSetUrl.toList
|
||||
val jwksUris: List[String] = url.map(_.toLowerCase()).map(_.split(",").toList).flatten
|
||||
|
||||
|
||||
logger.debug(s"checkUrlOfJwkSets - identityProvider: '$identityProvider'")
|
||||
logger.debug(s"checkUrlOfJwkSets - oauth2.jwk_set.url raw value: '${Constant.oauth2JwkSetUrl}'")
|
||||
logger.debug(s"checkUrlOfJwkSets - parsed jwksUris: $jwksUris")
|
||||
|
||||
// Enhanced matching for both URL-based and semantic identifiers
|
||||
val identityProviderLower = identityProvider.toLowerCase()
|
||||
val jwksUri = jwksUris.filter(_.contains(identityProviderLower))
|
||||
|
||||
|
||||
logger.debug(s"checkUrlOfJwkSets - identityProviderLower: '$identityProviderLower'")
|
||||
logger.debug(s"checkUrlOfJwkSets - filtered jwksUri: $jwksUri")
|
||||
|
||||
jwksUri match {
|
||||
case x :: _ => Full(x)
|
||||
case Nil => Failure(Oauth2CannotMatchIssuerAndJwksUriException)
|
||||
case x :: _ =>
|
||||
logger.debug(s"checkUrlOfJwkSets - SUCCESS: Found matching JWKS URI: '$x'")
|
||||
Full(x)
|
||||
case Nil =>
|
||||
logger.debug(s"checkUrlOfJwkSets - FAILURE: Cannot match issuer '$identityProvider' with any JWKS URI")
|
||||
logger.debug(s"checkUrlOfJwkSets - Expected issuer pattern: '$identityProvider' (case-insensitive contains match)")
|
||||
logger.debug(s"checkUrlOfJwkSets - Available JWKS URIs: $jwksUris")
|
||||
logger.debug(s"checkUrlOfJwkSets - Identity provider (lowercase): '$identityProviderLower'")
|
||||
logger.debug(s"checkUrlOfJwkSets - Matching logic: Looking for JWKS URIs containing '$identityProviderLower'")
|
||||
Failure(Oauth2CannotMatchIssuerAndJwksUriException)
|
||||
}
|
||||
}
|
||||
|
||||
def checkUrlOfJwkSetsWithToken(identityProvider: String, jwtToken: String) = {
|
||||
val actualIssuer = JwtUtil.getIssuer(jwtToken).getOrElse("NO_ISSUER_CLAIM")
|
||||
val url: List[String] = Constant.oauth2JwkSetUrl.toList
|
||||
val jwksUris: List[String] = url.map(_.toLowerCase()).map(_.split(",").toList).flatten
|
||||
|
||||
logger.debug(s"checkUrlOfJwkSetsWithToken - Expected identity provider: '$identityProvider'")
|
||||
logger.debug(s"checkUrlOfJwkSetsWithToken - Actual JWT issuer claim: '$actualIssuer'")
|
||||
logger.debug(s"checkUrlOfJwkSetsWithToken - oauth2.jwk_set.url raw value: '${Constant.oauth2JwkSetUrl}'")
|
||||
logger.debug(s"checkUrlOfJwkSetsWithToken - parsed jwksUris: $jwksUris")
|
||||
|
||||
// Enhanced matching for both URL-based and semantic identifiers
|
||||
val identityProviderLower = identityProvider.toLowerCase()
|
||||
val jwksUri = jwksUris.filter(_.contains(identityProviderLower))
|
||||
|
||||
logger.debug(s"checkUrlOfJwkSetsWithToken - identityProviderLower: '$identityProviderLower'")
|
||||
logger.debug(s"checkUrlOfJwkSetsWithToken - filtered jwksUri: $jwksUri")
|
||||
|
||||
jwksUri match {
|
||||
case x :: _ =>
|
||||
logger.debug(s"checkUrlOfJwkSetsWithToken - SUCCESS: Found matching JWKS URI: '$x'")
|
||||
Full(x)
|
||||
case Nil =>
|
||||
logger.debug(s"checkUrlOfJwkSetsWithToken - FAILURE: Cannot match issuer with any JWKS URI")
|
||||
logger.debug(s"checkUrlOfJwkSetsWithToken - Expected identity provider: '$identityProvider'")
|
||||
logger.debug(s"checkUrlOfJwkSetsWithToken - Actual JWT issuer claim: '$actualIssuer'")
|
||||
logger.debug(s"checkUrlOfJwkSetsWithToken - Available JWKS URIs: $jwksUris")
|
||||
logger.debug(s"checkUrlOfJwkSetsWithToken - Expected pattern (lowercase): '$identityProviderLower'")
|
||||
logger.debug(s"checkUrlOfJwkSetsWithToken - Matching logic: Looking for JWKS URIs containing '$identityProviderLower'")
|
||||
logger.debug(s"checkUrlOfJwkSetsWithToken - TROUBLESHOOTING:")
|
||||
logger.debug(s"checkUrlOfJwkSetsWithToken - 1. Verify oauth2.jwk_set.url contains URL matching '$identityProvider'")
|
||||
logger.debug(s"checkUrlOfJwkSetsWithToken - 2. Check if JWT issuer '$actualIssuer' should match identity provider '$identityProvider'")
|
||||
logger.debug(s"checkUrlOfJwkSetsWithToken - 3. Ensure case-insensitive substring matching works: does any JWKS URI contain '$identityProviderLower'?")
|
||||
Failure(Oauth2CannotMatchIssuerAndJwksUriException)
|
||||
}
|
||||
}
|
||||
|
||||
@ -259,14 +310,33 @@ object OAuth2Login extends RestHelper with MdcLoggable {
|
||||
}.getOrElse(false)
|
||||
}
|
||||
def validateIdToken(idToken: String): Box[IDTokenClaimsSet] = {
|
||||
logger.debug(s"validateIdToken - attempting to validate ID token")
|
||||
|
||||
// Extract issuer for better error reporting
|
||||
val actualIssuer = JwtUtil.getIssuer(idToken).getOrElse("NO_ISSUER_CLAIM")
|
||||
logger.debug(s"validateIdToken - JWT issuer claim: '$actualIssuer'")
|
||||
|
||||
urlOfJwkSets match {
|
||||
case Full(url) =>
|
||||
logger.debug(s"validateIdToken - using JWKS URL: '$url'")
|
||||
JwtUtil.validateIdToken(idToken, url)
|
||||
case ParamFailure(a, b, c, apiFailure : APIFailure) =>
|
||||
logger.debug(s"validateIdToken - ParamFailure: $a, $b, $c, $apiFailure")
|
||||
logger.debug(s"validateIdToken - JWT issuer was: '$actualIssuer'")
|
||||
ParamFailure(a, b, c, apiFailure : APIFailure)
|
||||
case Failure(msg, t, c) =>
|
||||
logger.debug(s"validateIdToken - Failure getting JWKS URL: $msg")
|
||||
logger.debug(s"validateIdToken - JWT issuer was: '$actualIssuer'")
|
||||
if (msg.contains("OBP-20208")) {
|
||||
logger.debug("validateIdToken - OBP-20208 Error Details:")
|
||||
logger.debug(s"validateIdToken - JWT issuer claim: '$actualIssuer'")
|
||||
logger.debug(s"validateIdToken - oauth2.jwk_set.url value: '${Constant.oauth2JwkSetUrl}'")
|
||||
logger.debug("validateIdToken - Check that the JWKS URL configuration matches the JWT issuer")
|
||||
}
|
||||
Failure(msg, t, c)
|
||||
case _ =>
|
||||
logger.debug("validateIdToken - No JWKS URL available")
|
||||
logger.debug(s"validateIdToken - JWT issuer was: '$actualIssuer'")
|
||||
Failure(Oauth2ThereIsNoUrlOfJwkSet)
|
||||
}
|
||||
}
|
||||
@ -414,8 +484,15 @@ object OAuth2Login extends RestHelper with MdcLoggable {
|
||||
}
|
||||
|
||||
def applyIdTokenRules(token: String, cc: CallContext): (Box[User], Some[CallContext]) = {
|
||||
logger.debug("applyIdTokenRules - starting ID token validation")
|
||||
|
||||
// Extract issuer from token for debugging
|
||||
val actualIssuer = JwtUtil.getIssuer(token).getOrElse("NO_ISSUER_CLAIM")
|
||||
logger.debug(s"applyIdTokenRules - JWT issuer claim: '$actualIssuer'")
|
||||
|
||||
validateIdToken(token) match {
|
||||
case Full(_) =>
|
||||
logger.debug("applyIdTokenRules - ID token validation successful")
|
||||
val user = getOrCreateResourceUser(token)
|
||||
val consumer = getOrCreateConsumer(token, user.map(_.userId), Some(OpenIdConnect.openIdConnect))
|
||||
LoginAttempt.userIsLocked(user.map(_.provider).getOrElse(""), user.map(_.name).getOrElse("")) match {
|
||||
@ -423,10 +500,26 @@ object OAuth2Login extends RestHelper with MdcLoggable {
|
||||
case false => (user, Some(cc.copy(consumer = consumer)))
|
||||
}
|
||||
case ParamFailure(a, b, c, apiFailure : APIFailure) =>
|
||||
logger.debug(s"applyIdTokenRules - ParamFailure during token validation: $a")
|
||||
logger.debug(s"applyIdTokenRules - JWT issuer was: '$actualIssuer'")
|
||||
(ParamFailure(a, b, c, apiFailure : APIFailure), Some(cc))
|
||||
case Failure(msg, t, c) =>
|
||||
logger.debug(s"applyIdTokenRules - Failure during token validation: $msg")
|
||||
logger.debug(s"applyIdTokenRules - JWT issuer was: '$actualIssuer'")
|
||||
if (msg.contains("OBP-20208")) {
|
||||
logger.debug("applyIdTokenRules - OBP-20208: JWKS URI matching failed. Diagnostic info:")
|
||||
logger.debug(s"applyIdTokenRules - Actual JWT issuer: '$actualIssuer'")
|
||||
logger.debug(s"applyIdTokenRules - oauth2.jwk_set.url config: '${Constant.oauth2JwkSetUrl}'")
|
||||
logger.debug("applyIdTokenRules - Resolution steps:")
|
||||
logger.debug("1. Verify oauth2.jwk_set.url contains URLs that match the JWT issuer")
|
||||
logger.debug("2. Check if JWT issuer claim matches expected identity provider")
|
||||
logger.debug("3. Ensure case-insensitive substring matching works between issuer and JWKS URLs")
|
||||
logger.debug("4. Consider if trailing slashes or URL formatting might be causing mismatch")
|
||||
}
|
||||
(Failure(msg, t, c), Some(cc))
|
||||
case _ =>
|
||||
logger.debug("applyIdTokenRules - Unknown failure during token validation")
|
||||
logger.debug(s"applyIdTokenRules - JWT issuer was: '$actualIssuer'")
|
||||
(Failure(Oauth2IJwtCannotBeVerified), Some(cc))
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user