6

I am designing a REST API using SpringBoot. In parallel, I am building an SPA that uses that API.

For security, I chose Basic Auth, which is easy to set up. I am facing now the 401 challenge issue. When my SPA makes requests to my API, if the auth fails, the browser displays the classical login popup which I want to get rid of.

I read here and there, that browser display this popup when the 401 response contains also a WWW-authenticate header with value 'Basic'.

So to remove this, according to Spring Security, I shall provide my own AuthenticationEntryPoint. A bit like it's done by default by Spring Boot. I tried this but it does not work, I get a stack at runtime. Any ideas?

Here is my code

@Order(SecurityProperties.BASIC_AUTH_ORDER)
@Configuration
public class CustomWebSecurityConfigurerAdapter extends  WebSecurityConfigurerAdapter {

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

       AuthenticationEntryPoint entryPoint = new CustomAuthenticationEntryPoint();

        http
            .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
            .authorizeRequests()
            .regexMatchers("/api/.*").authenticated().and()
            .httpBasic().authenticationEntryPoint(entryPoint).and()
            .exceptionHandling().authenticationEntryPoint(entryPoint).and()
            .csrf().disable();

     }
}

And this is the stack I get when running with mvn spring-boot:run

java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at org.springframework.boot.maven.AbstractRunMojo$LaunchRunner.run(AbstractRunMojo.java:467)
    at java.lang.Thread.run(Thread.java:745)

Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'springSecurityFilterChain' defined in class path resource [org/springframework/security/config/annotation/web/configuration/WebSecurityConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [javax.servlet.Filter]: Factory method 'springSecurityFilterChain' threw exception; nested exception is org.springframework.security.config.annotation.AlreadyBuiltException: This object has already been built
    at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:599)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1123)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1018)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:510)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:482)
    at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:296)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:772)
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:838)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:537)
    at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.refresh(EmbeddedWebApplicationContext.java:118)
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:752)
    at org.springframework.boot.SpringApplication.doRun(SpringApplication.java:347)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:295)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1112)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1101)
    at org.htulipe.WishlistApplication.main(WishlistApplication.java:44)
... 6 more

Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [javax.servlet.Filter]: Factory method 'springSecurityFilterChain' threw exception; nested exception is org.springframework.security.config.annotation.AlreadyBuiltException: This object has already been built
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:189)
    at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:588)
... 26 more

Caused by: org.springframework.security.config.annotation.AlreadyBuiltException: This object has already been built
at org.springframework.security.config.annotation.AbstractSecurityBuilder.build(AbstractSecurityBuilder.java:44)
at org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration.springSecurityFilterChain(WebSecurityConfiguration.java:105)
at org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration$$EnhancerBySpringCGLIB$$7d149fcb.CGLIB$springSecurityFilterChain$6(<generated>)
at org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration$$EnhancerBySpringCGLIB$$7d149fcb$$FastClassBySpringCGLIB$$208d4287.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:228)
at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:318)
at org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration$$EnhancerBySpringCGLIB$$7d149fcb.springSecurityFilterChain(<generated>)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:162)
... 27 more
Community
  • 1
  • 1
htulipe
  • 1,575
  • 1
  • 10
  • 22

3 Answers3

5

Found the issue, I had another stack sent before the one I posted in the question. The stack was complaining that my AuthenticationEntryPoint was missing a Realm name. For the record here is a working example:

public class CustomAuthenticationEntryPoint extends BasicAuthenticationEntryPoint{

    public CustomAuthenticationEntryPoint() {
        this.setRealmName("Yolo");
    }

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException)
        throws IOException, ServletException {
        response.setHeader("WWW-Authenticate", "FormBased");
        response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException.getMessage());
    }
}
htulipe
  • 1,575
  • 1
  • 10
  • 22
  • 1
    Another solution is not to extend `BasicAuthenticationEntryPoint`, but to implement `AuthenticationEntryPoint`. They don't do anything special in `BasicAuthenticationEntryPoint`, see https://git.io/vS31k. – Nick Russler Mar 29 '17 at 12:55
5

There's an easier option. Just add a Http401AuthenticationEntryPoint to your httpSecurity.

http.exceptionHandling()
    .authenticationEntryPoint(new Http401AuthenticationEntryPoint("FormBased"));
mpprdev
  • 1,273
  • 1
  • 15
  • 26
  • do you have an example for xml config? – tibi Nov 03 '17 at 09:58
  • Be careful with Spring Boot 2+, the class `org.springframework.boot.autoconfigure.security.Http401AuthenticationEntryPoint` has been replaced by `org.springframework.security.web.server.authentication.HttpStatusServerEntryPoint`. Source : https://stackoverflow.com/a/49241557/1655155 – Bludwarf Mar 29 '22 at 09:28
1

I am posting this in case someone has a similar problem/challenge.

Allow form authentication in your AuthenticationServerConfigurerAdapter class shown bellow:

 @Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
    oauthServer.checkTokenAccess("isAuthenticated()")
    .allowFormAuthenticationForClients();
}

This will allow you to send client_id and client_secret in the form. Also, make sure your form is submitted with content-type:application/x-www-form-urlencoded header.

MUNGAI NJOROGE
  • 1,144
  • 1
  • 12
  • 25