Skip to content

Commit 3026c37

Browse files
committed
Fix #30569: fix refreshing of access tokens
1 parent b488041 commit 3026c37

1 file changed

Lines changed: 51 additions & 22 deletions

File tree

src/main/java/eu/openanalytics/containerproxy/auth/impl/oidc/OpenIdReAuthorizeFilter.java

Lines changed: 51 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@
3030
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
3131
import org.springframework.security.oauth2.client.RefreshTokenOAuth2AuthorizedClientProvider;
3232
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
33-
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
3433
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
3534
import org.springframework.security.web.util.matcher.OrRequestMatcher;
3635
import org.springframework.security.web.util.matcher.RequestMatcher;
@@ -42,6 +41,7 @@
4241
import javax.servlet.ServletException;
4342
import javax.servlet.http.HttpServletRequest;
4443
import javax.servlet.http.HttpServletResponse;
44+
import javax.servlet.http.HttpSession;
4545
import java.io.IOException;
4646
import java.time.Clock;
4747
import java.time.Duration;
@@ -50,19 +50,28 @@
5050

5151
/**
5252
* Ensures that the access token of the user is refreshed when needed.
53-
* If the refresh token is invalid (e.g. because the session in the IdP expired), we throw a
54-
* {@link ClientAuthorizationRequiredException} exception such that the user is can re-login.
55-
*
53+
* If the refresh token is invalid (e.g. because the session in the IdP expired), we first invalidate the session and then throw a
54+
* {@link ClientAuthorizationRequiredException} exception such that the user can re-login.
5655
* This filter only applies to a limited set of requests and not to requests that are proxied to apps.
57-
* Otherwise, this filter would be called too much and cause too much overhead.
56+
* Otherwise, this filter would be called too much and cause too much overhead. In addition, this filter should
57+
* only be used on non-ajax requests. This is required for the redirect to the IDP to properly work.
58+
*
59+
* A special case is the /refresh-openid endpoint, which does not throw the exception (i.e. it does not cause a redirect to the IDP)
60+
* but only invalidates the session. This endpoint is frequently called (at least every <40 seconds) when the user is using an app.
61+
* It refreshes the OIDC session as long as possible and when it fails (e.g. because the session was revoked or reached it max life),
62+
* the session is invalidated. The app page detects this and shows a message that the user was logged out.
63+
*
64+
* See #30569, #28976
5865
*/
5966
public class OpenIdReAuthorizeFilter extends OncePerRequestFilter {
6067

68+
private static final RequestMatcher REFRESH_OPENID_MATCHER = new AntPathRequestMatcher("/refresh-openid");
69+
6170
private static final RequestMatcher REQUEST_MATCHER = new OrRequestMatcher(
6271
new AntPathRequestMatcher("/app/**"),
6372
new AntPathRequestMatcher("/app_i/**"),
6473
new AntPathRequestMatcher("/"),
65-
new AntPathRequestMatcher("/heartbeat"));
74+
REFRESH_OPENID_MATCHER);
6675

6776
@Inject
6877
private OAuth2AuthorizedClientManager oAuth2AuthorizedClientManager;
@@ -72,8 +81,8 @@ public class OpenIdReAuthorizeFilter extends OncePerRequestFilter {
7281

7382
private final Clock clock = Clock.systemUTC();
7483

75-
// use clock skew of 20 seconds instead of 60 seconds. Otherwise, if the access token is valid for 1 minute, it would get refreshed at each request.
76-
private final Duration clockSkew = Duration.ofSeconds(20);
84+
// use clock skew of 40 seconds instead of 60 seconds. Otherwise, if the access token is valid for 1 minute, it would get refreshed at each request.
85+
private final Duration clockSkew = Duration.ofSeconds(40);
7786

7887
@Override
7988
protected void doFilterInternal(@Nonnull HttpServletRequest request, @Nonnull HttpServletResponse response, @Nonnull FilterChain chain) throws ServletException, IOException {
@@ -83,25 +92,32 @@ protected void doFilterInternal(@Nonnull HttpServletRequest request, @Nonnull Ht
8392
if (auth instanceof OAuth2AuthenticationToken) {
8493
OAuth2AuthorizedClient authorizedClient = oAuth2AuthorizedClientService.loadAuthorizedClient(REG_ID, auth.getName());
8594

86-
if (accessTokenExpired(authorizedClient)) {
87-
OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest
88-
.withAuthorizedClient(authorizedClient)
89-
.principal(auth)
90-
.build();
91-
92-
// re-authorize
93-
try {
94-
oAuth2AuthorizedClientManager.authorize(authorizeRequest);
95-
} catch (ClientAuthorizationException ex) {
96-
if (ex.getError().getErrorCode().equals(OAuth2ErrorCodes.INVALID_GRANT)) {
97-
// if refresh token has expired or is invalid -> re-start authorization process
98-
throw new ClientAuthorizationRequiredException(ex.getClientRegistrationId());
95+
if (authorizedClient == null) {
96+
invalidateSession(request, response);
97+
return;
98+
} else {
99+
if (accessTokenExpired(authorizedClient)) {
100+
OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest
101+
.withAuthorizedClient(authorizedClient)
102+
.principal(auth)
103+
.build();
104+
105+
try {
106+
oAuth2AuthorizedClientManager.authorize(authorizeRequest);
107+
logger.info("Refresh");
108+
} catch (ClientAuthorizationException ex) {
109+
invalidateSession(request, response);
110+
return;
99111
}
100-
throw ex;
101112
}
102113
}
103114
}
104115
}
116+
if (REFRESH_OPENID_MATCHER.matches(request)) {
117+
response.getWriter().write("{\"status\":\"success\"}");
118+
response.setStatus(200);
119+
return;
120+
}
105121
chain.doFilter(request, response);
106122
}
107123

@@ -115,4 +131,17 @@ private boolean accessTokenExpired(OAuth2AuthorizedClient authorizedClient) {
115131
return clock.instant().isAfter(authorizedClient.getAccessToken().getExpiresAt().minus(this.clockSkew));
116132
}
117133

134+
private void invalidateSession(@Nonnull HttpServletRequest request, @Nonnull HttpServletResponse response) throws IOException {
135+
HttpSession session = request.getSession(false);
136+
if (session != null) {
137+
session.invalidate();
138+
}
139+
if (REFRESH_OPENID_MATCHER.matches(request)) {
140+
response.getWriter().write("{\"status\":\"success\"}");
141+
response.setStatus(200);
142+
} else {
143+
throw new ClientAuthorizationRequiredException(REG_ID);
144+
}
145+
}
146+
118147
}

0 commit comments

Comments
 (0)