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-oa
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)
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests().antMatchers("/login").permitAll().and()
// default protection for all resources (including /oauth/authorize)
.authorizeRequests()
.anyRequest().hasRole("USER")
// ... more configuration, e.g. for form login
}
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 :
http
.addFilterAfter(oAuthConsumerContextFilter(), SwitchUserFilter.class)
.addFilterAfter(oAuthConsumerProcessingFilter(), OAuthConsumerContextFilter.class)
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:
@Configuration
@EnableWebSecurity
public class OAuthConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterAfter(
this.oauthConsumerContextFilter(),
SwitchUserFilter.class
);
http.addFilterAfter(
this.oauthConsumerProcessingFilter(),
OAuthConsumerContextFilter.class
);
}
// IMPORTANT: this must not be a Bean
OAuthConsumerContextFilter oauthConsumerContextFilter() {
OAuthConsumerContextFilter filter = new OAuthConsumerContextFilter();
filter.setConsumerSupport(this.consumerSupport());
return filter;
}
// IMPORTANT: this must not be a Bean
OAuthConsumerProcessingFilter oauthConsumerProcessingFilter() {
OAuthConsumerProcessingFilter filter = new OAuthConsumerProcessingFilter();
filter.setProtectedResourceDetailsService(this.prds());
LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> map =
new LinkedHashMap<>();
// one entry per oauth:url element in xml
map.put(
// 1st arg is equivalent of url:pattern in xml
// 2nd arg is equivalent of url:httpMethod in xml
new AntPathRequestMatcher("/oauth/**", null),
// arg is equivalent of url:resources in xml
// IMPORTANT: this must match the ids in prds() and prd() below
Collections.singletonList(new SecurityConfig("myResource"))
);
filter.setObjectDefinitionSource(
new DefaultFilterInvocationSecurityMetadataSource(map)
);
return filter;
}
@Bean // optional, I re-use it elsewhere, hence the Bean
OAuthConsumerSupport consumerSupport() {
CoreOAuthConsumerSupport consumerSupport = new CoreOAuthConsumerSupport();
consumerSupport.setProtectedResourceDetailsService(prds());
return consumerSupport;
}
@Bean // optional, I re-use it elsewhere, hence the Bean
ProtectedResourceDetailsService prds() {
return (String id) -> {
switch (id) {
// this must match the id in prd() below
case "myResource":
return prd();
}
throw new RuntimeException("Invalid id: " + id);
};
}
ProtectedResourceDetails prd() {
BaseProtectedResourceDetails details = new BaseProtectedResourceDetails();
// this must be present and match the id in prds() and prd() above
details.setId("myResource");
details.setConsumerKey("OAuth_Key");
details.setSharedSecret("OAuth_Secret");
details.setRequestTokenURL("...");
details.setUserAuthorizationURL("...");
details.setAccessTokenURL("...");
// any other service-specific settings
return details;
}
}
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 the ProtectedResourceDetails
, and both ids must be present. Even though they are somewhat redundant, leaving out the id in ProtectedResourceDetails
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