11

I have an app build with Spring MVC and secured with Spring security, a bunch of the controllers are JSON rest services that are all protected. I'm using LoginUrlAuthenticationEntryPoint to detect AJAX requests and send 403 ERROR CODE if session timeout occurs - all other requests just get redirected back to login page.

Below is the Spring Security XML snippet and the authenticationEntryPoint java class.

The issue is that the first AJAX Request after session timeout, Spring redirects to login page and the returns login page HTML, if I attempt to do AJAX request again (after redirect happens) the authenticationEntryPoint executes and HTTP Error Code 403 is returned. I have attempted the same thing using this mechanism http://distigme.wordpress.com/2012/11/01/ajax-and-spring-security-form-based-login/ and the same exact thing happens (on first AJAX request a redirect occurs, all subsequent AJAX requests HTTP 403 is returned). I do not want to get Redirect to login page for AJAX requests where session is timed out.

Any ideas?

<beans:bean id="authenticationEntryPoint"  class="mojo.ocs.web.AjaxAwareAuthenticationEntryPoint">
    <beans:constructor-arg name="loginUrl" value="/login"/>
</beans:bean>
<!-- ENTRY POINT REF IMPLEMENTATION -->
<http auto-config="true" use-expressions="true" access-denied-page="/accessdenied" entry-point-ref="authenticationEntryPoint">
    <intercept-url pattern="/login" access="isAnonymous()"/>
    <intercept-url pattern="/loginfailed" access="isAnonymous()"/>
    <intercept-url pattern="/welcome" access="isAuthenticated()" />
    <intercept-url pattern="/" access="isAuthenticated()" />
    <intercept-url pattern="/private_res/**" access="isAuthenticated()" />
    <intercept-url pattern="/tne/**" access="isAuthenticated()" />
    <intercept-url pattern="/team_reports/**" access="isAuthenticated()" />
    <form-login login-page="/login" default-target-url="/welcome" always-use-default-target="true" authentication-failure-url="/loginfailed" />
    <logout delete-cookies="JSESSIONID"  logout-success-url="/logout" invalidate-session="true"/>
    <session-management invalid-session-url="/login" />
</http>

Here is the LoginAuthenticationEntryPoint:

public class AjaxAwareAuthenticationEntryPoint extends LoginUrlAuthenticationEntryPoint     
{
    public AjaxAwareAuthenticationEntryPoint(String loginUrl) {
        super(loginUrl);
    }

    @Override
    public void commence(
        HttpServletRequest request,
        HttpServletResponse response,
        AuthenticationException authException)
        throws IOException, ServletException {
        String ajaxHeader = ((HttpServletRequest) request).getHeader("X-Requested-With");
        boolean isAjax = "XMLHttpRequest".equals(ajaxHeader);
        if (isAjax) {
            response.sendError(HttpServletResponse.SC_FORBIDDEN, "Ajax REquest Denied (Session Expired)");
        } else {
            super.commence(request, response, authException);
        }
    }
}
alessandro ferrucci
  • 1,261
  • 2
  • 24
  • 48

3 Answers3

8

Confirming that this works perfectly fine also with Spring Boot combined with Spring security in the programmatic way to setup security without any required XML, for example:

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
        .authorizeRequests()
            .antMatchers("/admin**").hasRole("ADMIN")
            // everything else
            .anyRequest().fullyAuthenticated()
        .and()
            .exceptionHandling().authenticationEntryPoint(new AjaxAwareAuthenticationEntryPoint("/login"));
}
ngeek
  • 7,733
  • 11
  • 36
  • 42
  • This is working fine for me. Also I'd like to add a link to a gist with a complete example: https://gist.github.com/jecyhw/f9f65185dd5d4b284ce4e755637475c7 – sebasira Aug 08 '21 at 14:53
0

I realise this is from quite some time ago, but I want to put this here to see if it helps someone else.

I followed the same idea in http://distigme.wordpress.com/2012/11/01/ajax-and-spring-security-form-based-login/ and had the same issue in that the first returned content was the login page, and the next was a HTTP 403.

I think this is the part of Spring where we hit the split between Spring XML config doing everything, or we write a bunch of code to overload what it can do for us. I prefer to do as much as I can in the XML config.

My solution was to have the XML configuration throwing a 403 error as what the blog has. I didn't write a Matching class because my workflow required going back to the first page, so I don't use the org.springframework.security.web.savedrequest.HttpSessionRequestCache.

<bean id="loginUrlAuthenticationEntryPoint" class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
    <constructor-arg name="loginFormUrl" value="/index.html" />
</bean>

<bean id="authenticationEntryPoint" class="org.springframework.security.web.authentication.DelegatingAuthenticationEntryPoint">
    <constructor-arg>
        <map>
            <entry key="!hasHeader('X-Requested-With','XMLHttpRequest')" value-ref="loginUrlAuthenticationEntryPoint" />
        </map>
    </constructor-arg>
    <property name="defaultEntryPoint">
        <bean class="org.springframework.security.web.authentication.Http403ForbiddenEntryPoint" />
    </property>
</bean>

I'm a big fan of nesting beans if I don't need them elsewhere. In my $.ajax call I put

dataType: 'json'

to make sure that if the returned content is not JSON (e.g. the login page) then the error function is called. This will also catch a 403 error as well.

error: function (xhr, textStatus, errorThrown) {
    if (xhr.status == 403 || textStatus == 'parsererror' && xhr.responseText.match('rememberMe').length > 0) {
        alert('Your session has timed out.');
        window.location = '<c:url value="/index.html" />';
    } else
        alert('Something went wrong. ' + xhr.status + ': ' + errorThrown);

}

I'm searching for the rememberMe text to make sure it's the login page. I don't expect that on any other page.

Jim Richards
  • 708
  • 1
  • 7
  • 19
-1

I fixed this issue by implementing by own custom filter, placing it before ANONYMOUS_FILTER and return 403 if the Spring principal doesn't exist.

alessandro ferrucci
  • 1,261
  • 2
  • 24
  • 48