Reputation: 935
We are building a REST resource server (a Java sample app) which we plan to protect using an identity propagation mechanism defined by RFC7662 available from the MITREID Connect project. We tested both configuration methods, the XML setup, as well as the annotation based setup added to the resource server class (see sample code attached below).
Our testing shows successful initialization of the Spring Security routine, but we did not succeed to trigger the Bearer token passage through the authorization header. The request and resource executes successfully but no token parsing and introspect validation took place. Please check the configuration settings and logs attached below.
Support is welcome to isolate the missing wire between the components (Spring Security, Spring Oauth2 and Mitreid Connect Introspect).
Setup file: spring-security.xml
<?xml version="1.0" encoding="UTF-8"?>
http://www.springframework.org/schema/beans/spring-beans-4.1.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-4.0.xsd http://www.springframework.org/schema/security/oauth2 http://www.springframework.org/schema/security/spring-security-oauth2.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.1.xsd">
<sec:http pattern="/css/**" security="none" />
<sec:http pattern="/js/**" security="none" />
<sec:http auto-config="true" use-expressions="true"
disable-url-rewriting="true" entry-point-ref="oauthAuthenticationEntryPoint"
pattern="/rest/service/sample/restService">
<sec:custom-filter before="PRE_AUTH_FILTER" ref="resourceServerFilter" />
</sec:http>
<sec:authentication-manager alias="authenticationManager">
</sec:authentication-manager>
<!-- Begin OAuth2 Introspect configuration -->
<bean id="oauthAuthenticationEntryPoint"
class="org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint">
<property name="realmName" value="W3IDRealm" />
</bean>
<oauth:resource-server id="resourceServerFilter"
token-services-ref="introspectingService" />
<bean id="introspectingService"
class="org.mitre.oauth2.introspectingfilter.IntrospectingTokenService">
<property name="introspectionConfigurationService" ref="staticIntrospectionConfigurationService">
</property>
</bean>
<!-- <oauth:resource
id="protectedResource"
access-token-uri="${oidc.tokenEndpointUri}"
client-secret="${oidc.clientSecret}"
client-id="${oidc.clientId}"></oauth:resource> -->
<bean
class="org.mitre.oauth2.introspectingfilter.service.impl.StaticIntrospectionConfigurationService"
id="staticIntrospectionConfigurationService">
<property name="introspectionUrl" value="${oidc.introspectEndpointUri}" />
<property name="clientConfiguration">
<bean class="org.mitre.oauth2.model.RegisteredClient">
<property name="clientId" value="${oidc.clientId}" />
<property name="clientSecret" value="${oidc.clientSecret}" />
</bean>
</property>
<!-- <property name="introspectionAuthorityGranter">
<bean class="org.mitre.oauth2.introspectingfilter.SimpleIntrospectionAuthorityGranter">
<property name="authorities">
<value>ROLE_API</value>
</property>
</bean>
</property> -->
</bean>
resource.java
package com.red.sampleoidcservice;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@EnableWebSecurity
@Configuration
@EnableResourceServer
public class RestController {
private static final Logger logger = LoggerFactory.getLogger(RestController.class);
@RequestMapping(value = "/restService", method = RequestMethod.POST)
public @ResponseBody String restService(HttpServletRequest request, HttpServletResponse respose) {
logger.info("Calling rest service");
String requestToString = request.toString();
String headerType = request.getHeader("Content-Type");
String headerAuth = request.getHeader("Authorization");
Map map = request.getParameterMap();
String attributes = request.getAttributeNames().toString();
// String someParam = request.getParameter("someParam");
return "{\"status\":\"OK\"}";
}
protected static class ResourceServer extends ResourceServerConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http.requestMatchers().antMatchers("/rest/service/sample/restService").and().authorizeRequests()
.anyRequest().access("#oauth2.hasScope('read')");
}
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId("W3IDRealm");
}
}
}
post.java
// HTTP POST request
private void sendPost(String token) throws Exception {
try {
token = "blablabla";
TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) {
}
public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) {
}
} };
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, trustAllCerts, new java.security.SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
CloseableHttpClient httpClient = HttpClientBuilder.create().build();
SSLConnectionSocketFactory f = new SSLConnectionSocketFactory(sc, new String[] { "TLSv1.2" }, null,
org.apache.http.conn.ssl.SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
httpClient = HttpClients.custom().setSSLSocketFactory(f).build();
HttpPost postRequest = new HttpPost("https://localhost:9444/rest/service/sample/restService");
postRequest.addHeader("Content-Type", "application/x-www-form-urlencoded");
List<NameValuePair> formparams = new ArrayList<NameValuePair>();
formparams.add(new BasicNameValuePair("client_id", clientId));
formparams.add(new BasicNameValuePair("client_secret", clientSecret));
formparams.add(new BasicNameValuePair("token", token));
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(formparams, "utf-8");
postRequest.setEntity(entity);
postRequest.setHeader("Authorization", "Bearer " + token + "");
HttpResponse response = httpClient.execute(postRequest, new BasicHttpContext());
int statusCode = response.getStatusLine().getStatusCode();
logger.info("HTTP status code : " + statusCode);
} catch (Exception e) {
logger.error(e.getMessage());
}
}
Trace:
INFO : org.springframework.web.servlet.DispatcherServlet - FrameworkServlet 'appServlet': initialization completed in 5872 ms
DEBUG: org.springframework.web.servlet.DispatcherServlet - Servlet 'appServlet' configured successfully DEBUG: org.springframework.web.context.support.StandardServletEnvironment - Adding [servletConfigInitParams] PropertySource with lowest search precedence DEBUG: org.springframework.web.context.support.StandardServletEnvironment - Adding [servletContextInitParams] PropertySource with lowest search precedence DEBUG: org.springframework.web.context.support.StandardServletEnvironment - Adding [jndiProperties] PropertySource with lowest search precedence DEBUG: org.springframework.web.context.support.StandardServletEnvironment - Adding [systemProperties] PropertySource with lowest search precedence DEBUG: org.springframework.web.context.support.StandardServletEnvironment - Adding [systemEnvironment] PropertySource with lowest search precedence DEBUG: org.springframework.web.context.support.StandardServletEnvironment - Initialized StandardServletEnvironment with PropertySources [servletConfigInitParams,servletContextInitParams,jndiProperties,systemProperties,systemEnvironment] DEBUG: org.springframework.web.filter.DelegatingFilterProxy - Initializing filter 'springSecurityFilterChain' DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Returning cached instance of singleton bean 'org.springframework.security.filterChainProxy' DEBUG: org.springframework.web.filter.DelegatingFilterProxy - Filter 'springSecurityFilterChain' configured successfully DEBUG: org.springframework.web.servlet.DispatcherServlet - DispatcherServlet with name 'appServlet' processing POST request for [/rest/service/sample/restService] DEBUG: org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping - Looking up handler method for path /restService DEBUG: org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping - Returning handler method [public java.lang.String com.red.sampleoidcservice.RestController.restService(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)] DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Returning cached instance of singleton bean 'restController' INFO : com.red.sampleoidcservice.RestController - Calling rest service DEBUG: org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor - Written [{"status":"OK"}] as "text/plain" using [org.springframework.http.converter.StringHttpMessageConverter@6912d551] DEBUG: org.springframework.web.servlet.DispatcherServlet - Null ModelAndView returned to DispatcherServlet with name 'appServlet': assuming HandlerAdapter completed request handling DEBUG: org.springframework.web.servlet.DispatcherServlet - Successfully completed request DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Returning cached instance of singleton bean 'delegatingApplicationListener'
Solution Found
Configuration with Annotation
/*******************************************************************************
* Copyright 2014 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 com.RED.sampleoidcservice;
import java.io.IOException;
import java.security.Principal;
import java.util.Locale;
import java.util.Map;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.mitre.oauth2.introspectingfilter.IntrospectingTokenService;
import org.mitre.oauth2.introspectingfilter.service.impl.StaticIntrospectionConfigurationService;
import org.mitre.oauth2.model.ClientDetailsEntity.AuthMethod;
import org.mitre.oauth2.model.RegisteredClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.authentication.BearerTokenExtractor;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.servlet.ModelAndView;
@Controller
@EnableWebSecurity
@Configuration
@EnableResourceServer // [2]
@ComponentScan({ "com.RED.sampleoidcservice" })
public class ResourceServer extends ResourceServerConfigurerAdapter {
private static final Logger logger = LoggerFactory.getLogger(ResourceServer.class);
@Value("${oidc.jwks.keys}")
private String jwksString;
@Value("${oidc.introspectEndpointUri}")
private String introspectURL;
@Value("${oidc.clientId}")
private String clientId;
@Value("${oidc.clientSecret}")
private String clientSecret;
IntrospectingTokenService introspectTokenService = new IntrospectingTokenService();
@RequestMapping(value = "/", method = RequestMethod.GET)
public ModelAndView modelHome(Locale locale, Principal p) {
logger.info("Initializing service resource");
ModelAndView model = new ModelAndView("/home.tiles");
return model;
}
@RequestMapping(value = "/jwk", method = RequestMethod.GET, produces = "application/json")
public @ResponseBody String jwk() {
return jwksString;
}
@RequestMapping(value = "/restService", method = RequestMethod.POST)
public @ResponseBody String restService(HttpServletRequest request, HttpServletResponse respose) {
logger.info("Calling rest service");
String requestToString = request.toString();
String headerType = request.getHeader("Content-Type");
String headerAuth = request.getHeader("Authorization");
String token = headerAuth.split(" ")[1];
// introspectTokenService.readAccessToken(token);
Map map = request.getParameterMap();
String attributes = request.getAttributeNames().toString();
// String someParam = request.getParameter("someParam");
return "{\"status\":\"OK\"}";
}
@Override
public void configure(HttpSecurity http) throws Exception {
http.requestMatcher(new OAuthRequestedMatcher())
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS).permitAll()
.anyRequest().authenticated();
// http.addFilterBefore(new TokenExtractorFilter(), BasicAuthenticationFilter.class).requestMatchers()
// .antMatchers("/rest/service/sample/restService").and().authorizeRequests().anyRequest()
// .access("ROLE_API");
}
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId("W3IDRealm");
resources.tokenExtractor(new BearerTokenExtractor());
StaticIntrospectionConfigurationService introspectConfig = new StaticIntrospectionConfigurationService();
introspectConfig.setIntrospectionUrl(introspectURL);
RegisteredClient client = new RegisteredClient();
client.setClientId(clientId);
client.setClientSecret(clientSecret);
client.setTokenEndpointAuthMethod(AuthMethod.NONE);
introspectConfig.setClientConfiguration(client);
introspectTokenService.setIntrospectionConfigurationService(introspectConfig);
resources.tokenServices(introspectTokenService);
}
private static class OAuthRequestedMatcher implements RequestMatcher {
public boolean matches(HttpServletRequest request) {
String auth = request.getHeader("Authorization");
// Determine if the client request contained an OAuth Authorization
boolean haveOauth2Token = (auth != null) && auth.startsWith("Bearer");
boolean haveAccessToken = request.getParameter("access_token")!=null;
return haveOauth2Token || haveAccessToken;
}
}
class TokenExtractorFilter extends OncePerRequestFilter implements Filter, InitializingBean {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
//UserDetails details = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
BearerTokenExtractor bte = new BearerTokenExtractor();
String mytoken = bte.extract(request).toString();
logger.info("Filter activated");
}
}
}
Upvotes: 2
Views: 2518
Reputation: 935
I've published a solution using an annotation based configuration, which helped us to debug the flow details using Java breakpoints. The OAuth2 Bearer token validation using /introspect is working now. Thanks to all debug support delivered by MITREID. See the code solution posted in the final section of the report, shown above.
Upvotes: 2