Skip to content
This repository was archived by the owner on Dec 12, 2018. It is now read-only.

Commit 1c0c16a

Browse files
author
Mario
committed
1247 - Modified TokenRevocationListener to use the backend + Added ITs
1 parent 597bda1 commit 1c0c16a

File tree

4 files changed

+71
-61
lines changed

4 files changed

+71
-61
lines changed

extensions/servlet/src/main/java/com/stormpath/sdk/servlet/event/TokenRevocationRequestEventListener.java

Lines changed: 23 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,28 @@
1515
*/
1616
package com.stormpath.sdk.servlet.event;
1717

18+
import com.stormpath.sdk.application.Application;
1819
import com.stormpath.sdk.client.Client;
1920
import com.stormpath.sdk.impl.ds.InternalDataStore;
21+
import com.stormpath.sdk.impl.error.DefaultError;
22+
import com.stormpath.sdk.lang.Strings;
2023
import com.stormpath.sdk.oauth.AccessToken;
24+
import com.stormpath.sdk.oauth.OAuthRequests;
25+
import com.stormpath.sdk.oauth.OAuthRevocationRequest;
26+
import com.stormpath.sdk.oauth.OAuthRevocationRequestBuilder;
27+
import com.stormpath.sdk.oauth.OAuthTokenRevocators;
2128
import com.stormpath.sdk.oauth.RefreshToken;
29+
import com.stormpath.sdk.oauth.TokenTypeHint;
2230
import com.stormpath.sdk.resource.ResourceException;
2331
import com.stormpath.sdk.servlet.account.event.RegisteredAccountRequestEvent;
2432
import com.stormpath.sdk.servlet.account.event.VerifiedAccountRequestEvent;
33+
import com.stormpath.sdk.servlet.application.ApplicationResolver;
2534
import com.stormpath.sdk.servlet.authc.FailedAuthenticationRequestEvent;
2635
import com.stormpath.sdk.servlet.authc.LogoutRequestEvent;
2736
import com.stormpath.sdk.servlet.authc.SuccessfulAuthenticationRequestEvent;
2837
import com.stormpath.sdk.servlet.client.ClientResolver;
38+
import com.stormpath.sdk.servlet.filter.oauth.OAuthErrorCode;
39+
import com.stormpath.sdk.servlet.filter.oauth.OAuthException;
2940
import com.stormpath.sdk.servlet.http.CookieResolver;
3041
import com.stormpath.sdk.servlet.oauth.impl.JwtTokenSigningKeyResolver;
3142
import io.jsonwebtoken.Claims;
@@ -37,6 +48,7 @@
3748
import org.slf4j.Logger;
3849
import org.slf4j.LoggerFactory;
3950

51+
import javax.servlet.http.HttpServletRequest;
4052
import java.security.Key;
4153
import java.util.LinkedHashMap;
4254
import java.util.Map;
@@ -51,9 +63,7 @@ public class TokenRevocationRequestEventListener implements RequestEventListener
5163
private final TokenExtractor tokenExtractor = new BearerHeaderTokenExtractor();
5264
private final CookieResolver accessTokenCookieResolver = new CookieResolver("access_token");
5365

54-
private final JwtTokenSigningKeyResolver jwtTokenSigningKeyResolver = new JwtTokenSigningKeyResolver();
55-
56-
private Client client = null;
66+
protected ApplicationResolver applicationResolver = ApplicationResolver.INSTANCE;
5767

5868
@Override
5969
public void on(SuccessfulAuthenticationRequestEvent event) {
@@ -78,30 +88,17 @@ public void on(VerifiedAccountRequestEvent event) {
7888
@Override
7989
public void on(LogoutRequestEvent event) {
8090
String jwt = getJwtFromLogoutRequestEvent(event);
81-
if (jwt != null) {
82-
if (this.client == null) {
83-
this.client = ClientResolver.INSTANCE.getClient(event.getRequest()); //will throw if not found
84-
}
85-
86-
Key signingKey = jwtTokenSigningKeyResolver.getSigningKey(event.getRequest(), event.getResponse(), null, SignatureAlgorithm.HS256);
87-
JwsHeader header = Jwts.parser().setSigningKey(signingKey.getEncoded()).parseClaimsJws(jwt).getHeader();
88-
Claims claims = Jwts.parser().setSigningKey(signingKey.getEncoded()).parseClaimsJws(jwt).getBody();
89-
90-
//Let's be sure this jwt is actually an access token otherwise we will have an error when trying to retrieve
91-
//a resource (in order to delete it) that actually is not what we expect
92-
if (isAccessToken(header)) {
93-
gracefullyDeleteRefreshToken((String) claims.get("rti"));
94-
gracefullyDeleteAccessToken(claims.getId());
91+
HttpServletRequest request = event.getRequest();
92+
Application application = applicationResolver.getApplication(request);
93+
if (application != null && jwt != null) {
94+
try {
95+
OAuthRevocationRequest revocationRequest = OAuthRequests.OAUTH_TOKEN_REVOCATION_REQUEST.builder().setToken(jwt).build();
96+
OAuthTokenRevocators.OAUTH_TOKEN_REVOCATOR.forApplication(application).revoke(revocationRequest);
97+
} catch (ResourceException e) {
98+
com.stormpath.sdk.error.Error error = e.getStormpathError();
99+
String message = error.getMessage();
100+
log.warn("There was an error trying to revoke a token", message);
95101
}
96-
//There should never be a refresh token here. Therefore we will not even try to identify if the received JWT is
97-
//a refresh token. That would be a bug in the filter chain as a refresh token should never be used to anything other than
98-
//obtaining a new access token
99-
100-
//Fix for https://github.com/stormpath/stormpath-sdk-java/issues/611
101-
log.debug(
102-
"The current access and refresh tokens for '{}' have been revoked.",
103-
(event.getAccount() != null) ? event.getAccount().getEmail() : "unknown user"
104-
);
105102
}
106103
}
107104

@@ -117,34 +114,4 @@ protected String getJwtFromLogoutRequestEvent(LogoutRequestEvent event) {
117114
return jwt;
118115
}
119116

120-
private boolean isAccessToken(JwsHeader header) {
121-
return header.get("stt").equals("access");
122-
}
123-
124-
private void gracefullyDeleteAccessToken(String accessTokenId) {
125-
try {
126-
String href = "/accessTokens/" + accessTokenId;
127-
Map<String, Object> map = new LinkedHashMap<String, Object>();
128-
map.put("href", href);
129-
AccessToken accessToken = ((InternalDataStore)client.getDataStore()).instantiate(AccessToken.class, map, true);
130-
accessToken.delete();
131-
} catch (ResourceException e) {
132-
//Let's prevent an error to allow the flow to continue
133-
log.warn("There was an error trying to delete access token with ID {}", accessTokenId, e);
134-
}
135-
}
136-
137-
private void gracefullyDeleteRefreshToken(String refreshTokenId) {
138-
try{
139-
String href = "/refreshTokens/" + refreshTokenId;
140-
Map<String, Object> map = new LinkedHashMap<String, Object>();
141-
map.put("href", href);
142-
RefreshToken refreshToken = ((InternalDataStore)client.getDataStore()).instantiate(RefreshToken.class, map, true);
143-
refreshToken.delete();
144-
} catch (ResourceException e) {
145-
//Let's prevent an error to allow the flow to continue, this component is basically a listener that tries to delete
146-
//the current access and refresh tokens on logout, we will only post this error in the log
147-
log.warn("There was an error trying to delete refresh token with ID {}", refreshTokenId, e);
148-
}
149-
}
150117
}

extensions/spring/boot/stormpath-spring-security-webmvc-spring-boot-starter/src/test/groovy/com/stormpath/spring/boot/autoconfigure/StormpathWebSecurityAutoConfigurationIT.groovy

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -232,8 +232,9 @@ class StormpathWebSecurityAutoConfigurationIT extends AbstractTestNGSpringContex
232232
def accessToken = result.getAccessToken()
233233

234234
expect(httpServletRequest.getHeader("Authorization")).andReturn("Bearer " + accessToken.getJwt())
235-
expect(httpServletRequest.getServletContext()).andReturn(servletContext).times(2)
236-
expect(servletContext.getAttribute("com.stormpath.sdk.client.Client")).andReturn(client).times(2)
235+
//expect(httpServletRequest.getServletContext()).andReturn(servletContext).times(2)
236+
expect(httpServletRequest.getAttribute(Application.class.getName())).andReturn(application).times(1)
237+
//expect(servletContext.getAttribute("com.stormpath.sdk.client.Client")).andReturn(client).times(2)
237238

238239
replay(httpServletRequest, httpServletResponse, servletContext)
239240

@@ -243,7 +244,7 @@ class StormpathWebSecurityAutoConfigurationIT extends AbstractTestNGSpringContex
243244
def logoutRequestEvent = new DefaultLogoutRequestEvent(httpServletRequest, httpServletResponse, account)
244245
requestEventPublisher.publish(logoutRequestEvent)
245246
Assert.isTrue(account.getAccessTokens().getSize() == 0)
246-
Assert.isTrue(account.getRefreshTokens().getSize() == 0)
247+
Assert.isTrue(account.getRefreshTokens().getSize() == 1)
247248

248249
verify(httpServletRequest, httpServletResponse, servletContext)
249250
}

extensions/spring/stormpath-spring-security-webmvc/src/test/groovy/com/stormpath/spring/config/CorsFilterIT.groovy

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,7 @@ package com.stormpath.spring.config
1818
import com.stormpath.sdk.account.Account
1919
import com.stormpath.sdk.account.Accounts
2020
import com.stormpath.sdk.application.Application
21-
import com.stormpath.sdk.application.Applications
2221
import com.stormpath.sdk.client.Client
23-
import com.stormpath.sdk.directory.Directories
2422
import com.stormpath.sdk.directory.Directory
2523
import com.stormpath.sdk.lang.Strings
2624
import org.springframework.beans.factory.annotation.Autowired

extensions/spring/stormpath-spring-security-webmvc/src/test/groovy/com/stormpath/spring/config/MinimalStormpathSpringSecurityWebMvcConfigurationIT.groovy

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,11 @@ import com.stormpath.sdk.api.ApiKey
2020
import com.stormpath.sdk.cache.CacheManager
2121
import com.stormpath.sdk.lang.Assert
2222
import com.stormpath.sdk.oauth.Authenticators
23+
import com.stormpath.sdk.oauth.OAuthBearerRequestAuthentication
2324
import com.stormpath.sdk.oauth.OAuthPasswordGrantRequestAuthentication
25+
import com.stormpath.sdk.oauth.OAuthRequestAuthenticationResult
2426
import com.stormpath.sdk.oauth.OAuthRequests
27+
import com.stormpath.sdk.resource.ResourceException
2528
import com.stormpath.sdk.servlet.authc.impl.DefaultLogoutRequestEvent
2629
import com.stormpath.sdk.servlet.csrf.CsrfTokenManager
2730
import com.stormpath.sdk.servlet.csrf.DefaultCsrfTokenManager
@@ -66,6 +69,7 @@ import static org.easymock.EasyMock.*
6669
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf
6770
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post
6871
import static org.testng.Assert.*
72+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
6973

7074
/**
7175
* @since 1.0.RC5
@@ -274,6 +278,46 @@ class MinimalStormpathSpringSecurityWebMvcConfigurationIT extends AbstractClient
274278
SecurityContextHolder.clearContext()
275279
}
276280

281+
/**
282+
* @since 1.5.0
283+
*/
284+
@Test
285+
void testOauthRevokeEndpoint() {
286+
287+
String password = "Changeme1!"
288+
Account account = createTempAccount(password)
289+
290+
//Let's create the tokes
291+
OAuthPasswordGrantRequestAuthentication createRequest = OAuthRequests.OAUTH_PASSWORD_GRANT_REQUEST.builder().setLogin(account.getEmail()).setPassword(password).build();
292+
OAuthRequestAuthenticationResult result = Authenticators.OAUTH_PASSWORD_GRANT_REQUEST_AUTHENTICATOR.forApplication(application).authenticate(createRequest)
293+
294+
String jwt = result.getAccessToken().getJwt()
295+
296+
//Authenticate with access token
297+
OAuthBearerRequestAuthentication authRequest = OAuthRequests.OAUTH_BEARER_REQUEST.builder().setJwt(jwt).build()
298+
def bearerResult = Authenticators.OAUTH_BEARER_REQUEST_AUTHENTICATOR.forApplication(application).authenticate(authRequest)
299+
300+
assertTrue(bearerResult.getAccount().getEmail().equals(account.getEmail()))
301+
302+
//Let's revoke the tokens
303+
mvc.perform(post(new URI("/oauth/revoke"))
304+
.contentType(org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED_VALUE)
305+
.content("token="+jwt)
306+
).andExpect(status().is(HttpServletResponse.SC_OK)) //200
307+
308+
// Authentication with the token should fail now since we have just revoked it
309+
authRequest = OAuthRequests.OAUTH_BEARER_REQUEST.builder().setJwt(jwt).build()
310+
try {
311+
Authenticators.OAUTH_BEARER_REQUEST_AUTHENTICATOR.forApplication(application).authenticate(authRequest)
312+
fail("should have thrown")
313+
} catch (ResourceException e) {
314+
//this exceptions is expected
315+
assertEquals(e.getStatus(), 404)
316+
assertEquals(e.getCode(), 10013)
317+
}
318+
}
319+
320+
277321
/**
278322
* @return true if the user has one of the specified roles.
279323
*/

0 commit comments

Comments
 (0)