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

Spring WebClient调用OAuth2令牌接口返回401 UNAUTHORIZED问题排查

问题根因汇总

你遇到的所有报错都是三个核心问题导致的:

  • 最初的401错误:你将x-www-form-urlencoded格式的身份参数放在了请求头中,而OAuth2令牌接口要求参数必须放在请求体中,服务端读取不到参数直接返回鉴权失败。
  • 不支持application/x-www-form-urlencoded媒体类型错误:Spring WebClient默认没有提供将自定义POJO序列化为表单格式的编码器,直接传自定义对象会触发序列化失败。
  • 反序列化错误:你定义的TokenResponse类和接口实际返回的JSON字段不匹配,OAuth2标准令牌响应没有result字段,Kotlin非空类型要求字段必须有值,找不到对应字段就会抛出空参数异常。
修复步骤

1. 修正表单参数传递逻辑

WebClient提交x-www-form-urlencoded格式表单有两种正确写法,任选一种即可:

写法1:使用FormInserter直接构造参数

import org.springframework.web.reactive.function.BodyInserters
import org.springframework.http.MediaType

@Service
class TokenService(private val webClient: WebClient) {

    fun postAsynchronous(): Mono<TokenResponse> = webClient
        .post()
        .uri("https://mycomp.url/api/oauth/token")
        .contentType(MediaType.APPLICATION_FORM_URLENCODED)
        .body(
            BodyInserters.fromFormData("grant_type", "client_credentials")
                .with("client_id", "xxx")
                .with("client_secret", "yyy")
        )
        .retrieve()
        .onStatus(HttpStatus::is4xxClientError) { Mono.error(RuntimeException("4XX Error ${it.statusCode()}")) }
        .onStatus(HttpStatus::is5xxServerError) { Mono.error(RuntimeException("5XX Error ${it.statusCode()}")) }
        .bodyToMono(TokenResponse::class.java)
}

写法2:使用MultiValueMap传递参数

import org.springframework.util.LinkedMultiValueMap
import org.springframework.util.MultiValueMap

val formData: MultiValueMap<String, String> = LinkedMultiValueMap()
formData.add("grant_type", "client_credentials")
formData.add("client_id", "xxx")
formData.add("client_secret", "yyy")

// 传入body
.bodyValue(formData)

2. 修正TokenResponse字段映射

首先确认接口实际返回的JSON结构,OAuth2标准令牌返回字段为access_token、token_type、expires_in等,你需要根据实际返回调整类定义:

方案A:和返回字段直接匹配

data class TokenResponse (
    val access_token: String,
    val token_type: String,
    val expires_in: Long,
    // 不确定是否返回的字段设为可空类型,避免反序列化失败
    val scope: String? = null
)

方案B:用@JsonProperty映射自定义字段名

如果你要保留result作为字段名,添加注解映射实际返回的字段即可:

import com.fasterxml.jackson.annotation.JsonProperty

data class TokenResponse (
    @JsonProperty("access_token")
    val result: String
)
验证顺序

修改完成后先调用接口打印完整返回结果,确认字段匹配后再做反序列化,避免反复调试。


内容的提问来源于stack exchange,提问作者Jim C

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.02 21:09:03