如何使用 Spring Security 和 jQuery 处理过期的会话?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/3339431/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to handle expired session using Spring Security and jQuery?
提问by viator
I'm using Spring Security and jQuery in my application. Main page uses loading content dynamically into tabs via AJAX. And all is OK, however sometimes I've got the login page inside my tab and if I type credentials I will be redirected to the content page without tabs.
我在我的应用程序中使用 Spring Security 和 jQuery。主页使用 AJAX 将内容动态加载到选项卡中。一切都很好,但是有时我的选项卡中有登录页面,如果我输入凭据,我将被重定向到没有选项卡的内容页面。
So I'd like to handle this situation. I know some of the people use AJAX authentication, but I'm not sure it's suitable for me because it looks quite complicated for me and my application doesn't allow any access without log into before. I would like to just write a global handler for all AJAX responses that will do window.location.reload()
if we need to authenticate. I think in this case it's better to get 401
error instead of standard login form because it's easier to handle.
所以我想处理这种情况。我知道有些人使用 AJAX 身份验证,但我不确定它是否适合我,因为它对我来说看起来很复杂,而且我的应用程序不允许任何未经登录的访问。我只想为所有window.location.reload()
需要身份验证的AJAX 响应编写一个全局处理程序。我认为在这种情况下,最好使用401
错误而不是标准登录表单,因为它更容易处理。
So,
所以,
1) Is it possible to write global error handlerfor all jQuery AJAX requests?
1) 是否可以为所有 jQuery AJAX 请求编写全局错误处理程序?
2) How can I customize behavior of Spring Security to send 401 error for AJAX requests but for regular requests to show standard login page as usual?
2) 如何自定义 Spring Security 的行为,以便为 AJAX 请求发送 401 错误,但对于常规请求以像往常一样显示标准登录页面?
3) May be you have more graceful solution? Please share it.
3)你可能有更优雅的解决方案吗?请分享。
Thanks.
谢谢。
回答by Matt Friedman
Here's an approach that I think is quite simple. It's a combination of approaches that I've observed on this site. I wrote a blog post about it: http://yoyar.com/blog/2012/06/dealing-with-the-spring-security-ajax-session-timeout-problem/
这是一种我认为非常简单的方法。这是我在本网站上观察到的方法的组合。我写了一篇关于它的博客文章:http: //yoyar.com/blog/2012/06/dealing-with-the-spring-security-ajax-session-timeout-problem/
The basic idea is to use an api url prefix (i.e. /api/secured) as suggested above along with an authentication entry point. It's simple and works.
基本思想是使用上面建议的 api url 前缀(即 /api/secured)以及身份验证入口点。这很简单而且有效。
Here's the authentication entry point:
这是身份验证入口点:
package com.yoyar.yaya.config;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
import javax.servlet.ServletException;
import javax.servlet.http.*;
import java.io.IOException;
public class AjaxAwareAuthenticationEntryPoint
extends LoginUrlAuthenticationEntryPoint {
public AjaxAwareAuthenticationEntryPoint(String loginUrl) {
super(loginUrl);
}
@Override
public void commence(
HttpServletRequest request,
HttpServletResponse response,
AuthenticationException authException)
throws IOException, ServletException {
boolean isAjax
= request.getRequestURI().startsWith("/api/secured");
if (isAjax) {
response.sendError(403, "Forbidden");
} else {
super.commence(request, response, authException);
}
}
}
And here's what goes in your spring context xml:
这是您的 spring 上下文 xml 中的内容:
<bean id="authenticationEntryPoint"
class="com.yoyar.yaya.config.AjaxAwareAuthenticationEntryPoint">
<constructor-arg name="loginUrl" value="/login"/>
</bean>
<security:http auto-config="true"
use-expressions="true"
entry-point-ref="authenticationEntryPoint">
<security:intercept-url pattern="/api/secured/**" access="hasRole('ROLE_USER')"/>
<security:intercept-url pattern="/login" access="permitAll"/>
<security:intercept-url pattern="/logout" access="permitAll"/>
<security:intercept-url pattern="/denied" access="hasRole('ROLE_USER')"/>
<security:intercept-url pattern="/" access="permitAll"/>
<security:form-login login-page="/login"
authentication-failure-url="/loginfailed"
default-target-url="/login/success"/>
<security:access-denied-handler error-page="/denied"/>
<security:logout invalidate-session="true"
logout-success-url="/logout/success"
logout-url="/logout"/>
</security:http>
回答by andro83
I used the following solution.
我使用了以下解决方案。
In spring security defined invalid session url
在 Spring Security 中定义了无效的会话 url
<security:session-management invalid-session-url="/invalidate.do"/>
For that page added following controller
对于该页面添加以下控制器
@Controller
public class InvalidateSession
{
/**
* This url gets invoked when spring security invalidates session (ie timeout).
* Specific content indicates ui layer that session has been invalidated and page should be redirected to logout.
*/
@RequestMapping(value = "invalidate.do", method = RequestMethod.GET)
@ResponseBody
public String invalidateSession() {
return "invalidSession";
}
}
And for ajax used ajaxSetup to handle all ajax requests:
对于 ajax,使用 ajaxSetup 来处理所有的 ajax 请求:
// Checks, if data indicates that session has been invalidated.
// If session is invalidated, page is redirected to logout
$.ajaxSetup({
complete: function(xhr, status) {
if (xhr.responseText == 'invalidSession') {
if ($("#colorbox").count > 0) {
$("#colorbox").destroy();
}
window.location = "logout";
}
}
});
回答by Guido
Take a look at http://forum.springsource.org/showthread.php?t=95881, I think the proposed solution is much clearer than other answers here:
看看http://forum.springsource.org/showthread.php?t=95881,我认为这里提出的解决方案比其他答案更清晰:
- Add a custom header in your jquery ajax calls (using 'beforeSend' hook). You can also use the "X-Requested-With" header that jQuery sends.
- Configure Spring Security to look for that header in the server side to return a HTTP 401 error code instead of taking the user to the login page.
- 在 jquery ajax 调用中添加自定义标头(使用“beforeSend”钩子)。您还可以使用 jQuery 发送的“X-Requested-With”标头。
- 配置 Spring Security 以在服务器端查找该标头以返回 HTTP 401 错误代码,而不是将用户带到登录页面。
回答by Hank
I just came up with a solution to this problem, but haven't tested it thoroughly. I am also using spring, spring security, and jQuery. First, from my login's controller, I set the status code to 401:
我刚刚想出了解决这个问题的方法,但还没有彻底测试过。我也在使用 spring、spring security 和 jQuery。首先,从我的登录控制器,我将状态代码设置为 401:
LoginController {
public ModelAndView loginHandler(HttpServletRequest request, HttpServletResponse response) {
...
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
...
return new ModelAndView("login", model);
}
In their onload() methods, all of my pages call a function in my global javascript file:
在它们的 onload() 方法中,我所有的页面都调用了全局 javascript 文件中的一个函数:
function initAjaxErrors() {
jQuery(window).ajaxError(function(event, xmlHttpRequest, ajaxOptions, thrownError) {
if (403 == xmlHttpRequest.status)
showMessage("Permission Denied");
else
showMessage("An error occurred: "+xmlHttpRequest.status+" "+xmlHttpRequest.statusText);
});
}
}
At this point, you can handle the 401 error any way you like. In one project, I have handled jQuery authentication by putting a jQuery dialog around an iframe containing a login form.
此时,您可以以任何您喜欢的方式处理 401 错误。在一个项目中,我通过在包含登录表单的 iframe 周围放置一个 jQuery 对话框来处理 jQuery 身份验证。
回答by Glen Little
Here's how I typically do it. On every AJAX call, check the result before using it.
这是我通常的做法。在每次 AJAX 调用中,使用前检查结果。
$.ajax({ type: 'GET',
url: GetRootUrl() + '/services/dosomething.ashx',
success: function (data) {
if (HasErrors(data)) return;
// process data returned...
},
error: function (xmlHttpRequest, textStatus) {
ShowStatusFailed(xmlHttpRequest);
}
});
And then the HasErrors()
function looks like this, and can be shared on all pages.
然后这个HasErrors()
功能就是这个样子,并且可以在所有页面上共享。
function HasErrors(data) {
// check for redirect to login page
if (data.search(/login\.aspx/i) != -1) {
top.location.href = GetRootUrl() + '/login.aspx?lo=TimedOut';
return true;
}
// check for IIS error page
if (data.search(/Internal Server Error/) != -1) {
ShowStatusFailed('Server Error.');
return true;
}
// check for our custom error handling page
if (data.search(/Error.aspx/) != -1) {
ShowStatusFailed('An error occurred on the server. The Technical Support Team has been provided with the error details.');
return true;
}
return false;
}
回答by MattC
So there are 2 problems here. 1) Spring security is working, but the response is coming back to the browser in an ajax call. 2) Spring security keeps track of the originally requested page so that it can redirect you to it AFTER you log in (unless you specify that you always want to use a certain page after logging in). In this case, the request was an Ajax string, so you will be re-directed to that string and that is what you will see in the browser.
所以这里有2个问题。1) Spring security 正在运行,但响应以 ajax 调用返回到浏览器。2) Spring security 会跟踪最初请求的页面,以便它可以在您登录后将您重定向到该页面(除非您指定在登录后始终要使用某个页面)。在这种情况下,请求是一个 Ajax 字符串,因此您将被重定向到该字符串,这就是您将在浏览器中看到的内容。
A simple solution is to detect the Ajax error, and if the request sent back is specific to your login page (Spring will send back the login page html, it will be the 'responseText' property of the request) detect it. Then just reload your current page, which will remove the user from the context of the Ajax call. Spring will then automatically send them to the login page. (I am using the default j_username, which is a string value that is unique to my login page).
一个简单的解决方案是检测 Ajax 错误,如果发回的请求特定于您的登录页面(Spring 会发回登录页面 html,它将是请求的 'responseText' 属性)检测它。然后只需重新加载当前页面,这将从 Ajax 调用的上下文中删除用户。然后 Spring 会自动将它们发送到登录页面。(我使用的是默认的 j_username,它是我的登录页面唯一的字符串值)。
$(document).ajaxError( function(event, request, settings, exception) {
if(String.prototype.indexOf.call(request.responseText, "j_username") != -1) {
window.location.reload(document.URL);
}
});
回答by Thomas Crook
When a timeout occurs, user is redirected to login page after any ajax action is triggered while session already cleared
发生超时时,在会话已清除的情况下触发任何 ajax 操作后,用户将被重定向到登录页面
security context :
安全上下文:
<http use-expressions="true" entry-point-ref="authenticationEntryPoint">
<logout invalidate-session="true" success-handler-ref="logoutSuccessBean" delete-cookies="JSESSIONID" />
<custom-filter position="CONCURRENT_SESSION_FILTER" ref="concurrencyFilter" />
<custom-filter position="FORM_LOGIN_FILTER" ref="authFilter" />
<session-management invalid-session-url="/logout.xhtml" session-authentication-strategy-ref="sas"/>
</http>
<beans:bean id="concurrencyFilter"
class="org.springframework.security.web.session.ConcurrentSessionFilter">
<beans:property name="sessionRegistry" ref="sessionRegistry" />
<beans:property name="expiredUrl" value="/logout.xhtml" />
</beans:bean>
<beans:bean id="authenticationEntryPoint" class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
<beans:property name="loginFormUrl" value="/login.xhtml" />
</beans:bean>
<beans:bean id="authFilter"
class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter">
<beans:property name="sessionAuthenticationStrategy" ref="sas" />
<beans:property name="authenticationManager" ref="authenticationManager" />
<beans:property name="authenticationSuccessHandler" ref="authenticationSuccessBean" />
<beans:property name="authenticationFailureHandler" ref="authenticationFailureBean" />
</beans:bean>
<beans:bean id="sas" class="org.springframework.security.web.authentication.session.ConcurrentSessionControlStrategy">
<beans:constructor-arg name="sessionRegistry" ref="sessionRegistry" />
<beans:property name="maximumSessions" value="1" />
<beans:property name="exceptionIfMaximumExceeded" value="1" />
</beans:bean>
Login listener :
登录监听器:
public class LoginListener implements PhaseListener {
@Override
public PhaseId getPhaseId() {
return PhaseId.RESTORE_VIEW;
}
@Override
public void beforePhase(PhaseEvent event) {
// do nothing
}
@Override
public void afterPhase(PhaseEvent event) {
FacesContext context = event.getFacesContext();
HttpServletRequest request = (HttpServletRequest) context.getExternalContext().getRequest();
String logoutURL = request.getContextPath() + "/logout.xhtml";
String loginURL = request.getContextPath() + "/login.xhtml";
if (logoutURL.equals(request.getRequestURI())) {
try {
context.getExternalContext().redirect(loginURL);
} catch (IOException e) {
throw new FacesException(e);
}
}
}
}
}