Sometime, you have to host the API inside the same project where you serve your HTML pages
Or probably a more usual case which may happen when you use JQuery with a framework like Spring MVC, the web pages are protected with a login form but you have also some URLs used by Ajax requests coming from JQuery components (such as datable) and the authentication cannot be handled in the same way.
When a user connects to the web site, he will be redirected (with a 302 http code) to the login form.
But, for example, when the user refreshes the page after the session expired, the JQuery components tries to connect through Ajax, the 302 code is returned, intercepted by the browser which follow the 302 to the login page, and then, the JQuery component receiving HTML form content in the response is not able to process it and fails with error massage.
Here the JQuery datatable code:
$('#example').DataTable( { "processing": true, "serverSide": true, "ajax": { url: "api/data/", // json datasource type: "get", error: function (jqXHR, textStatus, errorThrown) { if (jqXHR.status == 401) { document.location.href = "login"; } else { alert("Cannot get data from server, refresh the page or call support if error persists\n: (" + JSON.stringify(jqXHR) + "\ntextStatus: " + textStatus + "errorThrown: " + errorThrown + ")") } } } } );
To make it behaves properly, the Ajax request should simply return a 401 code to give a chance to the JQuery component to redirect to the login page.
To implement this behaviour, Spring needs 2 security configurations:
- One for the web pages
- One for the URLs reached through Ajax calls
Let’s start with the security configuration for web pages, it is set in a first implementation of WebSecurityConfigurerAdapter
@Configuration @Slf4j @Order(100) public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(final HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers( "/login", "/css/**") .permitAll() .anyRequest().authenticated() .and() .formLogin() .and() .logout() .logoutRequestMatcher(new AntPathRequestMatcher("/logout")); log.info("Web security enabled"); } [...] }
Here, we need to pay attention to the pages which should be publicly accessible, in my case, it means only login and css, the rest of the application must be protected.
Now, we need to take care of the API endpoints to allow the right redirection when the user session has expired (and easy way to test it is to rely on browser dev tools which often give you the opportunity to reset your session cookie).
@EnableWebSecurity @Configuration @Slf4j @Order(99) public class ApiSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(final HttpSecurity http) throws Exception { http .csrf().disable() .requestMatchers() .antMatchers("/api/**") .and() .authorizeRequests() .anyRequest().authenticated() .and() .httpBasic(); log.info("Api security enabled"); } }
The configuration of the protected url is done in a same way as before and we use basic-auth instead of form-login to send a 401 to the browser.
Something important to notice is the order annotation which makes this configuration loaded before the web security configuration to ensure that it will be executed in first. If we try to connect to “api/data” without being authenticated, we need to be sure that we will receive a 401 response and not the 302 redirection as it is the case with the web security configuration.
the code I showed can be found in this repository.