Skip to content

Commit

Permalink
For microservices + OIDC, cache the requests to the OIDC server
Browse files Browse the repository at this point in the history
Without this cache, each microservice call does a cache to the "user-info" endpoint on the OIDC server, which will flood the OIDC server
  • Loading branch information
jdubois committed Oct 26, 2017
1 parent be9ff4f commit fba5187
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 6 deletions.
3 changes: 2 additions & 1 deletion generators/server/files.js
Expand Up @@ -329,9 +329,10 @@ function writeFiles() {
this.template(`${SERVER_MAIN_SRC_DIR}package/client/_OAuth2InterceptedFeignConfiguration.java`, `${javaDir}client/OAuth2InterceptedFeignConfiguration.java`);
}
if (this.authenticationType === 'oauth2') {
this.template(`${SERVER_MAIN_SRC_DIR}package/security/oauth2/_AuthorizationHeaderUtil.java`, `${javaDir}/security/oauth2/AuthorizationHeaderUtil.java`);
this.template(`${SERVER_MAIN_SRC_DIR}package/security/oauth2/_CachedUserInfoTokenServices.java`, `${javaDir}/security/oauth2/CachedUserInfoTokenServices.java`);
this.template(`${SERVER_MAIN_SRC_DIR}package/security/oauth2/_SimplePrincipalExtractor.java`, `${javaDir}/security/oauth2/SimplePrincipalExtractor.java`);
this.template(`${SERVER_MAIN_SRC_DIR}package/security/oauth2/_SimpleAuthoritiesExtractor.java`, `${javaDir}/security/oauth2/SimpleAuthoritiesExtractor.java`);
this.template(`${SERVER_MAIN_SRC_DIR}package/security/oauth2/_AuthorizationHeaderUtil.java`, `${javaDir}/security/oauth2/AuthorizationHeaderUtil.java`);
}
if (this.authenticationType === 'oauth2' && (this.applicationType === 'microservice' || this.applicationType === 'gateway')) {
this.template(`${SERVER_MAIN_SRC_DIR}package/config/_FeignConfiguration.java`, `${javaDir}config/FeignConfiguration.java`);
Expand Down
Expand Up @@ -140,6 +140,9 @@ public CacheConfiguration(JHipsterProperties jHipsterProperties) {
@Bean
public JCacheManagerCustomizer cacheManagerCustomizer() {
return cm -> {
<%_ if (authenticationType === 'oauth2' && applicationType === 'microservice') { _%>
cm.createCache("oAuth2Authentication", jcacheConfiguration);
<%_ } _%>
<%_ if (!skipUserManagement || (authenticationType === 'oauth2' && applicationType === 'monolith')) { _%>
cm.createCache("users", jcacheConfiguration);
cm.createCache(<%=packageName%>.domain.User.class.getName(), jcacheConfiguration);
Expand Down Expand Up @@ -453,7 +456,7 @@ public InfinispanCacheConfigurer cacheConfigurer(JHipsterProperties jHipsterProp
.eviction().type(EvictionType.COUNT).size(cacheInfo.getReplicated()
.getMaxEntries()).expiration().lifespan(cacheInfo.getReplicated().getTimeToLiveSeconds(), TimeUnit.MINUTES).build());

// initilaize Hiberante L2 cache
// initilaize Hibernate L2 cache
manager.defineConfiguration("entity", new ConfigurationBuilder().clustering().cacheMode(CacheMode.INVALIDATION_SYNC)
.jmxStatistics().enabled(cacheInfo.isStatsEnabled())
.locking().concurrencyLevel(1000).lockAcquisitionTimeout(15000).build());
Expand Down Expand Up @@ -498,6 +501,11 @@ public InfinispanJCacheManager(URI uri, EmbeddedCacheManager cacheManager, Cachi
JHipsterProperties jHipsterProperties) {
super(uri, cacheManager, provider);
// register individual caches to make the stats info available.
<%_ if (authenticationType === 'oauth2' && applicationType === 'microservice') { _%>
registerPredefinedCache("oAuth2Authentication", new JCache<Object, Object>(
cacheManager.getCache("oAuth2Authentication").getAdvancedCache(), this,
ConfigurationAdapter.create()));
<%_ } _%>
<%_ if (!skipUserManagement || authenticationType === 'oauth2') { _%>
registerPredefinedCache("users", new JCache<Object, Object>(
cacheManager.getCache("users").getAdvancedCache(), this,
Expand Down
Expand Up @@ -193,16 +193,14 @@ public RestTemplate vanillaRestTemplate() {
<%_ } _%>
<%_ if(authenticationType === 'oauth2') { _%>
import <%=packageName%>.security.AuthoritiesConstants;
import <%=packageName%>.security.oauth2.SimpleAuthoritiesExtractor;
import <%=packageName%>.security.oauth2.SimplePrincipalExtractor;
import <%=packageName%>.security.oauth2.*;
<%_ if(applicationType === 'gateway') { _%>
import org.springframework.beans.factory.annotation.Qualifier;
<%_ } _%>
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.security.oauth2.resource.AuthoritiesExtractor;
import org.springframework.boot.autoconfigure.security.oauth2.resource.PrincipalExtractor;
import org.springframework.boot.autoconfigure.security.oauth2.resource.ResourceServerProperties;
import org.springframework.boot.autoconfigure.security.oauth2.resource.UserInfoTokenServices;
import org.springframework.context.annotation.*;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
Expand Down Expand Up @@ -248,7 +246,9 @@ public MicroserviceSecurityConfiguration(ResourceServerProperties resourceServer
@Bean
@Primary
public UserInfoTokenServices userInfoTokenServices(PrincipalExtractor principalExtractor, AuthoritiesExtractor authoritiesExtractor) {
UserInfoTokenServices userInfoTokenServices = new UserInfoTokenServices(resourceServerProperties.getUserInfoUri(), resourceServerProperties.getClientId());
UserInfoTokenServices userInfoTokenServices =
new CachedUserInfoTokenServices(resourceServerProperties.getUserInfoUri(), resourceServerProperties.getClientId());

userInfoTokenServices.setPrincipalExtractor(principalExtractor);
userInfoTokenServices.setAuthoritiesExtractor(authoritiesExtractor);
return userInfoTokenServices;
Expand Down
@@ -0,0 +1,43 @@
<%#
Copyright 2013-2017 the original author or authors from the JHipster project.

This file is part of the JHipster project, see https://jhipster.github.io/
for more information.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-%>
package <%=packageName%>.security.oauth2;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.security.oauth2.resource.UserInfoTokenServices;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
public class CachedUserInfoTokenServices extends UserInfoTokenServices {
private final Logger log = LoggerFactory.getLogger(UserInfoTokenServices.class);
public CachedUserInfoTokenServices(String userInfoEndpointUrl, String clientId) {
super(userInfoEndpointUrl, clientId);
}
@Override
@Cacheable("oAuth2Authentication")
public OAuth2Authentication loadAuthentication(String accessToken) throws AuthenticationException, InvalidTokenException {
log.debug("Getting user information from OpenID Connect server");
return super.loadAuthentication(accessToken);
}
}

4 comments on commit fba5187

@jdubois
Copy link
Member Author

Choose a reason for hiding this comment

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

@mraible @danielpetisme I'm sorry I'm going fast here, I need this for one of my clients - but I'd love to have your review here, this solves a very important performance issue, as normally microservices hit the OIDC server all the time to get the "user info"

@mraible
Copy link
Contributor

Choose a reason for hiding this comment

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

LGTM! Should it apply to monoliths too or they're OK because of sessions?

@danielpetisme
Copy link
Member

Choose a reason for hiding this comment

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

+1

@jdubois
Copy link
Member Author

@jdubois jdubois commented on fba5187 Oct 27, 2017

Choose a reason for hiding this comment

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

Yes only for microservices as they are stateless - you don't have this issue with gateways and monoliths.
Now I messed up because this needs a cache, so this only works if you have an Hibernate L2 cache, which is stupid... I will correct this today and create a new ticket to refactor caching.

Please sign in to comment.