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

Posted

技术标签:

【中文标题】Spring OAuth2 不会在表单登录时重定向回客户端【英文标题】:Spring OAuth2 not redirecting back to client on form login 【发布时间】:2015-03-27 16:31:47 【问题描述】:

我正在使用 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

安全配置:

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

【问题讨论】:

它对我有用(我从不使用defaultSuccessUrl(),但我没有看到任何明显的问题)。你能发布一个完整项目的链接吗? 感谢@DaveSyer 观看!我尝试了 defaultSuccessUrl() 希望它能解决我的问题,但不管有没有骰子。如果我使用基本身份验证而不是表单登录来保护 /oauth/**,它就可以工作。明天我会尝试将项目放到 github 上并提供链接。 嗨,这个成功了吗?我有同样的问题。 您好,也有同样的问题。 OP 或@pborbas 你解决了吗? @J.Marciniak 我添加了答案。 【参考方案1】:

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

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

    我们没有将用户重定向到授权网址,而是直接重定向到登录页面。 登录页面有一个控制器,用于检查用户是否已登录,如果已登录,它会使用 redirect_uri(如果存在)或默认应用程序 url 作为 redirect_uri 重定向到授权 url。 从这里开始,redirect_uri 由授权 url 正确处理。

第 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";
    

【讨论】:

授权不是应该由隐式处理而不是硬编码吗?我总是被重定向到成功登录时登录

以上是关于Spring OAuth2 不会在表单登录时重定向回客户端的主要内容,如果未能解决你的问题,请参考以下文章

Spring Cloud OAuth2:身份验证后再次重定向到登录页面,就像使用表单登录时未经身份验证一样

提交表单时重定向到错误的路线

RouterBrowser 不会在刷新时重定向到默认页面

Spring Social:当服务器在代理后面时重定向 url

通过 JSF 表单成功登录后 Spring Security 不会重定向到登录页面

使用 react-router-v5 和 redux-toolkit 登录时重定向页面