You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Spring Security过滤器中程序化验证请求参数

在Spring Security过滤器中实现请求体的程序化验证

核心步骤

  1. 注入标准验证器:从Spring容器获取javax.validation.Validator实例,这是JSR-380(Bean Validation)的标准实现。
  2. 验证请求对象:调用验证器的validate()方法对解析后的MyRequest对象做校验,获取约束违规信息。
  3. 处理验证结果:若存在违规,直接返回错误响应;若验证通过,需将已读取的请求体包装后传递给后续过滤器链(因为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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 05:13:14