From da0a58c892143f858fbe7a51c1a63228d7dc9691 Mon Sep 17 00:00:00 2001 From: Matija Obreza Date: Sun, 27 Jan 2019 16:47:23 +0100 Subject: [PATCH] Implemented a CORS Origins filter - Added allowedOrigins to OAuthClient - Return 403 if Origin does not match a listed origin for the client --- .../blocks/oauth/model/OAuthClient.java | 30 +++++ .../OAuthClientOriginCheckFilter.java | 117 ++++++++++++++++++ 2 files changed, 147 insertions(+) create mode 100644 security/src/main/java/org/genesys/blocks/security/component/OAuthClientOriginCheckFilter.java diff --git a/security/src/main/java/org/genesys/blocks/oauth/model/OAuthClient.java b/security/src/main/java/org/genesys/blocks/oauth/model/OAuthClient.java index 7529e64..5c03cca 100644 --- a/security/src/main/java/org/genesys/blocks/oauth/model/OAuthClient.java +++ b/security/src/main/java/org/genesys/blocks/oauth/model/OAuthClient.java @@ -154,6 +154,16 @@ public class OAuthClient extends AclSid implements ClientDetails, Copyable allowedOrigins = new HashSet<>(); + + /** The origins. */ + @JsonIgnore + @Column(nullable = true, length = 200) + private String origins; + /** * Instantiates a new o auth client. */ @@ -179,6 +189,7 @@ public class OAuthClient extends AclSid implements ClientDetails, Copyable StringUtils.isNotBlank(r)).forEach(u -> redirectUris.add(u)); } + if (origins != null) { + Arrays.stream(StringUtils.split(origins, ";")).filter(r -> StringUtils.isNotBlank(r)).forEach(u -> allowedOrigins.add(u)); + } } /* @@ -647,4 +661,20 @@ public class OAuthClient extends AclSid implements ClientDetails, Copyable getAllowedOrigins() { + return allowedOrigins; + } + + public void setAllowedOrigins(Set allowedOrigins) { + this.allowedOrigins = allowedOrigins; + } } diff --git a/security/src/main/java/org/genesys/blocks/security/component/OAuthClientOriginCheckFilter.java b/security/src/main/java/org/genesys/blocks/security/component/OAuthClientOriginCheckFilter.java new file mode 100644 index 0000000..1c7b6aa --- /dev/null +++ b/security/src/main/java/org/genesys/blocks/security/component/OAuthClientOriginCheckFilter.java @@ -0,0 +1,117 @@ +/* + * Copyright 2018 Global Crop Diversity Trust + * + * 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 org.genesys.blocks.security.component; + +import java.io.IOException; +import java.util.Set; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.genesys.blocks.oauth.model.OAuthClient; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.oauth2.provider.ClientDetailsService; +import org.springframework.security.oauth2.provider.OAuth2Authentication; +import org.springframework.web.filter.OncePerRequestFilter; + +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; + +/** + * Filter OAuth2 requests and check that Origin header matches what we have on + * file. + */ +public class OAuthClientOriginCheckFilter extends OncePerRequestFilter { + + @Autowired + @Qualifier("oauthService") + private ClientDetailsService clientDetailsService; + + private LoadingCache> clientOriginsCache = CacheBuilder.newBuilder().maximumSize(100).expireAfterWrite(10, TimeUnit.MINUTES).build( + new CacheLoader>() { + public Set load(String clientId) { + if (logger.isInfoEnabled()) { + logger.info("Loading allowed origins for client: " + clientId); + } + OAuthClient clientDetails = (OAuthClient) clientDetailsService.loadClientByClientId(clientId); + return clientDetails.getAllowedOrigins(); + } + }); + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + if (authentication != null && authentication instanceof OAuth2Authentication) { + if (!checkValidOrigin(request, (OAuth2Authentication) authentication)) { + response.sendError(403, "Request origin not valid"); + return; + } + } else { + if (logger.isDebugEnabled()) { + logger.debug("Authentication null for origin: " + request.getHeader("Origin")); + } + } + filterChain.doFilter(request, response); + } + + private boolean checkValidOrigin(HttpServletRequest request, OAuth2Authentication authAuth) { + String reqOrigin = request.getHeader("Origin"); + + if (authAuth.getOAuth2Request() != null) { + String clientId = authAuth.getOAuth2Request().getClientId(); + + try { + Set allowedOrigins = clientOriginsCache.get(clientId); + + if (!allowedOrigins.isEmpty()) { + if (reqOrigin == null) { + if (logger.isInfoEnabled()) { + logger.info("No origin header in request. Denying."); + } + return false; + } + for (String allowedOrigin : allowedOrigins) { + if (reqOrigin.startsWith(allowedOrigin)) { + if (logger.isDebugEnabled()) { + logger.debug("Origin match: " + reqOrigin + " startsWith " + allowedOrigin); + } + return true; + } + } + // No declared origins match + if (logger.isInfoEnabled()) { + logger.info("No origin match: " + reqOrigin + " in " + allowedOrigins.toString()); + } + return false; + } + } catch (ExecutionException e) { + logger.warn("Error loading client origins", e); + } + } + if (logger.isDebugEnabled()) { + logger.debug("Allowing request with Origin: " + reqOrigin); + } + return true; + } +} \ No newline at end of file -- GitLab