I'm trying to get OAuth 1 (3 legged) on a simple Spring Boot + Spring OAuth app, only as a consumer.
I've been trying to port the tonr sample on the spring-security-oauth repository (https://github.com/spring-projects/spring-security-oauth) to use Java config instead of XML.
However, I'm getting:
java.lang.NullPointerException: null
at org.springframework.security.oauth.consumer.filter.OAuthConsumerProcessingFilter.doFilter(OAuthConsumerProcessingFilter.java:87)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.springframework.boot.actuate.trace.WebRequestTraceFilter.doFilterInternal(WebRequestTraceFilter.java:102)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:88)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.springframework.boot.actuate.autoconfigure.MetricFilterAutoConfiguration$MetricsFilter.doFilterInternal(MetricFilterAutoConfiguration.java:90)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:219)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:106)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:501)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:142)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:88)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:516)
at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1086)
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:659)
at org.apache.coyote.http11.Http11NioProtocol$Http11ConnectionHandler.process(Http11NioProtocol.java:223)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1558)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1515)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:745)
...probably because the OAuthConsumerContextFilter is not being setup properly.
I tried configuring the <oauth:consumer>
part as follows:
@Bean
public OAuthConsumerProcessingFilter oAuthConsumerProcessingFilter()
{
OAuthConsumerProcessingFilter result = new OAuthConsumerProcessingFilter();
result.setProtectedResourceDetailsService(protectedResourceDetailsService());
final LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> map = new LinkedHashMap<>();
map.put(new RegexRequestMatcher("/sparklr/*", null), Collections.singletonList(ConsumerSecurityConfig.PERMIT_ALL_ATTRIBUTE));
result.setObjectDefinitionSource(new DefaultFilterInvocationSecurityMetadataSource(map));
return result;
}
@Bean
public ProtectedResourceDetailsService protectedResourceDetailsService()
{
return (String id) -> {
switch (id) {
case "sparklrPhotos":
sparklrProtectedResourceDetails();
break;
}
throw new RuntimeException("Error");
};
}
@Bean
public OAuthConsumerContextFilter oAuthConsumerContextFilter() {
final CoreOAuthConsumerSupport consumerSupport = new CoreOAuthConsumerSupport();
consumerSupport.setProtectedResourceDetailsService(protectedResourceDetailsService());
final OAuthConsumerContextFilter filter = new OAuthConsumerContextFilter();
filter.setConsumerSupport(consumerSupport);
return filter;
}
...but obviously something is missing. I even removed the switch
and returned the same protected resource details all the time, but that doesn't change the fact that I don't have a context.
What should I do to make this work? Let me know if I need to show any other part of my code.
UPDATE: I've added the Consumer Context filter, but I think it's not being applied, as I get the same error
In order to use Spring Security with Java Config you have to have SecurityConfig file with something like this inside (taken from http://projects.spring.io/spring-security-oauth/docs/oauth2.html)
That's also a place where you can add your filters in specific order using
http.addFilterAfter(oAuthConsumerContextFilter(), AnonymousAuthenticationFilter.class);
The problem with your code is that your filter is being executed before Authetication created.
So I guess both of yout filters should be at least after AnonymousAuthenticationFilter.class
You can find list of filters here : http://docs.spring.io/spring-security/site/docs/3.1.x/reference/springsecurity-single.html#filter-stack
This worked for me :
I found that several parts of the code in this question and other answers were incomplete and did not work for various reasons when taken as a whole. Here's the complete solution I found to get Spring Security OAuth 1 working with Spring Boot using Java config.
You need a configuration class like so:
Some key points to understand so you can avoid the problems I faced:
First, the original question configures the Processing filter with
ConsumerSecurityConfig.PERMIT_ALL_ATTRIBUTE
but this doesn't work. The value in the map has to be a SecurityConfig containing the id of the resource which is the OAuth owner for those paths.This id must also match the ids in both the
ProtectedResourceDetailsService
and theProtectedResourceDetails
, and both ids must be present. Even though they are somewhat redundant, leaving out the id inProtectedResourceDetails
breaks the setup.Second, the original question configures both filters as Beans. However, Spring Boot automatically registers any Beans which implement Filter in the main application filter chain which in this case will cause them to run twice and fail the OAuth access token process every time since it looks like a replay attack. More details in this question: Oauth 1.0a consumer code equesting an access token twice
There are two ways to fix this. I used the shorter one above (just don't make them Beans) but you can also create a
FilterRegistrationBean
to disable the auto-registration behavior.With those corrections in place, the code above is tested and confirmed to support a working OAuth 1 negotiation with pure Java config in a Spring Boot container.
References:
The Spring OAuth 1 doc is good for understanding the classes involved and what they're for: https://projects.spring.io/spring-security-oauth/docs/oauth1.html
The Bean Definition Parser is the canonical implementation which converts XML to Java, so see this for any edge cases I didn't mention: https://github.com/codehaus/spring-security-oauth/blob/master/spring-security-oauth/src/main/java/org/springframework/security/oauth/config/OAuthConsumerBeanDefinitionParser.java