有一个场景需要在登录或退出时传递一个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