consolidated client credential filter beans

(note: imports magic from secoauth)
pull/803/head
Justin Richer 2015-03-13 18:30:09 -04:00
parent 4f12fab56b
commit ba51df0c37
3 changed files with 152 additions and 62 deletions

View File

@ -22,10 +22,12 @@
xmlns:context="http://www.springframework.org/schema/context"
xmlns:security="http://www.springframework.org/schema/security"
xmlns:oauth="http://www.springframework.org/schema/security/oauth2"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/security/oauth2 http://www.springframework.org/schema/security/spring-security-oauth2-2.0.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.1.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.2.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.1.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.1.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd">
@ -80,8 +82,8 @@
<security:intercept-url pattern="/token" access="isAuthenticated()" />
<security:http-basic entry-point-ref="oauthAuthenticationEntryPoint" />
<!-- include this only if you need to authenticate clients via request parameters -->
<security:custom-filter ref="clientAssertionTokenEndpointFilter" after="PRE_AUTH_FILTER" /> <!-- this one has to go first -->
<security:custom-filter ref="clientCredentialsTokenEndpointFilter" after="BASIC_AUTH_FILTER" />
<security:custom-filter ref="clientAssertionEndpointFilter" after="PRE_AUTH_FILTER" /> <!-- this one has to go first -->
<security:custom-filter ref="clientCredentialsEndpointFilter" after="BASIC_AUTH_FILTER" />
<security:custom-filter ref="corsFilter" after="SECURITY_CONTEXT_FILTER" />
<security:access-denied-handler ref="oauthAccessDeniedHandler" />
</security:http>
@ -135,9 +137,9 @@
authentication-manager-ref="clientAuthenticationManager">
<security:http-basic entry-point-ref="oauthAuthenticationEntryPoint" />
<!-- <security:custom-filter ref="resourceServerFilter" before="PRE_AUTH_FILTER" /> -->
<security:custom-filter ref="clientAssertionIntrospectionEndpointFilter" after="PRE_AUTH_FILTER" /> <!-- this one has to go first -->
<security:custom-filter ref="clientAssertionEndpointFilter" after="PRE_AUTH_FILTER" /> <!-- this one has to go first -->
<security:custom-filter ref="corsFilter" after="SECURITY_CONTEXT_FILTER" />
<security:custom-filter ref="clientCredentialsIntrospectionEndpointFilter" after="BASIC_AUTH_FILTER" />
<security:custom-filter ref="clientCredentialsEndpointFilter" after="BASIC_AUTH_FILTER" />
</security:http>
<security:http pattern="/#{T(org.mitre.oauth2.web.RevocationEndpoint).URL}**"
@ -147,9 +149,9 @@
authentication-manager-ref="clientAuthenticationManager">
<security:http-basic entry-point-ref="oauthAuthenticationEntryPoint" />
<!-- <security:custom-filter ref="resourceServerFilter" before="PRE_AUTH_FILTER" /> -->
<security:custom-filter ref="clientAssertionRevocationEndpointFilter" after="PRE_AUTH_FILTER" /> <!-- this one has to go first -->
<security:custom-filter ref="clientAssertionEndpointFilter" after="PRE_AUTH_FILTER" /> <!-- this one has to go first -->
<security:custom-filter ref="corsFilter" after="SECURITY_CONTEXT_FILTER" />
<security:custom-filter ref="clientCredentialsRevocationEndpointFilter" after="BASIC_AUTH_FILTER" />
<security:custom-filter ref="clientCredentialsEndpointFilter" after="BASIC_AUTH_FILTER" />
</security:http>
<bean id="oauthAuthenticationEntryPoint" class="org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint">
@ -164,36 +166,26 @@
<bean id="oauth2ExceptionTranslator" class="org.springframework.security.oauth2.provider.error.DefaultWebResponseExceptionTranslator" />
<bean id="clientCredentialsTokenEndpointFilter" class="org.springframework.security.oauth2.provider.client.ClientCredentialsTokenEndpointFilter">
<property name="authenticationManager" ref="clientAuthenticationManager" />
<property name="filterProcessesUrl" value="/token"/>
</bean>
<bean id="clientCredentialsIntrospectionEndpointFilter" class="org.springframework.security.oauth2.provider.client.ClientCredentialsTokenEndpointFilter">
<property name="authenticationManager" ref="clientAuthenticationManager" />
<property name="filterProcessesUrl" value="/introspect"/>
<bean id="clientAuthMatcher" class="org.mitre.openid.connect.filter.MultiUrlRequestMatcher">
<constructor-arg name="filterProcessesUrls">
<set>
<value>/introspect</value>
<value>/revoke</value>
<value>/token</value>
</set>
</constructor-arg>
</bean>
<bean id="clientCredentialsRevocationEndpointFilter" class="org.springframework.security.oauth2.provider.client.ClientCredentialsTokenEndpointFilter">
<bean id="clientCredentialsEndpointFilter" class="org.springframework.security.oauth2.provider.client.ClientCredentialsTokenEndpointFilter">
<property name="authenticationManager" ref="clientAuthenticationManager" />
<property name="filterProcessesUrl" value="/revoke"/>
<property name="requiresAuthenticationRequestMatcher" ref="clientAuthMatcher" />
</bean>
<bean id="clientAssertionTokenEndpointFilter" class="org.mitre.openid.connect.assertion.JWTBearerClientAssertionTokenEndpointFilter">
<bean id="clientAssertionEndpointFilter" class="org.mitre.openid.connect.assertion.JWTBearerClientAssertionTokenEndpointFilter">
<constructor-arg name="additionalMatcher" ref="clientAuthMatcher" />
<property name="authenticationManager" ref="clientAssertionAuthenticationManager" />
<property name="filterProcessesUrl" value="/token" />
</bean>
<bean id="clientAssertionIntrospectionEndpointFilter" class="org.mitre.openid.connect.assertion.JWTBearerClientAssertionTokenEndpointFilter">
<property name="authenticationManager" ref="clientAssertionAuthenticationManager" />
<property name="filterProcessesUrl" value="/introspect" />
</bean>
<bean id="clientAssertionRevocationEndpointFilter" class="org.mitre.openid.connect.assertion.JWTBearerClientAssertionTokenEndpointFilter">
<property name="authenticationManager" ref="clientAssertionAuthenticationManager" />
<property name="filterProcessesUrl" value="/revoke" />
</bean>
<security:authentication-manager id="clientAuthenticationManager">
<security:authentication-provider user-service-ref="clientUserDetailsService" />
</security:authentication-manager>

View File

@ -22,14 +22,23 @@ package org.mitre.openid.connect.assertion;
import java.io.IOException;
import java.text.ParseException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.mockito.AdditionalMatchers;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.oauth2.common.exceptions.BadClientCredentialsException;
import org.springframework.security.oauth2.provider.client.ClientCredentialsTokenEndpointFilter;
import org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.util.matcher.RequestMatcher;
import com.google.common.base.Strings;
import com.nimbusds.jwt.JWT;
@ -41,14 +50,34 @@ import com.nimbusds.jwt.JWTParser;
* @author jricher
*
*/
public class JWTBearerClientAssertionTokenEndpointFilter extends ClientCredentialsTokenEndpointFilter {
public class JWTBearerClientAssertionTokenEndpointFilter extends AbstractAuthenticationProcessingFilter {
public JWTBearerClientAssertionTokenEndpointFilter() {
super();
private AuthenticationEntryPoint authenticationEntryPoint = new OAuth2AuthenticationEntryPoint();
public JWTBearerClientAssertionTokenEndpointFilter(RequestMatcher additionalMatcher) {
super(new ClientAssertionRequestMatcher(additionalMatcher));
// If authentication fails the type is "Form"
((OAuth2AuthenticationEntryPoint) authenticationEntryPoint).setTypeName("Form");
}
public JWTBearerClientAssertionTokenEndpointFilter(String path) {
super(path);
@Override
public void afterPropertiesSet() {
super.afterPropertiesSet();
setAuthenticationFailureHandler(new AuthenticationFailureHandler() {
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
if (exception instanceof BadCredentialsException) {
exception = new BadCredentialsException(exception.getMessage(), new BadClientCredentialsException());
}
authenticationEntryPoint.commence(request, response, exception);
}
});
setAuthenticationSuccessHandler(new AuthenticationSuccessHandler() {
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
// no-op - just allow filter chain to continue to token endpoint
}
});
}
/**
@ -74,42 +103,38 @@ public class JWTBearerClientAssertionTokenEndpointFilter extends ClientCredentia
}
}
/**
* Check to see if the "client_assertion_type" and "client_assertion" parameters are present and contain the right values.
*/
@Override
protected boolean requiresAuthentication(HttpServletRequest request, HttpServletResponse response) {
// check for appropriate parameters
String assertionType = request.getParameter("client_assertion_type");
String assertion = request.getParameter("client_assertion");
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response,
FilterChain chain, Authentication authResult) throws IOException, ServletException {
super.successfulAuthentication(request, response, chain, authResult);
chain.doFilter(request, response);
}
if (Strings.isNullOrEmpty(assertionType) || Strings.isNullOrEmpty(assertion)) {
return false;
} else if (!assertionType.equals("urn:ietf:params:oauth:client-assertion-type:jwt-bearer")) {
return false;
private static class ClientAssertionRequestMatcher implements RequestMatcher {
private RequestMatcher additionalMatcher;
public ClientAssertionRequestMatcher(RequestMatcher additionalMatcher) {
this.additionalMatcher = additionalMatcher;
}
@Override
public boolean matches(HttpServletRequest request) {
// check for appropriate parameters
String assertionType = request.getParameter("client_assertion_type");
String assertion = request.getParameter("client_assertion");
// Can't call to superclass here b/c client creds would break for lack of client_id
// return super.requiresAuthentication(request, response);
String uri = request.getRequestURI();
int pathParamIndex = uri.indexOf(';');
if (pathParamIndex > 0) {
// strip everything after the first semi-colon
uri = uri.substring(0, pathParamIndex);
if (Strings.isNullOrEmpty(assertionType) || Strings.isNullOrEmpty(assertion)) {
return false;
} else if (!assertionType.equals("urn:ietf:params:oauth:client-assertion-type:jwt-bearer")) {
return false;
}
return additionalMatcher.matches(request);
}
if ("".equals(request.getContextPath())) {
return uri.endsWith(getFilterProcessesUrl());
}
return uri.endsWith(request.getContextPath() + getFilterProcessesUrl());
}
}

View File

@ -0,0 +1,73 @@
/*******************************************************************************
* Copyright 2015 The MITRE Corporation
* and the MIT Kerberos and Internet Trust Consortium
*
* 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.mitre.openid.connect.filter;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import org.springframework.security.web.util.UrlUtils;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
import com.google.common.collect.ImmutableSet;
/**
* @author jricher
*
*/
public class MultiUrlRequestMatcher implements RequestMatcher {
private final Set<String> filterProcessesUrls;
public MultiUrlRequestMatcher(Set<String> filterProcessesUrls) {
for (String filterProcessesUrl : filterProcessesUrls) {
Assert.hasLength(filterProcessesUrl, "filterProcessesUrl must be specified");
Assert.isTrue(UrlUtils.isValidRedirectUrl(filterProcessesUrl), filterProcessesUrl + " isn't a valid redirect URL");
}
this.filterProcessesUrls = ImmutableSet.copyOf(filterProcessesUrls);
}
public boolean matches(HttpServletRequest request) {
String uri = request.getRequestURI();
int pathParamIndex = uri.indexOf(';');
if (pathParamIndex > 0) {
// strip everything after the first semi-colon
uri = uri.substring(0, pathParamIndex);
}
if ("".equals(request.getContextPath())) {
// if any one of the URLs match, return true
for (String filterProcessesUrl : filterProcessesUrls) {
if (uri.endsWith(filterProcessesUrl)) {
return true;
}
}
return false;
}
for (String filterProcessesUrl : filterProcessesUrls) {
if (uri.endsWith(request.getContextPath() + filterProcessesUrl)) {
return true;
}
}
return false;
}
}