Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CUSTCOM-168 OpenID Connect: Fixed simultaneous redirects and invalidation of session #4419

Merged
merged 9 commits into from
Feb 20, 2020

Conversation

parysto
Copy link
Contributor

@parysto parysto commented Jan 9, 2020

Description

This is a bug fix / feature.

  1. If a page contains multiple protected resources and authentication (via OpenID Connect) is activated, multiple redirects to the OpenID Connect provider are performed (one for each protected resource). This is especially the case when auto refresh of the token is activated, the token expires and a new access token must be requested. To prevent multiple redirects, I have synchronized the authentication/refresh logic on the user's session.
  2. If auto refresh is enabled for the access token, the principal was registered in the session again after refreshing the token. This caused the currently active session to be invalidated (destroys all session scoped beans and session attributes).

Testing

Testing Performed

Deployed developed app on instance with fix installed and compared/validated behavior while using the app (especially when refresh of token happened).

Testing Environment

OpenJDK 11 on Ubuntu 19.10

@smillidge smillidge requested a review from jGauravGupta January 9, 2020 13:41
@smillidge smillidge added the PR: CLA CLA submitted on PR by the contributor label Jan 9, 2020
@parysto parysto requested a review from dmatej January 10, 2020 17:12
@jGauravGupta

This comment has been minimized.

@jGauravGupta
Copy link
Contributor

jGauravGupta commented Jan 13, 2020

Hi @parysto ,

Thanks for the PR . A import is missing in OpenIdAuthenticationMechanism class, please fix the compilation error:

project openid-client-integration: Compilation failure
/var/lib/jenkins/workspace/PayaraQuickBuildAndTest/appserver/payara-appserver-modules/security-openid/src/main/java/fish/payara/security/openid/OpenIdAuthenticationMechanism.java:[371,9] cannot find symbol
  symbol:   class HttpSession
  location: class fish.payara.security.openid.OpenIdAuthenticationMechanism

And can you also share the sample application with multiple protected resources on a page?

@parysto
Copy link
Contributor Author

parysto commented Jan 13, 2020

Hi @jGauravGupta ,

have pushed the sample application to: https://github.com/parysto/OpenIDJsfDemo
It's a JSF application that includes some JS (JQuery to increase size/load time) files. All resources, except callback.xhtml, (index.xhtml and *.js) are protected.
Please set providerURI, clientID and clientSecret in Configuration.java to your OpenID connect provider.

To trigger the race condition, set the validity time of the access token to a small value (like 1 min). Open the sample app, log in and wait until the token expired. Press F5 repeatedly, so that multiple requests are sent to Payara at the same time. Due to the race condition, you will be logged out and redirected to callback.xhtml. If you enabled logging for OpenIdAuthenticationMechanism, you will see log messages like:

FEIN: UserPrincipal is set, check if Access Token is valid.
FEIN: Access Token is expired. Request new Access Token with Refresh Token.
FEIN: UserPrincipal is set, check if Access Token is valid.
FEIN: Access Token is expired. Request new Access Token with Refresh Token.
FEIN: UserPrincipal is set, check if Access Token is valid.
FEIN: Access Token is expired. Request new Access Token with Refresh Token.
FEIN: UserPrincipal is set, check if Access Token is valid.
FEIN: Access Token is expired. Request new Access Token with Refresh Token.
AM FEINSTEN: Sending the request to the userinfo endpoint
AM FEINSTEN: Sending the request to the userinfo endpoint
AM FEINSTEN: Sending the request to the userinfo endpoint
AM FEINSTEN: Sending the request to the userinfo endpoint
FEIN: UserPrincipal is not set, authenticate user using OpenId Connect protocol.
AM FEINSTEN: Redirecting for authentication to https://(...)

Will fix the missing imports ;-)

@parysto

This comment has been minimized.

1 similar comment
@jGauravGupta

This comment has been minimized.

Copy link
Contributor

@jGauravGupta jGauravGupta left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @parysto,

Instead of locking the complete OpenIdAuthenticationMechanism#validateRequest call per request, I recommend synchronizing in specific snippets e.g If access token expired in OpenIdAuthenticationMechanism#reAuthenticate.

@parysto
Copy link
Contributor Author

parysto commented Jan 14, 2020

Hi @jGauravGupta ,

I will check if synchronizing OpenIdAuthenticationMechanism#reAuthenticate is sufficient.

@parysto
Copy link
Contributor Author

parysto commented Jan 19, 2020

Hi @jGauravGupta,

hadn't any issues synchronizing on OpenIdAuthenticationMechanism#reAuthenticate only :-)
Have updated the PR

@parysto parysto requested a review from jGauravGupta January 19, 2020 13:46
@jGauravGupta
Copy link
Contributor

jGauravGupta commented Jan 19, 2020

Hi @parysto,

Thanks for updating the PR, Can you perform the locking operation only in case of token expired as mentioned above.
e.g:

if (this.context.getAccessToken().isExpired()) {
            synchronized (this.getSessionLock(request)) {
                if (this.context.getAccessToken().isExpired()) {
                   // refreshTokens
                }
            }
        }

@parysto
Copy link
Contributor Author

parysto commented Jan 19, 2020

That's a good point (to improve performance). Will update the PR.

@jGauravGupta

This comment has been minimized.

@jGauravGupta
Copy link
Contributor

jGauravGupta commented Jan 20, 2020

Hi @parysto,

Please fix the following syntax error:

/appserver/payara-appserver-modules/security-openid/src/main/java/fish/payara/security/openid/OpenIdAuthenticationMechanism.java:[327,5] missing return statement

@jGauravGupta
Copy link
Contributor

jenkins test please

@dmatej dmatej changed the title OpenID Connect: Fixed simultaneous redirects and invalidation of session CUSTCOM-168 OpenID Connect: Fixed simultaneous redirects and invalidation of session Jan 24, 2020
@parysto parysto requested a review from jGauravGupta January 24, 2020 18:32
@parysto
Copy link
Contributor Author

parysto commented Feb 8, 2020

Hi @jGauravGupta,

is there something more I can do to complete this merge request?

@@ -364,4 +368,20 @@ private void updateContext(JsonObject tokensObject) {
}
}

private Object getSessionLock(HttpServletRequest request) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is really the plan to put the lock to the session object and leave it there forever? What if the token expires for second time? The lock will be still in session attributes from previous iteration.

Also replication in cluster will have some latency, is it or isn't it a problem? (probably it isn't)

I don't say it is incorrect, I am only asking ;-)

Copy link
Contributor

@pzygielo pzygielo Feb 9, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting...
I know raw Object is not Serializable. I do not remember perfectly, but I do wonder what happens at HttpSession passivation... Or if session migrates to other VM...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @dmatej, hi @pzygielo

Is really the plan to put the lock to the session object and leave it there forever? What if the token expires for second time? The lock will be still in session attributes from previous iteration.

You mean the lock-object? Yes, when the token expires again, the same instance of the lock will be reused. So, we do not need to set the "lock-attribute" again as long as the user's session is not invalidated. When the user's session is invalidated, the lock will be removed. Or am I wrong?

Also replication in cluster will have some latency, is it or isn't it a problem? (probably it isn't)

Object itself does not have any fields that need to be transferred. So there shouldn't be a huge impact on latency. Although I haven't measured that ;-)

I know raw Object is not Serializable. I do not remember perfectly, but I do wonder what happens at HttpSession passivation... Or if session migrates to other VM...

Good point, haven't thought about that. Yes, Object is not Serializable, so passivation or replication will fail. Will change the lock to an "empty" implementation of Serializable.

I don't say it is incorrect, I am only asking ;-)

I appreciate your feedback :-)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pzygielo I think I investigated in years ago when I was fixing SSO session replication and I found that nonserializable objects are simply ignored if there is no special implementation for them.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dmatej thanks for checking

I think I investigated in years ago when I was fixing SSO session replication and I found that nonserializable objects are simply ignored if there is no special implementation for them.

However it's not so clear for me, given section 7.7.2 of Servlet 4.0 spec, and:

The distributed servlet container must throw an IllegalArgumentException for
objects where the container cannot support the mechanism necessary for migration
of the session storing them.

thus I'm not so sure about simply ignoring such session attributes. So it seems I have to investigate myself 😉

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is one implementation piece I found, that confirms your statement, about "how it is":

} else if (isSerializable(value)) {
saveNames.add(keys[i]);
saveValues.add(value);
//end HERCULES:mod
} else {
removeAttribute(keys[i], true, true);
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"the container cannot support the mechanism necessary" ... such mechanism might be even ignoring objects which are managed by some mechanism on the other side.
But ... yes, I think there are some "blank spaces" in impl ... the problem is that implementing it would crash nearly all older+larger applications I have seen :D

@dmatej
Copy link
Contributor

dmatej commented Feb 19, 2020

Jenkins test please

@dmatej dmatej self-assigned this Feb 19, 2020
@dmatej dmatej self-requested a review February 19, 2020 11:44
@jGauravGupta
Copy link
Contributor

Jenkins test please

1 similar comment
@dmatej
Copy link
Contributor

dmatej commented Feb 19, 2020

Jenkins test please

@dmatej dmatej merged commit f742bad into payara:master Feb 20, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
PR: CLA CLA submitted on PR by the contributor
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants