Skip to content

OAuth 2.0 Resource Server XML Support #7775

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Mar 2, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ public abstract class Elements {
public static final String CORS = "cors";
public static final String CSRF = "csrf";

public static final String OAUTH2_RESOURCE_SERVER = "oauth2-resource-server";
public static final String JWT = "jwt";
public static final String OPAQUE_TOKEN = "opaque-token";

public static final String WEBSOCKET_MESSAGE_BROKER = "websocket-message-broker";
public static final String INTERCEPT_MESSAGE = "intercept-message";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,18 @@
*/
package org.springframework.security.config.http;

import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import javax.servlet.http.HttpServletRequest;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.w3c.dom.Element;

import org.springframework.beans.BeanMetadataElement;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanReference;
Expand All @@ -36,7 +46,9 @@
import org.springframework.security.core.authority.mapping.SimpleMappableAttributesRetriever;
import org.springframework.security.web.access.AccessDeniedHandlerImpl;
import org.springframework.security.web.access.ExceptionTranslationFilter;
import org.springframework.security.web.access.RequestMatcherDelegatingAccessDeniedHandler;
import org.springframework.security.web.authentication.AnonymousAuthenticationFilter;
import org.springframework.security.web.authentication.DelegatingAuthenticationEntryPoint;
import org.springframework.security.web.authentication.Http403ForbiddenEntryPoint;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider;
Expand All @@ -53,18 +65,10 @@
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.util.xml.DomUtils;
import org.w3c.dom.Element;

import javax.servlet.http.HttpServletRequest;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.function.Function;

import static org.springframework.security.config.http.SecurityFilters.ANONYMOUS_FILTER;
import static org.springframework.security.config.http.SecurityFilters.BASIC_AUTH_FILTER;
import static org.springframework.security.config.http.SecurityFilters.BEARER_TOKEN_AUTH_FILTER;
import static org.springframework.security.config.http.SecurityFilters.EXCEPTION_TRANSLATION_FILTER;
import static org.springframework.security.config.http.SecurityFilters.FORM_LOGIN_FILTER;
import static org.springframework.security.config.http.SecurityFilters.LOGIN_PAGE_FILTER;
Expand Down Expand Up @@ -136,6 +140,8 @@ final class AuthenticationConfigBuilder {
private BeanMetadataElement mainEntryPoint;
private BeanMetadataElement accessDeniedHandler;

private BeanDefinition bearerTokenAuthenticationFilter;

private BeanDefinition logoutFilter;
@SuppressWarnings("rawtypes")
private ManagedList logoutHandlers;
Expand All @@ -160,11 +166,15 @@ final class AuthenticationConfigBuilder {
private BeanReference oauth2LoginAuthenticationProviderRef;
private BeanReference oauth2LoginOidcAuthenticationProviderRef;
private BeanDefinition oauth2LoginLinks;

private BeanDefinition authorizationRequestRedirectFilter;
private BeanDefinition authorizationCodeGrantFilter;
private BeanReference authorizationCodeAuthenticationProviderRef;

private final List<BeanReference> authenticationProviders = new ManagedList<>();
private final Map<BeanDefinition, BeanMetadataElement> defaultDeniedHandlerMappings = new ManagedMap<>();
private final Map<BeanDefinition, BeanMetadataElement> defaultEntryPointMappings = new ManagedMap<>();
private final List<BeanDefinition> csrfIgnoreRequestMatchers = new ManagedList<>();

AuthenticationConfigBuilder(Element element, boolean forceAutoConfig,
ParserContext pc, SessionCreationPolicy sessionPolicy,
BeanReference requestCache, BeanReference authenticationManager,
Expand All @@ -184,6 +194,7 @@ final class AuthenticationConfigBuilder {
createAnonymousFilter();
createRememberMeFilter(authenticationManager);
createBasicFilter(authenticationManager);
createBearerTokenAuthenticationFilter(authenticationManager);
createFormLoginFilter(sessionStrategy, authenticationManager);
createOAuth2LoginFilter(sessionStrategy, authenticationManager);
createOAuth2ClientFilter(requestCache, authenticationManager);
Expand All @@ -194,7 +205,6 @@ final class AuthenticationConfigBuilder {
createLoginPageFilterIfNeeded();
createUserDetailsServiceFactory();
createExceptionTranslationFilter();

}

void createRememberMeFilter(BeanReference authenticationManager) {
Expand Down Expand Up @@ -498,6 +508,21 @@ void createBasicFilter(BeanReference authManager) {
basicFilter = filterBuilder.getBeanDefinition();
}

void createBearerTokenAuthenticationFilter(BeanReference authManager) {
Element resourceServerElt = DomUtils.getChildElementByTagName(httpElt,
Elements.OAUTH2_RESOURCE_SERVER);

if (resourceServerElt == null) {
// No resource server, do nothing
return;
}

OAuth2ResourceServerBeanDefinitionParser resourceServerBuilder =
new OAuth2ResourceServerBeanDefinitionParser(authManager, authenticationProviders,
defaultEntryPointMappings, defaultDeniedHandlerMappings, csrfIgnoreRequestMatchers);
bearerTokenAuthenticationFilter = resourceServerBuilder.parse(resourceServerElt, pc);
}

void createX509Filter(BeanReference authManager) {
Element x509Elt = DomUtils.getChildElementByTagName(httpElt, Elements.X509);
RootBeanDefinition filter = null;
Expand Down Expand Up @@ -708,6 +733,10 @@ BeanMetadataElement getAccessDeniedHandlerBean() {
return accessDeniedHandler;
}

List<BeanDefinition> getCsrfIgnoreRequestMatchers() {
return csrfIgnoreRequestMatchers;
}

void createAnonymousFilter() {
Element anonymousElt = DomUtils.getChildElementByTagName(httpElt,
Elements.ANONYMOUS);
Expand Down Expand Up @@ -801,13 +830,27 @@ private BeanMetadataElement createAccessDeniedHandler(Element element,

}
accessDeniedHandler.addPropertyValue("errorPage", errorPage);
return accessDeniedHandler.getBeanDefinition();
}
else if (StringUtils.hasText(ref)) {
return new RuntimeBeanReference(ref);
}

}

if (this.defaultDeniedHandlerMappings.isEmpty()) {
return accessDeniedHandler.getBeanDefinition();
}
if (this.defaultDeniedHandlerMappings.size() == 1) {
return this.defaultDeniedHandlerMappings.values().iterator().next();
}

accessDeniedHandler = BeanDefinitionBuilder
.rootBeanDefinition(RequestMatcherDelegatingAccessDeniedHandler.class);
accessDeniedHandler.addConstructorArgValue(this.defaultDeniedHandlerMappings);
accessDeniedHandler.addConstructorArgValue
(BeanDefinitionBuilder.rootBeanDefinition(AccessDeniedHandlerImpl.class));

return accessDeniedHandler.getBeanDefinition();
}

Expand All @@ -820,6 +863,16 @@ private BeanMetadataElement selectEntryPoint() {
return new RuntimeBeanReference(customEntryPoint);
}

if (!defaultEntryPointMappings.isEmpty()) {
if (defaultEntryPointMappings.size() == 1) {
return defaultEntryPointMappings.values().iterator().next();
}
BeanDefinitionBuilder delegatingEntryPoint = BeanDefinitionBuilder
.rootBeanDefinition(DelegatingAuthenticationEntryPoint.class);
delegatingEntryPoint.addConstructorArgValue(defaultEntryPointMappings);
return delegatingEntryPoint.getBeanDefinition();
}

Element basicAuthElt = DomUtils.getChildElementByTagName(httpElt,
Elements.BASIC_AUTH);
Element formLoginElt = DomUtils.getChildElementByTagName(httpElt,
Expand Down Expand Up @@ -935,8 +988,12 @@ List<OrderDecorator> getFilters() {
filters.add(new OrderDecorator(basicFilter, BASIC_AUTH_FILTER));
}

if (bearerTokenAuthenticationFilter != null) {
filters.add(new OrderDecorator(bearerTokenAuthenticationFilter, BEARER_TOKEN_AUTH_FILTER));
}

if (authorizationCodeGrantFilter != null) {
filters.add(new OrderDecorator(authorizationRequestRedirectFilter, OAUTH2_AUTHORIZATION_REQUEST_FILTER.getOrder()+1));
filters.add(new OrderDecorator(authorizationRequestRedirectFilter, OAUTH2_AUTHORIZATION_REQUEST_FILTER.getOrder() + 1));
filters.add(new OrderDecorator(authorizationCodeGrantFilter, OAUTH2_AUTHORIZATION_CODE_GRANT_FILTER));
}

Expand Down Expand Up @@ -980,6 +1037,8 @@ List<BeanReference> getProviders() {
providers.add(authorizationCodeAuthenticationProviderRef);
}

providers.addAll(this.authenticationProviders);

return providers;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -15,12 +15,19 @@
*/
package org.springframework.security.config.http;

import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import javax.servlet.http.HttpServletRequest;

import org.w3c.dom.Element;

import org.springframework.beans.BeanMetadataElement;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.ManagedList;
import org.springframework.beans.factory.support.ManagedMap;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
Expand All @@ -38,6 +45,10 @@
import org.springframework.security.web.servlet.support.csrf.CsrfRequestDataValueProcessor;
import org.springframework.security.web.session.InvalidSessionAccessDeniedHandler;
import org.springframework.security.web.session.InvalidSessionStrategy;
import org.springframework.security.web.util.matcher.AndRequestMatcher;
import org.springframework.security.web.util.matcher.NegatedRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;

Expand All @@ -58,6 +69,8 @@ public class CsrfBeanDefinitionParser implements BeanDefinitionParser {
private String csrfRepositoryRef;
private BeanDefinition csrfFilter;

private String requestMatcherRef;

@Override
public BeanDefinition parse(Element element, ParserContext pc) {
boolean disabled = element != null
Expand All @@ -77,10 +90,9 @@ public BeanDefinition parse(Element element, ParserContext pc) {
}
}

String matcherRef = null;
if (element != null) {
this.csrfRepositoryRef = element.getAttribute(ATT_REPOSITORY);
matcherRef = element.getAttribute(ATT_MATCHER);
this.requestMatcherRef = element.getAttribute(ATT_MATCHER);
}

if (!StringUtils.hasText(this.csrfRepositoryRef)) {
Expand All @@ -100,8 +112,8 @@ public BeanDefinition parse(Element element, ParserContext pc) {
.rootBeanDefinition(CsrfFilter.class);
builder.addConstructorArgReference(this.csrfRepositoryRef);

if (StringUtils.hasText(matcherRef)) {
builder.addPropertyReference("requireCsrfProtectionMatcher", matcherRef);
if (StringUtils.hasText(this.requestMatcherRef)) {
builder.addPropertyReference("requireCsrfProtectionMatcher", this.requestMatcherRef);
}

this.csrfFilter = builder.getBeanDefinition();
Expand Down Expand Up @@ -172,4 +184,46 @@ BeanDefinition getCsrfLogoutHandler() {
csrfAuthenticationStrategy.addConstructorArgReference(this.csrfRepositoryRef);
return csrfAuthenticationStrategy.getBeanDefinition();
}

void setIgnoreCsrfRequestMatchers(List<BeanDefinition> requestMatchers) {
if (!requestMatchers.isEmpty()) {
BeanMetadataElement requestMatcher;
if (StringUtils.hasText(this.requestMatcherRef)) {
requestMatcher = new RuntimeBeanReference(this.requestMatcherRef);
} else {
requestMatcher = new RootBeanDefinition(DefaultRequiresCsrfMatcher.class);
}
BeanDefinitionBuilder and = BeanDefinitionBuilder
.rootBeanDefinition(AndRequestMatcher.class);
BeanDefinitionBuilder negated = BeanDefinitionBuilder
.rootBeanDefinition(NegatedRequestMatcher.class);
BeanDefinitionBuilder or = BeanDefinitionBuilder
.rootBeanDefinition(OrRequestMatcher.class);
or.addConstructorArgValue(requestMatchers);
negated.addConstructorArgValue(or.getBeanDefinition());
List<BeanMetadataElement> ands = new ManagedList<>();
ands.add(requestMatcher);
ands.add(negated.getBeanDefinition());
and.addConstructorArgValue(ands);
this.csrfFilter.getPropertyValues()
.add("requireCsrfProtectionMatcher", and.getBeanDefinition());
}
}

private static final class DefaultRequiresCsrfMatcher implements RequestMatcher {
private final HashSet<String> allowedMethods = new HashSet<>(
Arrays.asList("GET", "HEAD", "TRACE", "OPTIONS"));

/*
* (non-Javadoc)
*
* @see
* org.springframework.security.web.util.matcher.RequestMatcher#matches(javax.
* servlet.http.HttpServletRequest)
*/
@Override
public boolean matches(HttpServletRequest request) {
return !this.allowedMethods.contains(request.getMethod());
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -238,6 +238,12 @@ void setAccessDeniedHandler(BeanMetadataElement accessDeniedHandler) {
}
}

void setCsrfIgnoreRequestMatchers(List<BeanDefinition> requestMatchers) {
if (csrfParser != null) {
csrfParser.setIgnoreCsrfRequestMatchers(requestMatchers);
}
}

// Needed to account for placeholders
static String createPath(String path, boolean lowerCase) {
return lowerCase ? path.toLowerCase() : path;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,14 @@
*/
package org.springframework.security.config.http;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.w3c.dom.Element;

import org.springframework.beans.BeanMetadataElement;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanReference;
Expand Down Expand Up @@ -44,9 +50,6 @@
import org.springframework.security.web.util.matcher.AnyRequestMatcher;
import org.springframework.util.StringUtils;
import org.springframework.util.xml.DomUtils;
import org.w3c.dom.Element;

import java.util.*;

/**
* Sets up HTTP security: filter stack and protected URLs.
Expand Down Expand Up @@ -156,6 +159,7 @@ private BeanReference createFilterChain(Element element, ParserContext pc) {
httpBldr.setLogoutHandlers(authBldr.getLogoutHandlers());
httpBldr.setEntryPoint(authBldr.getEntryPointBean());
httpBldr.setAccessDeniedHandler(authBldr.getAccessDeniedHandlerBean());
httpBldr.setCsrfIgnoreRequestMatchers(authBldr.getCsrfIgnoreRequestMatchers());

authenticationProviders.addAll(authBldr.getProviders());

Expand Down
Loading