Spring Boot security shows Http-Basic-Auth popup after failed login

▼魔方 西西 提交于 2021-02-15 13:38:07

问题


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';
  }
]);

回答1:


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.




回答2:


As Yannic Klem already said this is happening because of the header

WWW-Authenticate: Basic

But there is a way in spring to turn it off, and it's really simple. In your configuration just add:

.httpBasic()
.authenticationEntryPoint(authenticationEntryPoint)

and since authenticationEntryPoint is not defined yet, autowire it at the beginning:

@Autowired private MyBasicAuthenticationEntryPoint authenticationEntryPoint;

And now create MyBasicAuthenticationEntryPoint.class and paste the following code:

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint;
import org.springframework.stereotype.Component;

@Component
public class MyBasicAuthenticationEntryPoint extends BasicAuthenticationEntryPoint {

/**
 * Used to make customizable error messages and codes when login fails
 */
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authEx) 
  throws IOException, ServletException {
    response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
    PrintWriter writer = response.getWriter();
    writer.println("HTTP Status 401 - " + authEx.getMessage());
}

@Override
public void afterPropertiesSet() throws Exception {
    setRealmName("YOUR REALM");
    super.afterPropertiesSet();
}
}

Now your app wont send WWW-Authenticate: Basic header, because of that pop-up windows will not show, and there is no need to mess with headers in Angular.




回答3:


As already explained above, the problem is in the header of response that is set with the values "WWW-Authenticate: Basic".

Another solution that can solve this is to implement the AuthenticationEntryPoint interface (directly) without placing these values in the header:

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    //(....)

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http
        .csrf()
            .disable()
        .authorizeRequests()
            .antMatchers("/*.css","/*.js","/*.jsp").permitAll()
            .antMatchers("/app/**").permitAll()
            .antMatchers("/login").permitAll()

            .anyRequest().authenticated()

            .and()
                .formLogin()
                .loginPage("/login")
                .loginProcessingUrl("/j_spring_security_check")
                .defaultSuccessUrl("/", true)
                .failureUrl("/login?error=true")
                .usernameParameter("username")
                .passwordParameter("password")
                .permitAll()
            .and()
                .logout()
                .logoutUrl("/logout")
                .logoutSuccessUrl("/login")
                .deleteCookies("JSESSIONID")
                .clearAuthentication(true)
                .invalidateHttpSession(true)
            .and()
                .exceptionHandling()
                .accessDeniedPage("/view/error/forbidden.jsp")
            .and()
                .httpBasic()
                .authenticationEntryPoint(new AuthenticationEntryPoint(){ //<< implementing this interface
                    @Override
                    public void commence(HttpServletRequest request, HttpServletResponse response,
                        AuthenticationException authException) throws IOException, ServletException {
                            //>>> response.addHeader("WWW-Authenticate", "Basic realm=\"" + realmName + "\""); <<< (((REMOVED)))
                            response.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase());
                    }
                });
    }

    //(....)
}


来源:https://stackoverflow.com/questions/37763186/spring-boot-security-shows-http-basic-auth-popup-after-failed-login

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!