Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring OAuth2 not redirecting back to client on form login

I am working on a sample Spring Boot app using OAuth2. The issue is that the client hosted on localhost:8080 calls out to https://localhost:8443/oauth/authorize to authorize itself (implicit grant type) but since /oauth/authorize requires that the user be authenticated they are redirected to the login page at https://localhost:8443/login.

This is all expected, but when the user lands on the login page all of the query strings including redirect_uri are missing. The user logs in and is redirected to https://localhost:8443 rather than the specified redirect_uri of http://localhost:8080.

Is there some way to have the user redirected back to the client after logging in w/ the server's login form? Am I missing something in my config? I can post more as needed.

The authorize request looks like: https://localhost:8443/oauth/authorize?response_type=token&state=6c2bb162-0f26-4caa-abbe-b65f7e5c6a2e&redirect_uri=http%3A%2F%2Flocalhost%3A8080&client_id=admin

SecurityConfig:

@Configuration
public static class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    private final Logger log = LoggerFactory.getLogger(WebSecurityConfig.class);                        

    @Override
    public void configure(WebSecurity web) throws Exception {

        web.ignoring().antMatchers("/resources/**");
    }       

    @SuppressWarnings("deprecation")
    @Override
    protected void configure(HttpSecurity http) throws Exception {                  

        http
            .requestMatchers()
                .antMatchers("/**")
        .and()
            .addFilterAfter(new CsrfCookieGeneratorFilter(), CsrfFilter.class)
            .exceptionHandling()
                .accessDeniedPage("/login?authorization_error=true")
        .and()
            .authorizeRequests()
            .antMatchers("/resources/**", "/csrf").permitAll()
            .anyRequest().authenticated()
        .and()
            .formLogin()
                .loginPage("/login")
                .usernameParameter("j_username")
                .passwordParameter("j_password")
                .defaultSuccessUrl("/", false)
                .failureUrl("/login?authentication_error=true")
                .permitAll()
        .and()
            .logout()
                .logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
                .logoutSuccessUrl("/login")
                .invalidateHttpSession(true)
                .deleteCookies("JSESSIONID", "CSRF-TOKEN")
                .permitAll()
       .and()
            .headers()
                .frameOptions()
                .disable();
    }

OAuthConfig:

@Configuration
@EnableAuthorizationServer
protected static class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {

    @Inject
    @Qualifier("authenticationManagerBean")
    private AuthenticationManager authenticationManager;

    @Bean
    public TokenStore tokenStore() {

        return new InMemoryTokenStore();
    }

    @Primary
    @Bean
    public ResourceServerTokenServices tokenServices() {

        DefaultTokenServices tokenServices = new DefaultTokenServices();
        tokenServices.setSupportRefreshToken(true);
        tokenServices.setTokenStore(tokenStore());

        return tokenServices;
    }        

    @Bean
    public ApprovalStore approvalStore() throws Exception {

        TokenApprovalStore approvalStore = new TokenApprovalStore();
        approvalStore.setTokenStore(tokenStore());

        return approvalStore;
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {

        clients
            .inMemory()
                .withClient("read-only")
                    .secret("readme")
                    .resourceIds(RESOURCE_ID)
                    .authorizedGrantTypes("implicit", "password", "refresh_token")                        
                    .authorities(Constant.USER)
                    .scopes("read")
                    .autoApprove(true)
                    .redirectUris("https://localhost:8443")
                .and()
                .withClient("admin")
                    .secret("admin")
                    .resourceIds(RESOURCE_ID)
                    .authorizedGrantTypes("implicit", "password", "refresh_token")
                    .authorities(Constant.USER, Constant.ADMIN)
                    .scopes("read", "write")
                    .autoApprove(true)
                    .redirectUris("https://localhost:8443", "http://localhost:8080")
                .and()
                .withClient("super-admin")
                    .secret("super")
                    .resourceIds(RESOURCE_ID)
                    .authorizedGrantTypes("implicit", "password", "refresh_token")
                    .authorities(Constant.USER, Constant.ADMIN)
                    .scopes("read", "write", "delete")
                    .redirectUris("https://localhost:8443");
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer configurer) throws Exception {

        configurer
            .tokenStore(tokenStore())
            .authenticationManager(authenticationManager);
    }        

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security)
            throws Exception {

        security.realm("hubble/client");
    }

}

@Configuration
@EnableResourceServer
protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {        

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {

        resources.resourceId(RESOURCE_ID);
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {

        http
            .requestMatchers()
                .antMatchers("/api/**")
        .and()
            .authorizeRequests()
                .antMatchers(HttpMethod.OPTIONS, "/api/**").permitAll()
                .antMatchers(HttpMethod.GET, "/api/**").access("#oauth2.hasScope('read')")
                .antMatchers(HttpMethod.POST, "/api/**").access("#oauth2.hasScope('write')")
                .antMatchers(HttpMethod.PATCH, "/api/**").access("#oauth2.hasScope('write')")
                .antMatchers(HttpMethod.PUT, "/api/**").access("#oauth2.hasScope('write')")
                .antMatchers(HttpMethod.DELETE, "/api/**").access("#oauth2.hasScope('delete')")
                .antMatchers("/api/**").access("hasRole('" + Constant.USER + "')")                  
       .and()
            .anonymous().authorities(Constant.ANONYMOUS)
       .and()
            .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS);                        
    }
}

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true, proxyTargetClass = true)
protected static class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {     

    @Override
    protected MethodSecurityExpressionHandler createExpressionHandler() {

        OAuth2MethodSecurityExpressionHandler methodHandler = new OAuth2MethodSecurityExpressionHandler();

        return methodHandler;
    }
}
like image 445
Ethan Anderson Avatar asked Jan 28 '15 16:01

Ethan Anderson


1 Answers

The problem occurs with form authentication only and it is not related to OAuth. The org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint has a buildRedirectUrlToLoginPage method that creates the login URL and it forgets the query strings.

Currently we solved it with a workaround.

  1. instead of redirecting the user to the authorize url, we redirect directly to the login page.
  2. the login page has a controller that checks if the user was already logged in and if so it redirects to the authorize url with the redirect_uri if present or the default app url as redirect_uri.
  3. from here the redirect_uri is handled correctly by the authorize url.

An example LoginController from step 2. could look like this:

@Controller
@RequestMapping(value = {"/login"})
public class LoginController {
    @RequestMapping(method = RequestMethod.GET)
    public String getPage(HttpServletRequest request, HttpServletResponse response, Principal principal)
            throws IOException {
        if (principal != null) { //depends on your security config, maybe you want to check the security context instead if you allow anonym access
            String redirect_uri = request.getParameter("redirect_uri"); 
            //here you must get all the other attributes thats needed for the authorize url
            if (redirect_uri == null) {
                redirect_uri = "https://your.default.app.url";
            }           
            return "redirect:https://localhost:8443/oauth/authorize?response_type=token&state=6c2bb162-0f26-4caa-abbe-b65f7e5c6a2e&client_id=admin&redirect_uri=" + URLEncoder.encode(redirect_uri, "UTF-8");
        }
        return "login";
    }
}
like image 76
Peter Borbas Avatar answered Oct 14 '22 22:10

Peter Borbas