Skip to content

Commit 7753faf

Browse files
authored
Add caching to authorization (#884)
* Cache authorization. * fix formatting, removed default key generator and added a bean. * fix rebase errors.
1 parent c4bcb02 commit 7753faf

6 files changed

Lines changed: 310 additions & 32 deletions

File tree

auth/pom.xml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@
2828
<artifactId>feast-common</artifactId>
2929
<version>${project.version}</version>
3030
</dependency>
31+
<dependency>
32+
<groupId>org.springframework</groupId>
33+
<artifactId>spring-context-support</artifactId>
34+
</dependency>
3135
<dependency>
3236
<groupId>net.devh</groupId>
3337
<artifactId>grpc-server-spring-boot-starter</artifactId>
@@ -91,6 +95,17 @@
9195
<artifactId>jsr305</artifactId>
9296
<version>3.0.2</version>
9397
</dependency>
98+
<dependency>
99+
<groupId>org.springframework</groupId>
100+
<artifactId>spring-test</artifactId>
101+
<scope>test</scope>
102+
</dependency>
103+
<dependency>
104+
<groupId>org.mockito</groupId>
105+
<artifactId>mockito-core</artifactId>
106+
<version>${mockito.version}</version>
107+
<scope>test</scope>
108+
</dependency>
94109
</dependencies>
95110
<build>
96111
<plugins>
@@ -131,6 +146,10 @@
131146
<excludePackageNames>feast.auth.generated.client.api</excludePackageNames>
132147
</configuration>
133148
</plugin>
149+
<plugin>
150+
<groupId>org.jacoco</groupId>
151+
<artifactId>jacoco-maven-plugin</artifactId>
152+
</plugin>
134153
</plugins>
135154
</build>
136155
</project>

auth/src/main/java/feast/auth/authorization/HttpAuthorizationProvider.java

Lines changed: 10 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,16 @@
1616
*/
1717
package feast.auth.authorization;
1818

19+
import feast.auth.config.CacheConfiguration;
1920
import feast.auth.generated.client.api.DefaultApi;
2021
import feast.auth.generated.client.invoker.ApiClient;
2122
import feast.auth.generated.client.invoker.ApiException;
2223
import feast.auth.generated.client.model.CheckAccessRequest;
24+
import feast.auth.utils.AuthUtils;
2325
import java.util.Map;
24-
import org.hibernate.validator.internal.constraintvalidators.bv.EmailValidator;
2526
import org.slf4j.Logger;
2627
import org.slf4j.LoggerFactory;
28+
import org.springframework.cache.annotation.Cacheable;
2729
import org.springframework.security.core.Authentication;
2830
import org.springframework.security.oauth2.jwt.Jwt;
2931

@@ -41,7 +43,7 @@ public class HttpAuthorizationProvider implements AuthorizationProvider {
4143
* The default subject claim is the key within the Authentication object where the user's identity
4244
* can be found
4345
*/
44-
private final String DEFAULT_SUBJECT_CLAIM = "email";
46+
private final String subjectClaim;
4547

4648
/**
4749
* Initializes the HTTPAuthorizationProvider
@@ -58,26 +60,29 @@ public HttpAuthorizationProvider(Map<String, String> options) {
5860
ApiClient apiClient = new ApiClient();
5961
apiClient.setBasePath(options.get("authorizationUrl"));
6062
this.defaultApiClient = new DefaultApi(apiClient);
63+
subjectClaim = options.get("subjectClaim");
6164
}
6265

6366
/**
64-
* Validates whether a user has access to a project
67+
* Validates whether a user has access to a project. @Cacheable is using {@link
68+
* CacheConfiguration} settings to cache output of the method {@link AuthorizationResult} for a
69+
* specified duration set in cache settings.
6570
*
6671
* @param projectId Name of the Feast project
6772
* @param authentication Spring Security Authentication object
6873
* @return AuthorizationResult result of authorization query
6974
*/
75+
@Cacheable(value = CacheConfiguration.AUTHORIZATION_CACHE, keyGenerator = "authKeyGenerator")
7076
public AuthorizationResult checkAccessToProject(String projectId, Authentication authentication) {
7177

7278
CheckAccessRequest checkAccessRequest = new CheckAccessRequest();
7379
Object context = getContext(authentication);
74-
String subject = getSubjectFromAuth(authentication, DEFAULT_SUBJECT_CLAIM);
80+
String subject = AuthUtils.getSubjectFromAuth(authentication, subjectClaim);
7581
String resource = "projects:" + projectId;
7682
checkAccessRequest.setAction("ALL");
7783
checkAccessRequest.setContext(context);
7884
checkAccessRequest.setResource(resource);
7985
checkAccessRequest.setSubject(subject);
80-
8186
try {
8287
Jwt credentials = ((Jwt) authentication.getCredentials());
8388
// Make authorization request to external service
@@ -114,31 +119,4 @@ private Object getContext(Authentication authentication) {
114119
// Not implemented yet, left empty
115120
return new Object();
116121
}
117-
118-
/**
119-
* Get user email from their authentication object.
120-
*
121-
* @param authentication Spring Security Authentication object, used to extract user details
122-
* @param subjectClaim Indicates the claim where the subject can be found
123-
* @return String user email
124-
*/
125-
private String getSubjectFromAuth(Authentication authentication, String subjectClaim) {
126-
Jwt principle = ((Jwt) authentication.getPrincipal());
127-
Map<String, Object> claims = principle.getClaims();
128-
String subjectValue = (String) claims.get(subjectClaim);
129-
130-
if (subjectValue.isEmpty()) {
131-
throw new IllegalStateException(
132-
String.format("JWT does not have a valid claim %s.", subjectClaim));
133-
}
134-
135-
if (subjectClaim.equals("email")) {
136-
boolean validEmail = (new EmailValidator()).isValid(subjectValue, null);
137-
if (!validEmail) {
138-
throw new IllegalStateException("JWT contains an invalid email address");
139-
}
140-
}
141-
142-
return subjectValue;
143-
}
144122
}
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright 2018-2020 The Feast Authors
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* https://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package feast.auth.config;
18+
19+
import com.google.common.cache.CacheBuilder;
20+
import feast.auth.utils.AuthUtils;
21+
import java.lang.reflect.Method;
22+
import java.util.concurrent.TimeUnit;
23+
import lombok.Getter;
24+
import lombok.Setter;
25+
import org.springframework.beans.factory.annotation.Autowired;
26+
import org.springframework.cache.Cache;
27+
import org.springframework.cache.CacheManager;
28+
import org.springframework.cache.annotation.CachingConfigurer;
29+
import org.springframework.cache.annotation.EnableCaching;
30+
import org.springframework.cache.concurrent.ConcurrentMapCache;
31+
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
32+
import org.springframework.cache.interceptor.CacheErrorHandler;
33+
import org.springframework.cache.interceptor.CacheResolver;
34+
import org.springframework.cache.interceptor.KeyGenerator;
35+
import org.springframework.context.annotation.Bean;
36+
import org.springframework.context.annotation.Configuration;
37+
import org.springframework.security.core.Authentication;
38+
39+
/** CacheConfiguration class defines Cache settings for HttpAuthorizationProvider class. */
40+
@Configuration
41+
@EnableCaching
42+
@Setter
43+
@Getter
44+
public class CacheConfiguration implements CachingConfigurer {
45+
46+
private static final int CACHE_SIZE = 10000;
47+
48+
public static int TTL = 60;
49+
50+
public static final String AUTHORIZATION_CACHE = "authorization";
51+
52+
@Autowired SecurityProperties secutiryProps;
53+
54+
@Bean
55+
public CacheManager cacheManager() {
56+
ConcurrentMapCacheManager cacheManager =
57+
new ConcurrentMapCacheManager(AUTHORIZATION_CACHE) {
58+
59+
@Override
60+
protected Cache createConcurrentMapCache(final String name) {
61+
return new ConcurrentMapCache(
62+
name,
63+
CacheBuilder.newBuilder()
64+
.expireAfterWrite(TTL, TimeUnit.SECONDS)
65+
.maximumSize(CACHE_SIZE)
66+
.build()
67+
.asMap(),
68+
false);
69+
}
70+
};
71+
72+
return cacheManager;
73+
}
74+
75+
/*
76+
* KeyGenerator used by {@link Cacheable} for caching authorization requests.
77+
* Key format : checkAccessToProject-<projectId>-<subjectClaim>
78+
*/
79+
@Bean
80+
public KeyGenerator authKeyGenerator() {
81+
return (Object target, Method method, Object... params) -> {
82+
String projectId = (String) params[0];
83+
Authentication authentication = (Authentication) params[1];
84+
String subject =
85+
AuthUtils.getSubjectFromAuth(
86+
authentication, secutiryProps.getAuthorization().getOptions().get("subjectClaim"));
87+
return String.format("%s-%s-%s", method.getName(), projectId, subject);
88+
};
89+
}
90+
91+
@Override
92+
public CacheResolver cacheResolver() {
93+
// TODO Auto-generated method stub
94+
return null;
95+
}
96+
97+
@Override
98+
public KeyGenerator keyGenerator() {
99+
return null;
100+
}
101+
102+
@Override
103+
public CacheErrorHandler errorHandler() {
104+
// TODO Auto-generated method stub
105+
return null;
106+
}
107+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright 2018-2020 The Feast Authors
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* https://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package feast.auth.utils;
18+
19+
import java.util.Map;
20+
import org.hibernate.validator.internal.constraintvalidators.bv.EmailValidator;
21+
import org.springframework.security.core.Authentication;
22+
import org.springframework.security.oauth2.jwt.Jwt;
23+
24+
public class AuthUtils {
25+
26+
// Suppresses default constructor, ensuring non-instantiability.
27+
private AuthUtils() {}
28+
29+
/**
30+
* Get user email from their authentication object.
31+
*
32+
* @param authentication Spring Security Authentication object, used to extract user details
33+
* @param subjectClaim Indicates the claim where the subject can be found
34+
* @return String user email
35+
*/
36+
public static String getSubjectFromAuth(Authentication authentication, String subjectClaim) {
37+
Jwt principle = ((Jwt) authentication.getPrincipal());
38+
Map<String, Object> claims = principle.getClaims();
39+
String subjectValue = (String) claims.getOrDefault(subjectClaim, "");
40+
41+
if (subjectValue.isEmpty()) {
42+
throw new IllegalStateException(
43+
String.format("JWT does not have a valid claim %s.", subjectClaim));
44+
}
45+
46+
if (subjectClaim.equals("email")) {
47+
boolean validEmail = (new EmailValidator()).isValid(subjectValue, null);
48+
if (!validEmail) {
49+
throw new IllegalStateException("JWT contains an invalid email address");
50+
}
51+
}
52+
return subjectValue;
53+
}
54+
}

0 commit comments

Comments
 (0)