spring security 3.1中login和logout后的跳转处理

有一个场景需要在登录或退出时传递一个redirectUrl参数作为退出后的跳转地址,找遍spring security的文档也没有发现相关配置。

spring security的form-login提供了default-target-url作为登录成功后的跳转地址,唯独没有允许传递一个redirectUrl参数来作为成功后的跳转地址。

同样的logout标签提供了logout-success-url作为退出成功后的跳转地址,也没有提供允许传递redirectUrl参数来进行跳转。

本来打算自己实现和AdminAuthSuccessHandler和LogoutSuccessHandler来接收redirectUrl参数进行跳转的,结果查看spring security的代码无意间发现spring security居然提供了targetUrlParameter作为跳转地址的参数,只是Security Namespace没有相关的标签或属性。于是在读完跳转相关的代码之后自己写了以下配置。

注:如果直接允许传递redirectUrl作为跳转地址,会有一定的安全风险。在使用之前,确保redirectUrl可信。下面的这段配置有一定安全隐患。

<sec:http auto-config=”true”>
    <sec:intercept-url pattern=”/admin/login” access=”IS_AUTHENTICATED_ANONYMOUSLY”/>
    <sec:form-login login-page=”/admin/login” authentication-failure-handler-ref=”adminAuthFailureHandler” authentication-success-handler-ref=”adminAuthSuccessHandler”/>
    <sec:logout success-handler-ref=”adminLogoutSuccessHandler”/>
    <sec:remember-me data-source-ref=”jobDataSource”/>
    sec:session-management
        <sec:concurrency-control error-if-maximum-exceeded=”true” max-sessions=”1”/>
   

           

通过查看源码我们发现,SimpleUrlLogoutSuccessHandler继续了AbstractAuthenticationTargetUrlRequestHandler,同样的SavedRequestAwareAuthenticationSuccessHandler也是继承了AbstractAuthenticationTargetUrlRequestHandler,而在AbstractAuthenticationTargetUrlRequestHandler的handle方法里,我们发现它是通过determineTargetUrl()方法来决定Redirect地址的。

public class SimpleUrlLogoutSuccessHandler extends AbstractAuthenticationTargetUrlRequestHandler
implements LogoutSuccessHandler {

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

}

通过分析determineTargetUrl我们得出以下结论:

①如果alwaysUseDefaultTargetUrl为true,则跳转地址始终为defaultTargetUrl

②如果targetUrlParameter不为null,则从request中targetUrlParameter指定的参数名获取跳转地址

③如果useReferer为true,并且前两步没有获取到跳转地址,则从请求头中的Referer获取跳转地址

④如果以上几步都为空,则使用设置的defaultTargetUrl作为跳转地址

⑤defaultTargetUrl的默认值是”/“

在最终跳转时,spring security使用一个RedirectStrategy策略来进行跳转,一般都是使用DefaultRedirectStrategy来进行跳转,你也可以实现自己的RedirectStrategy并配置在adminAuthSuccessHandler的bean定义中。

在刚开始的时候我们说过“如果直接允许传递redirectUrl作为跳转地址,会有一定的安全风险”。如果别人通过你的login或logout传递redirectUrl参数误导用户跳到了病毒木马网站,那这肯定是你的安全做的不够,为了安全我们,我们需要实现自己的SafeRedirectStrategy,如下所示,在跳转之前对URL进行了白名单校验。

/**
* 安全的RedirectStrategy,主要是判断跳转地址是否在白名单中
* @author guoweiwei gww0426@163.com
*
*/
public class SafeRedirectStrategy implements RedirectStrategy {

protected final Log logger = LogFactory.getLog(getClass());

private boolean contextRelative;

/\*\*
 \* Redirects the response to the supplied URL.
 \* <p>
 \* If <tt>contextRelative</tt> is set, the redirect value will be the value after the request context path. Note
 \* that this will result in the loss of protocol information (HTTP or HTTPS), so will cause problems if a
 \* redirect is being performed to change to HTTPS, for example.
 */
public void sendRedirect(HttpServletRequest request, HttpServletResponse response, String url) throws IOException {
    String redirectUrl = calculateRedirectUrl(request.getContextPath(), url);
    

    try {
        if(UrlUtils.isAbsoluteUrl(redirectUrl)){

                        redirectUrl = UrlUtil.buildRedirectLink(redirectUrl, false);
                    }
} catch (Exception e) {
throw new IOException(“error redirect url”, e);
}

    redirectUrl = response.encodeRedirectURL(redirectUrl);

    if (logger.isDebugEnabled()) {
        logger.debug("Redirecting to '" + redirectUrl + "'");
    }

    response.sendRedirect(redirectUrl);
}

private String calculateRedirectUrl(String contextPath, String url) {
    if (!UrlUtils.isAbsoluteUrl(url)) {
        if (contextRelative) {
            return url;
        } else {
            return contextPath + url;
        }
    }

    // Full URL, including http(s)://

    if (!contextRelative) {
        return url;
    }

    // Calculate the relative URL from the fully qualified URL, minus the scheme and base context.
    url = url.substring(url.indexOf("://") + 3); // strip off scheme
    url = url.substring(url.indexOf(contextPath) + contextPath.length());

    if (url.length() > 1 && url.charAt(0) == '/') {
        url = url.substring(1);
    }

    return url;
}

/\*\*
 \* If <tt>true</tt>, causes any redirection URLs to be calculated minus the protocol
 \* and context path (defaults to <tt>false</tt>).
 */
public void setContextRelative(boolean useRelativeContext) {
    this.contextRelative = useRelativeContext;
}

}

上面代码中UrlUtil为自己实现的一个Url处理工具,buildRedirectLink会对url做白名单校验。完了别忘了在servlet.xml的bean配置中加入safeRedirectStrategy。如下所示:

参考资料:

Spring Security Doc –> Appendix B. The Security Namespace