HTTP 摘要认证(Digest Access Authentication)是 HTTP 基本认证的一种替代方案,通过质询-响应机制避免密码明文传输。Spring Boot 本身不提供独立的 Digest 启动器,但可以通过 Spring Security 的 DigestAuthenticationFilter 和相关入口点快速整合。本文先拆解摘要认证的交互细节,再给出配置示例与验证过程。

一、摘要认证的握手流程与核心概念
Digest 认证的核心是一次质询-响应交互。客户端第一次请求受保护资源时不会携带 Authorization 头,服务端检测到未认证后返回 401 状态码,并在 WWW-Authenticate 头中附带 realm、nonce、qop、opaque 等参数。客户端拿到这些参数后开始计算摘要。摘要计算通常分两步:先计算 HA1,即 MD5 运算作用于 username、realm、password 的拼接结果;再计算 HA2,即 MD5 运算作用于 HTTP 方法、URI 的拼接结果。最终响应值由 HA1、nonce、nc、cnonce、qop、HA2 共同参与一次 MD5 计算得到。
服务端收到 Authorization 头后,会根据用户名查找用户存储中的密码,按照同样算法重新计算摘要并进行比对。因为密码没有直接出现在请求中,攻击者即使截获了 nonce 和响应值,也无法轻易还原出明文密码。nonce 由服务端生成,通常会设置有效时间,过期后客户端必须重新发起质询流程。qop 表示保护质量,常见值为 auth,表示只做身份认证不做内容完整性保护。opaque 是一个不透明字符串,服务端发出后客户端需要原样回传,用于关联同一认证流程。
这些参数虽然看起来多,但 Spring Security 已经封装了生成与解析逻辑。开发者需要关注的是如何正确配置 realm、nonce 有效期以及用户密码的存储格式,避免摘要比对失败。
二、Spring Security 中 Digest 认证的配置要点
在 Spring Boot 项目中整合 Digest 认证,主要依赖两个类:DigestAuthenticationEntryPoint 负责在未认证时返回 401 并生成 WWW-Authenticate 头,DigestAuthenticationFilter 负责解析客户端回传的 Authorization 头并执行认证。两者需要配合使用,并且过滤器必须添加到安全过滤链中合适的位置。
下面是一段可运行的 Java 配置。用户存储使用内存用户,密码保持明文,DigestAuthenticationFilter 的 passwordAlreadyEncoded 属性设置为 false,表示 UserDetailsService 返回的密码就是原始密码,Spring Security 会在内部计算 HA1。
@Configuration
@EnableWebSecurity
public class SecurityConfig {
private static final String DIGEST_REALM = "my-app-realm";
private static final String DIGEST_KEY = "change-this-secret-key";
@Bean
public UserDetailsService userDetailsService() {
UserDetails user = User.withUsername("digest-user")
.password("digest-pass")
.roles("USER")
.build();
return new InMemoryUserDetailsManager(user);
}
@Bean
public DigestAuthenticationEntryPoint digestAuthenticationEntryPoint() {
DigestAuthenticationEntryPoint entryPoint = new DigestAuthenticationEntryPoint();
entryPoint.setRealmName(DIGEST_REALM);
entryPoint.setKey(DIGEST_KEY);
entryPoint.setNonceValiditySeconds(300);
return entryPoint;
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
return configuration.getAuthenticationManager();
}
@Bean
public DigestAuthenticationFilter digestAuthenticationFilter(
DigestAuthenticationEntryPoint entryPoint,
UserDetailsService userDetailsService,
AuthenticationManager authenticationManager) {
DigestAuthenticationFilter filter = new DigestAuthenticationFilter();
filter.setAuthenticationEntryPoint(entryPoint);
filter.setUserDetailsService(userDetailsService);
filter.setAuthenticationManager(authenticationManager);
filter.setPasswordAlreadyEncoded(false);
return filter;
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http,
DigestAuthenticationFilter digestFilter,
DigestAuthenticationEntryPoint entryPoint) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/api/**").authenticated()
.anyRequest().permitAll()
.and()
.exceptionHandling().authenticationEntryPoint(entryPoint)
.and()
.addFilterBefore(digestFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
其中 DIGEST_KEY 用于服务端生成和校验 nonce,需要保密封装,不能使用默认值或可猜测的字符串。nonceValiditySeconds 设置了 nonce 的有效时间,单位是秒,示例中为 300 秒。实际项目中可以根据安全要求调整,但过短会导致用户在正常操作中频繁遇到 401 重新认证。
密码存储策略是一个容易踩坑的地方。如果 UserDetailsService 返回的是 BCrypt 或 PBKDF2 等编码后的密码,Digest 认证默认会把这些值直接当作原始密码参与 HA1 计算,结果必然无法匹配。此时可以将 passwordAlreadyEncoded 设置为 true,但前提是存储的密码必须是已经计算好的 MD5(username:realm:password) 十六进制字符串。换句话说,这个属性并不是支持任意加密算法,而是指定存储内容是否已经是 Digest 所需的 HA1 格式。
三、项目依赖与验证测试
Maven 项目中只需要引入 spring-boot-starter-security,它已经包含了 DigestAuthenticationFilter 所需的 Spring Security Web 与 Config 模块。依赖片段如下。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
添加配置类后,再提供一个受保护的接口,例如 /api/hello。启动应用后,使用 curl 的 Digest 模式进行验证。第一次请求会拿到 401 和 WWW-Authenticate 头,随后 curl 自动完成摘要计算并重试,最终返回 200。命令如下。
curl --digest -u digest-user:digest-pass http://127.0.0.1:8080/api/hello -v
从输出中可以观察到两次请求过程。第一次请求的响应头包含 WWW-Authenticate,其中 realm 为 my-app-realm,qop 为 auth,并带有 nonce 与 opaque 参数。第二次请求的 Authorization 头中会包含 Digest username、realm、nonce、uri、response 等字段。只要 realm 配置与客户端识别到的一致,并且 nonce 未过期,服务端就能成功认证。
如果接口返回 JSON 或文本,响应体会正常返回。若检查时仍看到 401,需要确认过滤器是否已经真正进入安全链,以及 UserDetailsService 中的用户名密码是否与 curl 指定的一致。
四、生产环境注意事项与常见问题
Digest 认证解决了 Basic 认证中密码明文传输的问题,但它本身并不加密请求和响应内容。攻击者仍然可以截获请求中的摘要值进行重放,虽然 nonce、nc、cnonce 能在一定程度上缓解重放,但无法提供与 HTTPS 同等级的保护。生产环境中应当将 Digest 认证与外层 TLS 结合使用,避免敏感业务数据泄露。
常见问题之一是 realm 不匹配。客户端计算 HA1 时使用了服务端返回的 realm,如果服务端配置的 realm 与客户端收到的 realm 不一致,或者反向代理修改了响应头,摘要就会计算错误。另一个问题是 nonce 过期,特别是在长时间未操作后,客户端携带旧 nonce 请求会直接收到新的 401。部分客户端能够自动重新发起质询,但并非所有浏览器都完整支持 Digest 认证,因此在面向普通用户的项目中,这种认证方式更适合内部系统或设备接口。
过滤器顺序也同样重要。DigestAuthenticationFilter 需要在校验请求前处理 Authorization 头,如果被放在别的认证过滤器之后,可能无法触发质询流程,导致认证失败或绕过。实际调试时可以在日志中开启 Spring Security 的 DEBUG 级别,观察过滤器链的加载顺序与认证分支。
总体来看,Spring Boot 整合 Digest 认证并不复杂,核心在于理解质询-响应流程,并正确配置 entryPoint、filter 与 passwordAlreadyEncoded 三个关键点。对于已经使用 Basic 认证且暂时无法迁移到 OAuth2 或 JWT 的系统,Digest 加 HTTPS 是一种可选的增强方案。
Spring BootDigest 认证摘要认证修改时间:2026-08-23 07:14:05