从 Spring Boot 中的基本身份验证中删除 WWW-authenticate 标头

Posted

技术标签:

【中文标题】从 Spring Boot 中的基本身份验证中删除 WWW-authenticate 标头【英文标题】:Remove WWW-authenticate header from Basic authentication in Spring Boot 【发布时间】:2016-03-21 15:44:29 【问题描述】:

我正在使用 SpringBoot 设计一个 REST API。与此同时,我正在构建一个使用该 API 的 SPA。

出于安全考虑,我选择了易于设置的基本身份验证。我现在正面临 401 挑战问题。当我的 SPA 向我的 API 发出请求时,如果身份验证失败,浏览器会显示我想要摆脱的经典登录弹出窗口。

我阅读了here 和there,当 401 响应还包含一个值为“基本”的 WWW-authenticate 标头时,浏览器会显示此弹出窗口。

所以要删除它,根据 Spring Security,我将提供我自己的 AuthenticationEntryPoint。有点像 Spring Boot 默认情况下的it's done。我试过这个但它不起作用,我在运行时得到一个堆栈。有什么想法吗?

这是我的代码

@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();

     

这是我在使用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

【问题讨论】:

【参考方案1】:

发现问题,我在问题中发布的堆栈之前发送了另一个堆栈。堆栈抱怨我的 AuthenticationEntryPoint 缺少领域名称。为了记录,这里是一个工作示例:

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());
    

【讨论】:

另一种解决方案不是扩展BasicAuthenticationEntryPoint,而是实现AuthenticationEntryPoint。他们在BasicAuthenticationEntryPoint 中没有做任何特别的事情,请参阅git.io/vS31k。【参考方案2】:

如果有人遇到类似的问题/挑战,我会发布此信息。

在您的 AuthenticationServerConfigurerAdapter 类中允许表单身份验证,如下所示:

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

这将允许您在表单中发送 client_id 和 client_secret。另外,请确保您提交的表单带有content-type:application/x-www-form-urlencoded 标头。

【讨论】:

【参考方案3】:

有一个更简单的选择。只需将 Http401AuthenticationEntryPoint 添加到您的 httpSecurity。

http.exceptionHandling()
    .authenticationEntryPoint(new Http401AuthenticationEntryPoint("FormBased"));

【讨论】:

你有xml配置的例子吗?

以上是关于从 Spring Boot 中的基本身份验证中删除 WWW-authenticate 标头的主要内容,如果未能解决你的问题,请参考以下文章

Spring Boot 应用程序中的 LDAP 身份验证

Spring Boot webservice (REST) - 如何将 JUnit 5 测试从基本身份验证更改为 OAuth2 (Keycloak)

Spring Boot with Spring Boot:将基本身份验证与JWT令牌身份验证相结合[复制]

如何在 Spring Boot 中使用 RESTful 和基本身份验证

在 Spring Security(spring-boot 项目)中使用 ldap 凭据进行 Http 基本身份验证以保护休息服务调用

Angular 基本身份验证 Spring Boot