如何在Spring Security过滤器中程序化验证请求参数
在Spring Security过滤器中实现请求体的程序化验证
核心步骤
- 注入标准验证器:从Spring容器获取
javax.validation.Validator实例,这是JSR-380(Bean Validation)的标准实现。 - 验证请求对象:调用验证器的
validate()方法对解析后的MyRequest对象做校验,获取约束违规信息。 - 处理验证结果:若存在违规,直接返回错误响应;若验证通过,需将已读取的请求体包装后传递给后续过滤器链(因为HttpServletRequest的输入流仅能读取一次)。
完整代码实现
1. 过滤器完整代码
import jakarta.servlet.FilterChain import jakarta.servlet.http.HttpServletRequest import jakarta.servlet.http.HttpServletResponse import jakarta.validation.Validator import org.springframework.security.web.util.matcher.AntPathRequestMatcher import org.springframework.web.filter.OncePerRequestFilter import com.fasterxml.jackson.databind.ObjectMapper import jakarta.validation.ConstraintViolation import java.io.BufferedReader import java.io.ByteArrayInputStream import java.io.IOException import java.io.InputStreamReader import java.nio.charset.StandardCharsets import java.util.* class LoginValidationFilter( private val validator: Validator, private val objectMapper: ObjectMapper ) : OncePerRequestFilter() { private val loginRequestMatcher = AntPathRequestMatcher("/login", "POST") override fun doFilterInternal( request: HttpServletRequest, response: HttpServletResponse, filterChain: FilterChain ) { // 仅对登录请求执行验证逻辑 if (loginRequestMatcher.matches(request)) { // 读取请求体并保存副本 val requestBody = readRequestBody(request) val wrappedRequest = CachedBodyHttpServletRequest(request, requestBody) // 解析请求体为MyRequest对象 val loginRequest = objectMapper.readValue(requestBody, MyRequest::class.java) // 执行字段验证 val violations: Set<ConstraintViolation<MyRequest>> = validator.validate(loginRequest) if (violations.isNotEmpty()) { // 处理验证错误:返回400状态码及错误详情 response.status = HttpServletResponse.SC_BAD_REQUEST response.contentType = "application/json;charset=UTF-8" val errorMessages = violations.map { "${it.propertyPath}: ${it.message}" } response.writer.write(objectMapper.writeValueAsString(mapOf("errors" to errorMessages))) return } // 验证通过,传递包装后的请求给后续过滤器链 filterChain.doFilter(wrappedRequest, response) } else { // 非登录请求直接放行 filterChain.doFilter(request, response) } } private fun readRequestBody(request: HttpServletRequest): String { val stringBuilder = StringBuilder() var reader: BufferedReader? = null try { reader = BufferedReader(InputStreamReader(request.inputStream, StandardCharsets.UTF_8)) var line: String? while (reader.readLine().also { line = it } != null) { stringBuilder.append(line) } } catch (e: IOException) { e.printStackTrace() } finally { reader?.close() } return stringBuilder.toString() } // 包装请求体,确保后续流程可重复读取输入流 private class CachedBodyHttpServletRequest( request: HttpServletRequest, private val cachedBody: String ) : HttpServletRequestWrapper(request) { override fun getInputStream() = ByteArrayInputStream(cachedBody.toByteArray(StandardCharsets.UTF_8)) override fun getReader() = BufferedReader(InputStreamReader(inputStream, StandardCharsets.UTF_8)) } }
2. 依赖配置(Spring Boot环境)
确保项目依赖中包含验证 Starter,Spring Boot会自动配置Validator实例:
<!-- Maven依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency>
或者Gradle:
// Gradle依赖 implementation 'org.springframework.boot:spring-boot-starter-validation'
3. 注册过滤器到Spring Security
将自定义过滤器添加到Spring Security过滤器链中,位置需在认证过滤器之前:
import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.security.config.annotation.web.builders.HttpSecurity import org.springframework.security.web.SecurityFilterChain import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter @Configuration class SecurityConfig( private val loginValidationFilter: LoginValidationFilter ) { @Bean fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { return http .authorizeHttpRequests { auth -> auth.requestMatchers("/login").permitAll() .anyRequest().authenticated() } // 将自定义验证过滤器置于用户名密码认证过滤器之前 .addFilterBefore(loginValidationFilter, UsernamePasswordAuthenticationFilter::class.java) .build() } }
关键细节说明
- 请求体包装:必须通过
HttpServletRequestWrapper缓存已读取的请求体,否则后续过滤器或控制器会因输入流已关闭无法读取请求体。 - 验证范围:示例仅对
/login的POST请求做验证,可根据业务需求调整AntPathRequestMatcher的匹配规则。 - 错误处理:示例将验证错误以JSON格式返回,可根据需求自定义响应格式和状态码。
内容的提问来源于stack exchange,提问作者Hola Soy Edu Feliz Navidad
相关产品推荐
相关产品推荐

