本文转自我的博客,转载请申明地址:http://www.heartlifes.com/archives/7/
背景:###
1.项目中使用耶鲁的cas做单点登录。 2.使用spring-oauth包实现oauth2服务 3.使用spring-cas做spring-security及cas的集成
现象:###
开发报了个bug,大致流程就是 系统调用/oauth/authorize接口,被spring-security拦截进入cas登录界面后,用户输入用户名密码做登录,登录成功后,浏览器跳转到授权界面,这时候用户点了回退按钮(这个用户好无聊),进入了404的错误界面
原因:###
撸了一把spring的源码后发现,spring-security的默认拦截器堆栈中,有个拦截器叫RequestCacheAwareFilter 官方文档解释说这个拦截器是用来还原被登录操作打断的用户界面跳转用的,什么意思呢,就是说: 1.你的系统用spring-security提供的登录方法 2.用户点了你系统的某个链接xxx,这个xxx在spring-security中是个需要被安全控制的连接 3.spring-security拦截这个xxx链接,并且跳到了登录界面 4.用户登录完了后,RequestCacheAwareFilter这个拦截器,再把xxx返回给浏览器
但是,我们这里是集成了cas做登录的,那么,悲剧发生了。
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest wrappedSavedRequest =
requestCache.getMatchingRequest((HttpServletRequest)request, (HttpServletResponse)response);
chain.doFilter(wrappedSavedRequest == null ? request : wrappedSavedRequest, response);
}</code></pre>
这个filter会去调用requestCache的一个方法,requestCache是spring中用来缓存浏览器request请求的缓存存储。这个方法如下所示:
<pre><code>public HttpServletRequest getMatchingRequest(HttpServletRequest request,
HttpServletResponse response) {
DefaultSavedRequest saved = (DefaultSavedRequest) getRequest(request,
response);
if (saved == null) {
return null;
}
if (!saved.doesRequestMatch(request, portResolver)) {
logger.debug("saved request doesn't match");
return null;
}
removeRequest(request, response);
return new SavedRequestAwareWrapper(saved, request);
}
有一行叫remveRequest,这行代码会把存在requestCache中的请求路径给清除。而这个请求路径就是用户在按回退按钮时,浏览器要跳转的路径。具体看以下逻辑:
当用户按回退按钮时,其实是回到了spring-cas提供的j_spring_cas_security_check这个url里,最终会到<pre><code>SavedRequestAwareAuthenticationSuccessHandler</code></pre>中判断该跳转到哪个页面:
SavedRequest savedRequest = requestCache.getRequest(request, response);
if (savedRequest == null) {
super.onAuthenticationSuccess(request, response, authentication);
return;
}
String targetUrlParameter = getTargetUrlParameter();
if (isAlwaysUseDefaultTargetUrl() || (targetUrlParameter != null && StringUtils.hasText(request.getParameter(targetUrlParameter)))) {
requestCache.removeRequest(request, response);
super.onAuthenticationSuccess(request, response, authentication);
return;
}
clearAuthenticationAttributes(request);
// Use the DefaultSavedRequest URL
String targetUrl = savedRequest.getRedirectUrl();
logger.debug("Redirecting to DefaultSavedRequest Url: " + targetUrl);
getRedirectStrategy().sendRedirect(request, response, targetUrl);
可以看到,如果requestCache的请求路径是空,那么就会跳转到默认路径,这个路径如果你没有在spring中配置的话,默认就是你应用访问上下文的跟路径,如果跟路径下没有首页的话,那就自然报404错误了。
解决:###
没什么好的解决方式,目前我们只是粗暴的将RequestCache的实现类HttpSessionRequestCache重写了一把,加入一段判断逻辑:
public void removeRequest(HttpServletRequest currentRequest,
HttpServletResponse response) {
HttpSession session = currentRequest.getSession(false);
if (session != null) {
logger.debug("Removing DefaultSavedRequest from session if present");
Object obj = session.getAttribute(SAVED_REQUEST);
if (obj instanceof DefaultSavedRequest) {
DefaultSavedRequest savedRequest = (DefaultSavedRequest) obj;
String requestURI = savedRequest.getRequestURI();
System.out.println("requestCache....requestURI...."
+ savedRequest.getRequestURI());
if (requestURI.contains("/api/oauth/")) {
return;
}
}
session.removeAttribute(SAVED_REQUEST);
}
}