简体   繁体   中英

Spring Boot + Spring OAuth Java configuration

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 )

@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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM