View on GitHub

Spring Cloud Gateway

Getting the most from the open-source API Gateway

End to end OAuth 2 JOSE/JWT Interaction

These logging highlights illustrate what’s happening during the OAuth2 flow when JOSE/JWT.

First, a request is sent to http://localhost:8080/resource and this is intercepted by the API gateway.

The gateway expects all requestes to be authenticated, so it immediately asks the authentication provider (the UAA) to authenticate the user via the authentication-uri of http://localhost:8090/uaa/oauth/authorize. The UAA then steps in and presents the user with an authentication challenge:

uaa | DEBUG --- UaaMetricsFilter: Successfully matched URI: /uaa/oauth/authorize to a group: /ui
uaa | DEBUG --- ChainedAuthenticationManager: Attempting chained authentication of org.springframework.security.authentication.UsernamePasswordAuthenticationToken@3ce6bcf6: Principal: user1; Credentials: [PROTECTED]; Authenticated: false; Details: remoteAddress=172.23.0.1, sessionId=<SESSION>; Not granted any authorities with manager:org.cloudfoundry.identity.uaa.authentication.manager.CheckIdpEnabledAuthenticationManager@3c5169cf required:null
uaa | DEBUG --- AuthzAuthenticationManager: Processing authentication request for user1
uaa | DEBUG --- AuthzAuthenticationManager: Password successfully matched for userId[user1]:61b7c1c4-a0c0-44f7-a709-a8638068d137
uaa | INFO --- Audit: IdentityProviderAuthenticationSuccess ('user1'): principal=61b7c1c4-a0c0-44f7-a709-a8638068d137, origin=[remoteAddress=172.23.0.1, sessionId=<SESSION>], identityZoneId=[uaa], authenticationType=[uaa]
uaa | INFO --- Audit: UserAuthenticationSuccess ('user1'): principal=61b7c1c4-a0c0-44f7-a709-a8638068d137, origin=[remoteAddress=172.23.0.1, sessionId=<SESSION>], identityZoneId=[uaa]

Above, the UAA has confirmed the users identity has been as user1 and their principal id has been assigned as 61b7c1c4-a0c0-44f7-a709-a8638068d137.

The UAA will now ask the user to ‘Authorise’ the login-client application (the gateway) and give it access to the users profile:

uaa | DEBUG --- UaaMetricsFilter: Successfully matched URI: /uaa/oauth/authorize to a group: /ui
uaa | DEBUG --- SessionResetFilter: Evaluating user-id for session reset:61b7c1c4-a0c0-44f7-a709-a8638068d137
uaa | DEBUG --- UserManagedAuthzApprovalHandler: Looking up user approved authorizations for client_id=login-client and username=user1
uaa | DEBUG --- JdbcApprovalStore: adding approval: [[61b7c1c4-a0c0-44f7-a709-a8638068d137, resource.read, login-client, Fri Aug 23 11:11:25 GMT 2019, APPROVED, Tue Jul 23 11:11:25 GMT 2019]]
uaa | INFO --- Audit: TokenIssuedEvent ('["resource.read","openid","email"]'): principal=61b7c1c4-a0c0-44f7-a709-a8638068d137, origin=[caller=login-client, details=(remoteAddress=172.23.0.4, clientId=login-client)], identityZoneId=[uaa]
gateway | DEBUG --- HttpLogging: [26d3ff07] Decoded [{access_token=eyJhbGciOiJSUzI1NiIsImprdSI6Imh0dHBzOi8vbG9jYWxob3N0OjgwODAvdWFhL3Rva2VuX2tleXMiLCJraW (truncated)...]

Above, the gateway application login-client has now been granted access to the users profile, which includes the scope resource.read. The gateway has also been issued with a JWT access_token (part redacted in the log for security):

The gateway checks the validity and authenticity of this token using the UAA’s published public keys on the jwk-set-uri (which is http://uaa:8090/uaa/token_keys):

uaa     | DEBUG --- SecurityFilterChainPostProcessor$HttpsEnforcementFilter: Filter chain 'tokenKeySecurity' processing request GET /uaa/token_keys
gateway | DEBUG --- HttpLogging: [434f3904] Decoded "{"keys":[{"kty":"RSA","e":"AQAB","use":"sig","kid":"key-id-1","alg":"RS256","value":"-----BEGIN PUB (truncated)...
gateway | DEBUG --- HttpLogging: [3c76d40a] Decoded [{user_id=61b7c1c4-a0c0-44f7-a709-a8638068d137, user_name=user1, name=first1 last1, given_name=first1 (truncated)...]

The JWT token is deemed to be authentic by the gateway, so the gateway starts forwarding the request to the resource server’s /resource endpoint:

gateway | DEBUG --- RoutePredicateHandlerMapping: Route matched: resource
gateway | DEBUG --- RoutePredicateHandlerMapping: Mapping [Exchange: GET http://localhost:8080/resource] to Route{id='resource', uri=http://resource:9000, order=0, predicate=org.springframework.cloud.gateway.support.ServerWebExchangeUtils$$Lambda$334/1074263646@2fb64b12, gatewayFilters=[OrderedGatewayFilter{delegate=org.springframework.cloud.security.oauth2.gateway.TokenRelayGatewayFilterFactory$$Lambda$336/1107412069@42187950, order=0}, OrderedGatewayFilter{delegate=org.springframework.cloud.gateway.filter.factory.RemoveRequestHeaderGatewayFilterFactory$$Lambda$339/1139814130@210d549e, order=0}]}

The resource server then contacts the UAA. It also wants to authenticate the users JWT access_token:

uaa | DEBUG --- UaaMetricsFilter: Successfully matched URI: /uaa/token_keys to a group: /oauth-oidc
uaa | DEBUG --- SecurityFilterChainPostProcessor$HttpsEnforcementFilter: Filter chain 'tokenKeySecurity' processing request GET /uaa/token_keys
resource | DEBUG --- HttpLogging: [472e353f] Decoded "{"keys":[{"kty":"RSA","e":"AQAB","use":"sig","kid":"key-id-1","alg":"RS256","value":"-----BEGIN PUB (truncated)...

Above, the resource server checks the validity of the JWT token against the keys held by the UAA.

The keys check out, so the resource server decodes the JWT access_token and allows the user to access the /resource endpoint:

resource | TRACE --- SecuredServiceApplication: ***** JWT Headers: {jku=https://localhost:8080/uaa/token_keys, kid=key-id-1, typ=JWT, alg=RS256}
resource | TRACE --- SecuredServiceApplication: ***** JWT Claims: {sub=61b7c1c4-a0c0-44f7-a709-a8638068d137, user_name=user1, origin=uaa, iss=http://uaa:8090/uaa/oauth/token, client_id=login-client, aud=[resource, openid, login-client], zid=uaa, grant_type=authorization_code, user_id=61b7c1c4-a0c0-44f7-a709-a8638068d137, azp=login-client, scope=["resource.read","openid","email"], auth_time=1563880281, exp=Tue Jul 23 23:11:25 GMT 2019, iat=Tue Jul 23 11:11:25 GMT 2019, jti=fa9c60e2e89b48a584169f839f32e282, email=user1@provider.com, rev_sig=b3f4e1e1, cid=login-client}
resource | TRACE --- SecuredServiceApplication: ***** JWT Token: eyJhbGciOiJSUzI1NiIsImprdSI6Imh0dHBzOi8vbG9jYWxob3N0OjgwODAvdWFhL3Rva2VuX2tleXMiLCJraWQiOiJrZXktaWQtMSIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJmYTljNjBlMmU4OWI0OGE1ODQxNjlmODM5ZjMyZTI4MiIsInN1YiI6IjYxYjdjMWM0LWEwYzAtNDRmNy1hNzA5LWE4NjM4MDY4ZDEzNyIsInNjb3BlIjpbInJlc291cmNlLnJlYWQiLCJvcGVuaWQiLCJlbWFpbCJdLCJjbGllbnRfaWQiOiJsb2dpbi1jbGllbnQiLCJjaWQiOiJsb2dpbi1jbGllbnQiLCJhenAiOiJsb2dpbi1jbGllbnQiLCJncmFudF90eXBlIjoiYXV0aG9yaXphdGlvbl9jb2RlIiwidXNlcl9pZCI6IjYxYjdjMWM0LWEwYzAtNDRmNy1hNzA5LWE4NjM4MDY4ZDEzNyIsIm9yaWdpbiI6InVhYSIsInVzZXJfbmFtZSI6InVzZXIxIiwiZW1haWwiOiJ1c2VyMUBwcm92aWRlci5jb20iLCJhdXRoX3RpbWUiOjE1NjM4ODAyODEsInJldl9zaWciOiJiM2Y0ZTFlMSIsImlhdCI6MTU2Mzg4MDI4NSwiZXhwIjoxNTYzOTIzNDg1LCJpc3MiOiJodHRwOi8vdWFhOjgwOTAvdWFhL29hdXRoL3Rva2VuIiwiemlkIjoidWFhIiwiYXVkIjpbInJlc291cmNlIiwib3BlbmlkIiwibG9naW4tY2xpZW50Il19.l9SC-3dvUWbqH-teAUpSDfn0V9EeRmaLioj5N6oYZSpKUBIFh7QR9Dd4e2wbG6itpI3ulA30629Tw8aIHo_72Owetc7v4dBmg-IL_c1Nycc5JYXguMMZmKT4oIW2lAfNxWl9Z821HyNk4SsRiIPpcWXiziAO8n4h3anjr7NQESYRFole0gDT19IOX1TIB03ZSbgjmRq3-UclpX-qRYW_XJ-CeVbcjRI_C1XEDuqLVflushpsQvBt6snb1Oq1c6zmvXkXag9QXA0iBusJnIt66zua2-dnGv334VPTo6SaIoGBSp4ReRDCFLKNk2tAF-Kqpc_S8KehmgY0jMYN7QLYNw

Information encoded into the JWT is then used to show who read the resource - “Resource accessed by: user1 (with subjectId: 61b7c1c4-a0c0-44f7-a709-a8638068d137)”:

 resource | DEBUG --- HttpLogging: [5becb7ae] Writing "Resource accessed by: user1 (with subjectId: 61b7c1c4-a0c0-44f7-a709-a8638068d137)"

You can decode the token in the trace and see the content at https://jwt.io/