Spring OAuth2在表单登录时不会重定向回客户端

时间:2015-01-28 16:55:28

标签: spring oauth spring-security spring-boot spring-security-oauth2

我正在使用OAuth2开发示例Spring Boot应用程序。问题是托管在localhost:8080上的客户端会调用https://localhost:8443/oauth/authorize来授权自己(隐式授权类型),但由于/oauth/authorize要求用户进行身份验证,因此会将其重定向到登录页面https://localhost:8443/login

这是所有预期的,但是当用户登陆登录页面时,所有查询字符串(包括redirect_uri)都将丢失。用户登录并重定向到https://localhost:8443,而不是指定的http://localhost:8080 redirect_uri。

在使用服务器的登录表单登录后,有没有办法让用户重定向回客户端?我在配置中遗漏了什么吗?我可以根据需要发布更多内容。

授权请求如下: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;
    }
}

1 个答案:

答案 0 :(得分:1)

问题仅出现在表单身份验证中,并且与OAuth无关。 org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint有一个 buildRedirectUrlToLoginPage方法,用于创建登录URL并忘记查询字符串。

目前我们通过解决方法解决了这个问题。

  1. 我们不是将用户重定向到授权网址,而是直接重定向到登录页面。
  2. 登录页面有一个控制器,用于检查用户是否已登录,如果是,则重定向到授权网址,其中包含redirect_uri(如果存在)或默认应用程序网址为redirect_uri。
  3. 从这里,redirect_uri由授权网址正确处理。
  4. 步骤2中的示例LoginController可能如下所示:

    @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";
        }
    }