1. 概述

我们将讲解如何使用Spring搭建,配置以及自定义Basic身份认证。 本教程是建立在 Spring MVC示例之上,使用Spring Security提供的Basic Auth机制保护MVC应用程序。

2. Spring Security配置

可以使用Java和XML两种方式配置Spring Security。

Java代码方式:

@Configuration
@EnableWebSecurity
public class CustomWebSecurityConfigurerAdapter {

    @Autowired private RestAuthenticationEntryPoint authenticationEntryPoint;

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth
          .inMemoryAuthentication()
          .withUser("user1")
          .password(passwordEncoder().encode("user1Pass"))
          .authorities("ROLE_USER");
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests(expressionInterceptUrlRegistry ->
                        expressionInterceptUrlRegistry.requestMatchers("/securityNone").permitAll()
                                .anyRequest().authenticated())
            .httpBasic(httpSecurityHttpBasicConfigurer -> httpSecurityHttpBasicConfigurer.authenticationEntryPoint(authenticationEntryPoint));
        http.addFilterAfter(new CustomFilter(), BasicAuthenticationFilter.class);
        return http.build();
    }
    
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

SecurityFilterChain bean中,我们使用httpBasic()声明使用Basic认证。

XML配置方式如下:

    <http pattern="/securityNone" security="none"/>
    <http use-expressions="true">
        <intercept-url pattern="/**" access="isAuthenticated()" />
        <http-basic />
    </http>
    
    <authentication-manager>
        <authentication-provider>
            <user-service>
                <user name="user1" password="{noop}user1Pass" authorities="ROLE_USER" />
            </user-service>
        </authentication-provider>
    </authentication-manager>

这里最重要的配置项是<http>元素下<http-basic> – 这对我们实现Basic Auth来说足够了。 Authentication Manager不是本文关注的重点,所以我们使用了内存版本Manager,硬编码用户信息和密码。

如何配置web.xml以启用我们的Spring Security,已经在Spring注销教程中讲过。

3. 消费受保护后的应用

我们将使用curl命令行工具发送请求。

首先,我们尝试在未加任何认证参数的情况下请求/homepage.html

curl -i http://localhost:8080/spring-security-rest-basic-auth/api/foos/1

服务端响应一个401未授权应答码,并提供一个认证域

HTTP/1.1 401 Unauthorized
Server: Apache-Coyote/1.1
Set-Cookie: JSESSIONID=E5A8D3C16B65A0A007CFAACAEEE6916B; Path=/spring-security-mvc-basic-auth/; HttpOnly
WWW-Authenticate: Basic realm="Spring Security Application"
Content-Type: text/html;charset=utf-8
Content-Length: 1061
Date: Wed, 29 May 2013 15:14:08 GMT

接到应答后,浏览器会弹出一个简单的对话框,提示我们输入账户和密码。因为我们使用的是curl所以不会弹出对话框。

我们再次发送请求 ,但这次带上认证凭据:

curl -i --user user1:user1Pass 
  http://localhost:8080/spring-security-rest-basic-auth/api/foos/1

现在,服务端成功响应一个200应答码,和一个Cookie:

HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Set-Cookie: JSESSIONID=301225C7AE7C74B0892887389996785D; Path=/spring-security-mvc-basic-auth/; HttpOnly
Content-Type: text/html;charset=ISO-8859-1
Content-Language: en-US
Content-Length: 90
Date: Wed, 29 May 2013 15:19:38 GMT

通过浏览器,我们可以正常使用该应用,唯一区别是不需要我们设计一个登录页面,因为所有浏览器都支持Basic身份验证,会自动弹出一个对话框并提示用户输入账号和密码。

4. 进阶 — 自定义AuthenticationEntryPoint(认证入口点)

默认情况下, Spring Security 内置的BasicAuthenticationEntryPoint返回给客户端的是一个完整的401 Unauthorized 页面。这种HTML格式在浏览器中能很好的展现错误信息,但如果我们提供的是 REST API,那么返回 JSON 格式会更好点。

为了解决此问题 – 我们可以重写AuthenticationEntryPoint:

<http-basic entry-point-ref="myBasicAuthenticationEntryPoint" />

MyBasicAuthenticationEntryPoint定义如下:

    @Component
    public class MyBasicAuthenticationEntryPoint extends BasicAuthenticationEntryPoint {
    
        @Override
        public void commence(
          HttpServletRequest request, HttpServletResponse response, AuthenticationException authEx) 
          throws IOException, ServletException {
            response.addHeader("WWW-Authenticate", "Basic realm="" + getRealmName() + """);
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            PrintWriter writer = response.getWriter();
            writer.println("HTTP Status 401 - " + authEx.getMessage());
        }
    
        @Override
        public void afterPropertiesSet() throws Exception {
            setRealmName("Baeldung");
            super.afterPropertiesSet();
        }
    }

通过直接对HTTP Response写入,我们现在能完全控制返回的格式。

5. Maven依赖

Spring Security 与 Maven 一文中,我们已经讨论过Spring Security依赖问题,我们需添加spring-security-webspring-security-config

6. 总结

本例中,我们使用Spring Security 和 Basic Auth对一个MVC应用进行安全保护。 我们讨论了XML配置方式并且使用简单的curl命令行来访问该应用。最后我们对具体的错误信息格式进行了处理 – 从标准的HTML 错误页面切换到自定义的文本或json格式。

本文完整代码实现存放在GitHub上 – 这是一个基于Maven的项目, 所以很容易导入和运行。

当项目运行后, 访问:http://localhost:8080/spring-security-rest-basic-auth/api/foos/1