1. 程式人生 > >Spring Security 4 退出後再登入,頁面停留在登入頁,Url卻多了一個Logout引數

Spring Security 4 退出後再登入,頁面停留在登入頁,Url卻多了一個Logout引數

spring security 4 的logout問題
今天在整合spring-boot 和 spring-security的時候,出現瞭如下的怪象:
配置好了基本的登入和身份驗證(自定義了一個簡單的UserDetailsService),springboot的配置如下:

       http.authorizeRequests().anyRequest().authenticated()
                    .and()
                    .formLogin()
                    .loginPage("/signin"
).permitAll() .failureUrl("/signin?error=1").permitAll() .defaultSuccessUrl("/index") .loginProcessingUrl("/login") .and() .logout().logoutRequestMatcher(AntPathRequestMatcher("/logout", "POST")) .deleteCookies("JSESSIONID"
) .invalidateHttpSession(true) .and() .httpBasic();

首次登入沒有問題,即:登入後直接進入了Java-Based配置好的index頁面。然後點選執行退出,因為啟用了CSRF策略,所有登出分為”signout -> logout”兩步。登出也OK了。但是,此時再次登入,頁面沒有進入index,而是停留在登入頁面。仔細觀察URL,發現瀏覽器位址列的URL變成了http://localhost:8080/signin?logout。經過調查,發現是LogoutSuccessHandler出了問題。

預設情況下,spring security 4啟用了SimpleUrlLogoutSuccessHandler來處理登出的邏輯,關鍵程式碼如下:

public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response,
            Authentication authentication) throws IOException, ServletException {
    super.handle(request, response, authentication);
}

superAbstractAuthenticationTargetUrlRequestHandler,其handle方法的程式碼為:

    protected void handle(HttpServletRequest request, HttpServletResponse response,
            Authentication authentication) throws IOException, ServletException {
         String targetUrl = determineTargetUrl(request, response);
        if (response.isCommitted()) {
            logger.debug("Response has already been committed. Unable to redirect to "
                    + targetUrl);
            return;
        }
        redirectStrategy.sendRedirect(request, response, targetUrl);
    }

其中,最關鍵的就是

String targetUrl = determineTargetUrl(request, response);

通過detemineTargetUrl方法得到的targetUrl就是http://localhost:8080/signin?logout,所以程式最後跳轉到了這個路徑。那麼問題來了,determineTargetUrl方法怎麼會生成帶有signin/logout的URL呢?

追根溯源,最後來到了這裡:
spring-security 4 內部使用AbstractAuthenticationFilterConfigurerLogoutConfigurer進行配置,而LogoutConfigurer在內部設定了SimpleUrlLogoutSuccessHandler的defaultTargetUrl引數,具體程式碼如下:
AbstractAuthenticationFilterConfigurer的相關程式碼:

private void updateAuthenticationDefaults() {
        if (loginProcessingUrl == null) {
            loginProcessingUrl(loginPage);
        }
        if (failureHandler == null) {
            failureUrl(loginPage + "?error");
        }
        final LogoutConfigurer<B> logoutConfigurer = getBuilder().getConfigurer(
                LogoutConfigurer.class);
        if (logoutConfigurer != null && !logoutConfigurer.isCustomLogoutSuccess()) {
             logoutConfigurer.logoutSuccessUrl(loginPage + "?logout");
        }
}

注意上面第11行,spring security 4 預設將登入頁面的url拼上一個?logout引數作為logout成功後的跳轉URL,正式這段程式碼,導致了本文之初所述的問題

LogoutConfigurer的相關程式碼:

    /**
     * The URL to redirect to after logout has occurred. The default is "/login?logout".
     * This is a shortcut for invoking {@link #logoutSuccessHandler(LogoutSuccessHandler)}
     * with a {@link SimpleUrlLogoutSuccessHandler}.
     *
     * @param logoutSuccessUrl the URL to redirect to after logout occurred
     * @return the {@link LogoutConfigurer} for further customization
     */
    public LogoutConfigurer<H> logoutSuccessUrl(String logoutSuccessUrl) {
        this.customLogoutSuccess = true;
        this.logoutSuccessUrl = logoutSuccessUrl;
        return this;
    }

    /**
     * Gets the {@link LogoutSuccessHandler} if not null, otherwise creates a new
     * {@link SimpleUrlLogoutSuccessHandler} using the {@link #logoutSuccessUrl(String)}.
     *
     * @return the {@link LogoutSuccessHandler} to use
     */
    private LogoutSuccessHandler getLogoutSuccessHandler() {
        LogoutSuccessHandler handler = this.logoutSuccessHandler;
        if (handler == null) {
            handler = createDefaultSuccessHandler();
        }
        return handler;
    }
    private LogoutSuccessHandler createDefaultSuccessHandler() {
        SimpleUrlLogoutSuccessHandler urlLogoutHandler = new SimpleUrlLogoutSuccessHandler();
        urlLogoutHandler.setDefaultTargetUrl(logoutSuccessUrl);
        if(defaultLogoutSuccessHandlerMappings.isEmpty()) {
            return urlLogoutHandler;
        }
        DelegatingLogoutSuccessHandler successHandler = new DelegatingLogoutSuccessHandler(defaultLogoutSuccessHandlerMappings);
        successHandler.setDefaultLogoutSuccessHandler(urlLogoutHandler);
        return successHandler;
    }