I'm currently creating a simple app for a school project, Spring Boot backend and AngularJS frontend, but have a problem with security that I can't seem to solve.
Logging in works perfectly, but when I enter a wrong password the default login popup shows up, which is kind of annoying. I've tried the annotation 'BasicWebSecurity' and putting httpBassic on disabled, but with no result (meaning, that the login procedure doesn't work at all anymore).
My security class:
package be.italent.security;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.csrf.CsrfFilter;
import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.security.web.csrf.CsrfTokenRepository;
import org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.WebUtils;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Autowired
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
@Override
public void configure(WebSecurity web){
web.ignoring()
.antMatchers("/scripts/**/*.{js,html}")
.antMatchers("/views/about.html")
.antMatchers("/views/detail.html")
.antMatchers("/views/home.html")
.antMatchers("/views/login.html")
.antMatchers("/bower_components/**")
.antMatchers("/resources/*.json");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic()
.and()
.authorizeRequests()
.antMatchers("/user", "/index.html", "/", "/projects/listHome", "/projects/{id}", "/categories", "/login").permitAll().anyRequest()
.authenticated()
.and()
.csrf().csrfTokenRepository(csrfTokenRepository())
.and()
.addFilterAfter(csrfHeaderFilter(), CsrfFilter.class).formLogin();
}
private Filter csrfHeaderFilter() {
return new OncePerRequestFilter() {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
CsrfToken csrf = (CsrfToken) request.getAttribute(CsrfToken.class
.getName());
if (csrf != null) {
Cookie cookie = WebUtils.getCookie(request, "XSRF-TOKEN");
String token = csrf.getToken();
if (cookie == null || token != null
&& !token.equals(cookie.getValue())) {
cookie = new Cookie("XSRF-TOKEN", token);
cookie.setPath("/");
response.addCookie(cookie);
}
}
filterChain.doFilter(request, response);
}
};
}
private CsrfTokenRepository csrfTokenRepository() {
HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
repository.setHeaderName("X-XSRF-TOKEN");
return repository;
}
}
Does anybody have an idea on how to prevent this popup from showing without breaking the rest?
solution
Added this to my Angular config:
myAngularApp.config(['$httpProvider',
function ($httpProvider) {
$httpProvider.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
}
]);
Let's start with your problem
It is not a "Spring Boot security popup" its a browser popup that shows up, if the response of your Spring Boot app contains the following header:
WWW-Authenticate: Basic
In your security configuration a .formLogin()
shows up. This should not be required. Though you want to authenticate through a form in your AngularJS application, your frontend is an independent javascript client, which should use httpBasic instead of form login.
How your security config could look like
I removed the .formLogin()
:
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.httpBasic()
.and()
.authorizeRequests()
.antMatchers("/user", "/index.html", "/", "/projects/listHome", "/projects/{id}", "/categories", "/login").permitAll().anyRequest()
.authenticated()
.and()
.csrf().csrfTokenRepository(csrfTokenRepository())
.and()
.addFilterAfter(csrfHeaderFilter(), CsrfFilter.class);
}
How to deal with the browser popup
As previously mentioned the popup shows up if the response of your Spring Boot app contains the header WWW-Authenticate: Basic
. This should not be disabled for all requests in your Spring Boot app, since it allows you to explore the api in your browser very easily.
Spring Security has a default configuration that allows you to tell the Spring Boot app within each request not to add this header in the response. This is done by setting the following header to your request:
X-Requested-With: XMLHttpRequest
How to add this header to every request made by your AngularJS app
You can just add a default header in the app config like that:
yourAngularApp.config(['$httpProvider',
function ($httpProvider) {
$httpProvider.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
}
]);
The backend will now respond with a 401-response that you have to handle by your angular app (by an interceptor for example).
If you need an example how to do this you could have a look at my shopping list app. Its done with spring boot and angular js.