我正在使用 SpringBoot (Kotlin) 开发一个应用程序,并且正在使用带有 Spring Boot Security 的身份验证过滤器。
过滤器如下所示:
class JWTAuthenticationFilter(private val tokenAuthenticationService: TokenAuthenticationService) : GenericFilterBean() {
@Throws(IOException::class, ServletException::class)
override fun doFilter(request: ServletRequest, response: ServletResponse, filterChain: FilterChain) {
val authentication = tokenAuthenticationService.getAuthentication(request as HttpServletRequest)
SecurityContextHolder.getContext().authentication = authentication
filterChain.doFilter(request, response)
}
}
fun getAuthentication(request: HttpServletRequest): Authentication? {
val token = request.getHeader(AUTHORIZATION_HEADER_KEY)
if (token != null) {
val credentials = ObjectMapper().readValue(request.inputStream, BaseRequest::class.java)
val user = Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token.replace(TOKEN_PREFIX, ""))
.body.subject
if (user != null && credentials.userName != null && user == credentials.userName) {
return UsernamePasswordAuthenticationToken(user, null, emptyList<GrantedAuthority>())
}
}
return null
}
对于没有变量的控制器方法,此方法有效并执行身份验证。对于具有请求正文的控制器方法,这不起作用,因为输入流被报告为已关闭。我从其他 SO 问题中读到,控制器方法在将请求正文添加到 HttpServletRequest 时读取输入流。它是否正确 ?在这种情况下,它在这里做什么?我已经阅读过有关 InputStream 缓存的内容,但在这种情况下,我不知道何时第一次读取输入流以缓存读取的值。
我在下面给出了控制器方法的代码:
@PostMapping
@RequestMapping("/status")
fun status(@Validated @RequestBody gameDetails: CreateGameRequest): ResponseEntity<GameVO> {
val gameRoundPair = gameService.giveGameStatus(gameDetails.gameName)
return ResponseEntity.ok(gameAssembler.toGameVO(gameRoundPair.first, gameRoundPair.second))
}
@PostMapping
@RequestMapping("/available_users")
fun get_available_users(): ResponseEntity<UsernameListVO> {
val response = userService.getLoggedInUsers()
return ResponseEntity.ok(userAssembler.toUsernameListVO(response))
}
经过几个小时的搜索,我终于解决了这个问题。缓存是在添加到 SpringBoot Security 的过滤器链中的过滤器中进行的,该过滤器在其他过滤器之前运行。该过滤器如下所示:
class CachingRequestBodyFilter() : OncePerRequestFilter() {
override fun doFilterInternal(
request: HttpServletRequest,
response: HttpServletResponse,
filterChain: FilterChain
) {
if (request.method.equals(HttpMethod.POST.name)) {
var requestWrapper = CachingRequestWrapper(request)
filterChain.doFilter(requestWrapper, response)
} else {
filterChain.doFilter(request, response)
}
}
class CachingRequestWrapper(request: HttpServletRequest) : HttpServletRequestWrapper(request) {
private var requestBody: String;
init {
val reader = BufferedReader(request.inputStream.reader())
val content = StringBuilder()
reader.use { reader ->
var line = reader.readLine()
while (line != null) {
content.append(line)
line = reader.readLine()
}
}
requestBody = content.toString()
}
override fun getInputStream(): ServletInputStream {
val byteArrayInputStream = requestBody.byteInputStream()
val obj = object : ServletInputStream() {
override fun read() : Int {
return byteArrayInputStream.read()
}
override fun isFinished(): Boolean {
return byteArrayInputStream.available() == 0
}
override fun isReady(): Boolean {
return true
}
override fun setReadListener(readListener: ReadListener): Unit {
}
}
return obj
}
}
}
然后在身份验证过滤器中,我执行以下操作以从请求中读取凭据:
if (request is CachingRequestBodyFilter.CachingRequestWrapper) {
val objectMapper = ObjectMapper()
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
val credentials = objectMapper.readValue((request as CachingRequestBodyFilter.CachingRequestWrapper).inputStream, BaseRequest::class.java)
....................................
}
这里我使用 Jackson 库从缓存的请求字符串中反序列化。