Skip to content

Commit 639a86e

Browse files
committed
Optimize InMemoryOAuth2AuthorizationService
Closes spring-projectsgh-654
1 parent afb1688 commit 639a86e

File tree

3 files changed

+99
-7
lines changed

3 files changed

+99
-7
lines changed

oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/InMemoryOAuth2AuthorizationService.java

Lines changed: 61 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2020-2021 the original author or authors.
2+
* Copyright 2020-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,6 +17,7 @@
1717

1818
import java.util.Arrays;
1919
import java.util.Collections;
20+
import java.util.LinkedHashMap;
2021
import java.util.List;
2122
import java.util.Map;
2223
import java.util.concurrent.ConcurrentHashMap;
@@ -41,8 +42,29 @@
4142
* @see OAuth2AuthorizationService
4243
*/
4344
public final class InMemoryOAuth2AuthorizationService implements OAuth2AuthorizationService {
45+
private int maxInitializedAuthorizations = 100;
46+
47+
/*
48+
* Stores "initialized" (uncompleted) authorizations, where an access token has not yet been granted.
49+
* This state occurs with the authorization_code grant flow during the user consent step OR
50+
* when the code is returned in the authorization response but the access token request is not yet initiated.
51+
*/
52+
private Map<String, OAuth2Authorization> initializedAuthorizations =
53+
Collections.synchronizedMap(new MaxSizeHashMap<>(this.maxInitializedAuthorizations));
54+
55+
/*
56+
* Stores "completed" authorizations, where an access token has been granted.
57+
*/
4458
private final Map<String, OAuth2Authorization> authorizations = new ConcurrentHashMap<>();
4559

60+
/*
61+
* Constructor used for testing only.
62+
*/
63+
InMemoryOAuth2AuthorizationService(int maxInitializedAuthorizations) {
64+
this.maxInitializedAuthorizations = maxInitializedAuthorizations;
65+
this.initializedAuthorizations = Collections.synchronizedMap(new MaxSizeHashMap<>(this.maxInitializedAuthorizations));
66+
}
67+
4668
/**
4769
* Constructs an {@code InMemoryOAuth2AuthorizationService}.
4870
*/
@@ -77,20 +99,31 @@ public InMemoryOAuth2AuthorizationService(List<OAuth2Authorization> authorizatio
7799
@Override
78100
public void save(OAuth2Authorization authorization) {
79101
Assert.notNull(authorization, "authorization cannot be null");
80-
this.authorizations.put(authorization.getId(), authorization);
102+
if (isComplete(authorization)) {
103+
this.authorizations.put(authorization.getId(), authorization);
104+
} else {
105+
this.initializedAuthorizations.put(authorization.getId(), authorization);
106+
}
81107
}
82108

83109
@Override
84110
public void remove(OAuth2Authorization authorization) {
85111
Assert.notNull(authorization, "authorization cannot be null");
86-
this.authorizations.remove(authorization.getId(), authorization);
112+
if (isComplete(authorization)) {
113+
this.authorizations.remove(authorization.getId(), authorization);
114+
} else {
115+
this.initializedAuthorizations.remove(authorization.getId(), authorization);
116+
}
87117
}
88118

89119
@Nullable
90120
@Override
91121
public OAuth2Authorization findById(String id) {
92122
Assert.hasText(id, "id cannot be empty");
93-
return this.authorizations.get(id);
123+
OAuth2Authorization authorization = this.authorizations.get(id);
124+
return authorization != null ?
125+
authorization :
126+
this.initializedAuthorizations.get(id);
94127
}
95128

96129
@Nullable
@@ -102,9 +135,18 @@ public OAuth2Authorization findByToken(String token, @Nullable OAuth2TokenType t
102135
return authorization;
103136
}
104137
}
138+
for (OAuth2Authorization authorization : this.initializedAuthorizations.values()) {
139+
if (hasToken(authorization, token, tokenType)) {
140+
return authorization;
141+
}
142+
}
105143
return null;
106144
}
107145

146+
private static boolean isComplete(OAuth2Authorization authorization) {
147+
return authorization.getAccessToken() != null;
148+
}
149+
108150
private static boolean hasToken(OAuth2Authorization authorization, String token, @Nullable OAuth2TokenType tokenType) {
109151
if (tokenType == null) {
110152
return matchesState(authorization, token) ||
@@ -144,4 +186,19 @@ private static boolean matchesRefreshToken(OAuth2Authorization authorization, St
144186
authorization.getToken(OAuth2RefreshToken.class);
145187
return refreshToken != null && refreshToken.getToken().getTokenValue().equals(token);
146188
}
189+
190+
private static final class MaxSizeHashMap<K, V> extends LinkedHashMap<K, V> {
191+
private final int maxSize;
192+
193+
private MaxSizeHashMap(int maxSize) {
194+
this.maxSize = maxSize;
195+
}
196+
197+
@Override
198+
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
199+
return size() > this.maxSize;
200+
}
201+
202+
}
203+
147204
}

oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeRequestAuthenticationProvider.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -266,8 +266,6 @@ private Authentication authenticateAuthorizationRequest(Authentication authentic
266266
.build();
267267
this.authorizationService.save(authorization);
268268

269-
// TODO Need to remove 'in-flight' authorization if consent step is not completed (e.g. approved or cancelled)
270-
271269
Set<String> currentAuthorizedScopes = currentAuthorizationConsent != null ?
272270
currentAuthorizationConsent.getScopes() : null;
273271

oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/InMemoryOAuth2AuthorizationServiceTests.java

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2020-2021 the original author or authors.
2+
* Copyright 2020-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -132,6 +132,43 @@ public void saveWhenAuthorizationExistsThenUpdated() {
132132
assertThat(authorization).isNotEqualTo(originalAuthorization);
133133
}
134134

135+
@Test
136+
public void saveWhenInitializedAuthorizationsReachMaxThenOldestRemoved() {
137+
int maxInitializedAuthorizations = 5;
138+
InMemoryOAuth2AuthorizationService authorizationService =
139+
new InMemoryOAuth2AuthorizationService(maxInitializedAuthorizations);
140+
141+
OAuth2Authorization initialAuthorization = OAuth2Authorization.withRegisteredClient(REGISTERED_CLIENT)
142+
.id(ID + "-initial")
143+
.principalName(PRINCIPAL_NAME)
144+
.authorizationGrantType(AUTHORIZATION_GRANT_TYPE)
145+
.attribute(OAuth2ParameterNames.STATE, "state-initial")
146+
.build();
147+
authorizationService.save(initialAuthorization);
148+
149+
OAuth2Authorization authorization = authorizationService.findById(initialAuthorization.getId());
150+
assertThat(authorization).isEqualTo(initialAuthorization);
151+
authorization = authorizationService.findByToken(
152+
initialAuthorization.getAttribute(OAuth2ParameterNames.STATE), STATE_TOKEN_TYPE);
153+
assertThat(authorization).isEqualTo(initialAuthorization);
154+
155+
for (int i = 0; i < maxInitializedAuthorizations; i++) {
156+
authorization = OAuth2Authorization.withRegisteredClient(REGISTERED_CLIENT)
157+
.id(ID + "-" + i)
158+
.principalName(PRINCIPAL_NAME)
159+
.authorizationGrantType(AUTHORIZATION_GRANT_TYPE)
160+
.attribute(OAuth2ParameterNames.STATE, "state-" + i)
161+
.build();
162+
authorizationService.save(authorization);
163+
}
164+
165+
authorization = authorizationService.findById(initialAuthorization.getId());
166+
assertThat(authorization).isNull();
167+
authorization = authorizationService.findByToken(
168+
initialAuthorization.getAttribute(OAuth2ParameterNames.STATE), STATE_TOKEN_TYPE);
169+
assertThat(authorization).isNull();
170+
}
171+
135172
@Test
136173
public void removeWhenAuthorizationNullThenThrowIllegalArgumentException() {
137174
assertThatThrownBy(() -> this.authorizationService.remove(null))

0 commit comments

Comments
 (0)