Spring-boot: Spring Boot OAuth2 Client Sample not working properly

Created on 14 Nov 2017  路  9Comments  路  Source: spring-projects/spring-boot

First of all thx a lot for the sample code @mbhave and @philwebb

While trying it out I noticed that after a successful login with either GitHub or Google I see following log in the console and get redirected to the /login page as the authorisation was unsuccessful.


Log output

org.springframework.security.access.AccessDeniedException: Access is denied
    at org.springframework.security.access.vote.AffirmativeBased.decide(AffirmativeBased.java:84) ~[spring-security-core-5.0.0.RC1.jar:5.0.0.RC1]
    at org.springframework.security.access.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:233) ~[spring-security-core-5.0.0.RC1.jar:5.0.0.RC1]
    at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:124) ~[spring-security-web-5.0.0.RC1.jar:5.0.0.RC1]
    at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:91) ~[spring-security-web-5.0.0.RC1.jar:5.0.0.RC1]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331) [spring-security-web-5.0.0.RC1.jar:5.0.0.RC1]
    at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:114) ~[spring-security-web-5.0.0.RC1.jar:5.0.0.RC1]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331) [spring-security-web-5.0.0.RC1.jar:5.0.0.RC1]
    at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:137) [spring-security-web-5.0.0.RC1.jar:5.0.0.RC1]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331) [spring-security-web-5.0.0.RC1.jar:5.0.0.RC1]
    at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:111) [spring-security-web-5.0.0.RC1.jar:5.0.0.RC1]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331) [spring-security-web-5.0.0.RC1.jar:5.0.0.RC1]
    at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:170) [spring-security-web-5.0.0.RC1.jar:5.0.0.RC1]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331) [spring-security-web-5.0.0.RC1.jar:5.0.0.RC1]
    at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63) [spring-security-web-5.0.0.RC1.jar:5.0.0.RC1]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331) [spring-security-web-5.0.0.RC1.jar:5.0.0.RC1]
    at org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter.doFilter(DefaultLoginPageGeneratingFilter.java:204) [spring-security-web-5.0.0.RC1.jar:5.0.0.RC1]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331) [spring-security-web-5.0.0.RC1.jar:5.0.0.RC1]
    at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:200) [spring-security-web-5.0.0.RC1.jar:5.0.0.RC1]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331) [spring-security-web-5.0.0.RC1.jar:5.0.0.RC1]
    at org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter.doFilterInternal(OAuth2AuthorizationRequestRedirectFilter.java:109) [spring-security-oauth2-client-5.0.0.RC1.jar:5.0.0.RC1]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-5.0.1.RELEASE.jar:5.0.1.RELEASE]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331) [spring-security-web-5.0.0.RC1.jar:5.0.0.RC1]
    at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:116) [spring-security-web-5.0.0.RC1.jar:5.0.0.RC1]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331) [spring-security-web-5.0.0.RC1.jar:5.0.0.RC1]
    at org.springframework.security.web.csrf.CsrfFilter.doFilterInternal(CsrfFilter.java:100) [spring-security-web-5.0.0.RC1.jar:5.0.0.RC1]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-5.0.1.RELEASE.jar:5.0.1.RELEASE]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331) [spring-security-web-5.0.0.RC1.jar:5.0.0.RC1]
    at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:64) [spring-security-web-5.0.0.RC1.jar:5.0.0.RC1]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-5.0.1.RELEASE.jar:5.0.1.RELEASE]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331) [spring-security-web-5.0.0.RC1.jar:5.0.0.RC1]
    at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:105) [spring-security-web-5.0.0.RC1.jar:5.0.0.RC1]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331) [spring-security-web-5.0.0.RC1.jar:5.0.0.RC1]
    at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:56) [spring-security-web-5.0.0.RC1.jar:5.0.0.RC1]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-5.0.1.RELEASE.jar:5.0.1.RELEASE]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331) [spring-security-web-5.0.0.RC1.jar:5.0.0.RC1]
    at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:214) [spring-security-web-5.0.0.RC1.jar:5.0.0.RC1]
    at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:177) [spring-security-web-5.0.0.RC1.jar:5.0.0.RC1]
    at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:357) [spring-web-5.0.1.RELEASE.jar:5.0.1.RELEASE]
    at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:270) [spring-web-5.0.1.RELEASE.jar:5.0.1.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-8.5.23.jar:8.5.23]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-8.5.23.jar:8.5.23]
    at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99) [spring-web-5.0.1.RELEASE.jar:5.0.1.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-5.0.1.RELEASE.jar:5.0.1.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-8.5.23.jar:8.5.23]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-8.5.23.jar:8.5.23]
    at org.springframework.web.filter.HttpPutFormContentFilter.doFilterInternal(HttpPutFormContentFilter.java:108) [spring-web-5.0.1.RELEASE.jar:5.0.1.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-5.0.1.RELEASE.jar:5.0.1.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-8.5.23.jar:8.5.23]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-8.5.23.jar:8.5.23]
    at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:81) [spring-web-5.0.1.RELEASE.jar:5.0.1.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-5.0.1.RELEASE.jar:5.0.1.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-8.5.23.jar:8.5.23]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-8.5.23.jar:8.5.23]
    at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:200) [spring-web-5.0.1.RELEASE.jar:5.0.1.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-5.0.1.RELEASE.jar:5.0.1.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-8.5.23.jar:8.5.23]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-8.5.23.jar:8.5.23]
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:199) [tomcat-embed-core-8.5.23.jar:8.5.23]
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) [tomcat-embed-core-8.5.23.jar:8.5.23]
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:478) [tomcat-embed-core-8.5.23.jar:8.5.23]
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140) [tomcat-embed-core-8.5.23.jar:8.5.23]
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:81) [tomcat-embed-core-8.5.23.jar:8.5.23]
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87) [tomcat-embed-core-8.5.23.jar:8.5.23]
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342) [tomcat-embed-core-8.5.23.jar:8.5.23]
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:803) [tomcat-embed-core-8.5.23.jar:8.5.23]
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66) [tomcat-embed-core-8.5.23.jar:8.5.23]
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868) [tomcat-embed-core-8.5.23.jar:8.5.23]
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1459) [tomcat-embed-core-8.5.23.jar:8.5.23]
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) [tomcat-embed-core-8.5.23.jar:8.5.23]
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) [na:1.8.0_112]
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) [na:1.8.0_112]
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-8.5.23.jar:8.5.23]
    at java.lang.Thread.run(Thread.java:745) [na:1.8.0_112]

Is there some configuration missing?

Steps to reproduce

  1. Setup project as in the sample repo


Sample pom.xml

```

xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4.0.0

<groupId>ch.aaap</groupId>
<artifactId>3APAdmin</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>

<name>3APAdmin</name>
<description>3AP Admin</description>

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.0.0.M6</version>
    <relativePath /> <!-- lookup parent from repository -->
</parent>


<!-- We don't need this once there is a 2.0.Release version available -->
<repositories>
    <repository>
        <id>spring-snapshots</id>
        <url>http://repo.spring.io/snapshot</url>
        <snapshots>
            <enabled>true</enabled>
        </snapshots>
    </repository>
    <repository>
        <id>spring-milestones</id>
        <url>http://repo.spring.io/milestone</url>
    </repository>
</repositories>
<pluginRepositories>
    <pluginRepository>
        <id>spring-snapshots</id>
        <url>http://repo.spring.io/snapshot</url>
    </pluginRepository>
    <pluginRepository>
        <id>spring-milestones</id>
        <url>http://repo.spring.io/milestone</url>
    </pluginRepository>
</pluginRepositories>

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <java.version>1.8</java.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-config</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-oauth2-client</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-oauth2-jose</artifactId>
    </dependency>


    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>


</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>


```

  1. Register a new OAuth application

  2. Adapt application.yml accordingly

Thx a lot for the help

documentation

Most helpful comment

@gabac I'm assuming you didn't change the redirect-uri in the sample? Correct?

If you didn't than it's http://localhost:8080/oauth2/authorize/code/github in application.yml and the same in your GitHub registered app.

The Filter that handles the Authorization Response callback is OAuth2LoginAuthenticationFilter with the default RequestMatcher matching on /login/oauth2/code/* (OAuth2LoginAuthenticationFilter.DEFAULT_FILTER_PROCESSES_URI).

As you can see the sample redirect-uri /oauth2/authorize/code/github does not match the default filter processing uri /login/oauth2/code/*. This was actually changed recently in Spring Security RC1 and hence broke the sample.

When the Authorization Response is redirected back to the application by GitHub to http://localhost:8080/oauth2/authorize/code/github, the Filter does not intercept the request and given that you need to be authenticated as all endpoints are protected, you get the Access Denied exception. So this makes sense.

For a quick test, change the redirect-uri in the sample to http://localhost:8080/login/oauth2/code/github as well in your GitHub app. This should work for you.

Alternatively, if you want to customize or keep the existing redirect-uri in the sample than you need to provide a custom security configuration as follows:

@EnableWebSecurity
public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .anyRequest().authenticated()
                .and()
            .oauth2Login()
                .redirectionEndpoint()
                    .baseUri("/oauth2/authorize/code/*");
    }
}

This will configure OAuth2LoginAuthenticationFilter to match requests on /oauth2/authorize/code/*.

Hope this all makes sense?

All 9 comments

@gabac that's really hard to say. Please describe the precise steps that lead to this situation. In particular, make sure you've registered your app in configuration

@snicoll adapted the issue with the steps to reproduce 馃憤

Thanks. Something looks broken:

  • In IntelliJ IDEA, I do not get OAuth2 support at all (the regular Spring Security login page) and the loginShouldHaveBothOAuthClientsToChooseFrom test fail for me. The same test passes on the command line (so it could be a glitch of some sort)
  • Running the application on the command-line gives me the two github link but clicking on them has no effect. Nothing in the logs

ping @mbhave

It looks this happens if the redirect-uri for the client registration is a custom one, which is what the sample is doing. If the oauth application is registered with the default redirect-uri used by Spring Security which is /login/oauth2/code/{registration-id}, the login works.

/cc @jgrandja.

@mbhave so the bug is that you can't provide a custom redirect-uri ?

@gabac I'm assuming you didn't change the redirect-uri in the sample? Correct?

If you didn't than it's http://localhost:8080/oauth2/authorize/code/github in application.yml and the same in your GitHub registered app.

The Filter that handles the Authorization Response callback is OAuth2LoginAuthenticationFilter with the default RequestMatcher matching on /login/oauth2/code/* (OAuth2LoginAuthenticationFilter.DEFAULT_FILTER_PROCESSES_URI).

As you can see the sample redirect-uri /oauth2/authorize/code/github does not match the default filter processing uri /login/oauth2/code/*. This was actually changed recently in Spring Security RC1 and hence broke the sample.

When the Authorization Response is redirected back to the application by GitHub to http://localhost:8080/oauth2/authorize/code/github, the Filter does not intercept the request and given that you need to be authenticated as all endpoints are protected, you get the Access Denied exception. So this makes sense.

For a quick test, change the redirect-uri in the sample to http://localhost:8080/login/oauth2/code/github as well in your GitHub app. This should work for you.

Alternatively, if you want to customize or keep the existing redirect-uri in the sample than you need to provide a custom security configuration as follows:

@EnableWebSecurity
public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .anyRequest().authenticated()
                .and()
            .oauth2Login()
                .redirectionEndpoint()
                    .baseUri("/oauth2/authorize/code/*");
    }
}

This will configure OAuth2LoginAuthenticationFilter to match requests on /oauth2/authorize/code/*.

Hope this all makes sense?

It seems like, in that case, the only part that is configurable for the redirectUri, is the *. So the configured redirectUri has to match this pattern: /login/oauth2/code/*. We can update the Boot sample to use that pattern instead. Along with documenting this constraint, we need to document the fact that to handle another url, users will need to configure to process it themselves.

I was looking now for a week to solve the issue with the redirect-uri configuration property with oauth2. The property spring.security.oauth2.client.registration.{registrationId}.redirect-uri is not supported at all and has to be manually configured as @jgrandja written above. Is that correct?

Was this page helpful?
0 / 5 - 0 ratings