普通网友 2026-05-17 00:30 采纳率: 98.6%
浏览 0
已采纳

Java HTTP请求如何正确添加Bearer Token认证头?

常见问题:在Java中使用`HttpURLConnection`或Apache HttpClient发送带Bearer Token的HTTP请求时,开发者常误将Token直接拼接为`"Bearer token_value"`并调用`setRequestProperty("Authorization", "Bearer token_value")`,却忽略Token字符串本身是否已含前缀(如后端返回的JWT可能已被错误地加上`Bearer `)、是否包含非法空白符或URL编码问题;更隐蔽的是,若Token含换行、制表符或不可见Unicode字符(如零宽空格),会导致401 Unauthorized但日志难以排查。此外,使用OkHttp时若未正确调用`addHeader("Authorization", "Bearer " + token)`而误用`header()`(会覆盖同名头),或在拦截器中重复添加认证头,亦引发认证失败。关键点在于:Token应严格校验格式、trim前后空格、避免硬编码拼接,并优先通过标准HTTP头构造机制注入,而非字符串拼凑。
  • 写回答

1条回答 默认 最新

  • 未登录导 2026-05-17 00:30
    关注
    ```html

    一、现象层:典型401错误的表象与日志盲区

    开发者在调用 HttpURLConnection.setRequestProperty("Authorization", "Bearer " + token) 后持续收到 HTTP 401 Unauthorized,但服务端日志仅显示“Invalid token”,无具体解析失败原因;本地打印的 token 看似正常,却无法复现 Postman 成功请求——这往往不是网络或权限问题,而是 Token 字符串本身携带了不可见污染。

    二、溯源层:Token 污染的四大隐性来源

    • 前缀冗余:后端误将 "Bearer eyJhbGciOi..." 作为完整 token 返回(含空格+Bearer),前端二次拼接导致 "Bearer Bearer eyJhbGciOi..."
    • 空白字符污染:JSON 响应中 token 字段前后含 \r\n\t 或 Unicode 零宽空格(U+200B);
    • URL 编码残留:OAuth2 授权码交换时未对 access_token 字段做 URLDecoder.decode(..., "UTF-8")
    • 头覆盖陷阱:OkHttp 中误用 request.newBuilder().header("Authorization", ...)(单次覆盖)替代 addHeader(),或在多个拦截器中重复注入认证头。

    三、验证层:可落地的 Token 健康度检查清单

    检查项检测代码片段预期结果
    是否含前缀token.startsWith("Bearer ") || token.startsWith("bearer ")false
    是否含不可见字符token.codePoints().anyMatch(cp -> cp < 32 && cp != 9 && cp != 10 && cp != 13)false
    URL 编码状态!token.equals(URLDecoder.decode(token, "UTF-8"))false

    四、实践层:防御式 Token 注入标准模式

    以下为跨客户端统一推荐的封装逻辑(以 Apache HttpClient 5.x 为例):

    public static HttpRequestBase withBearerAuth(HttpRequestBase request, String rawToken) {
        // 1. 严格清洗:trim + 移除零宽字符 + 标准化空白
        String cleanToken = rawToken
            .trim()
            .replaceAll("[\\u200B-\\u200F\\u202A-\\u202E\\u2060-\\u2064\\uFEFF]", "")
            .replaceAll("\\s+", " ");
        
        // 2. 智能剥离已有前缀(兼容历史脏数据)
        if (cleanToken.toLowerCase().startsWith("bearer ")) {
            cleanToken = cleanToken.substring(7).trim();
        }
        
        // 3. 标准化注入(非字符串拼接!)
        if (request instanceof HttpPost) {
            ((HttpPost) request).setHeader("Authorization", "Bearer " + cleanToken);
        }
        return request;
    }

    五、架构层:构建可审计的认证中间件

    使用 OkHttp 时,应通过 Interceptor 统一管控认证头,避免业务层硬编码:

    public class AuthHeaderInterceptor implements Interceptor {
        private final Supplier<String> tokenSupplier;
    
        public AuthHeaderInterceptor(Supplier<String> tokenSupplier) {
            this.tokenSupplier = tokenSupplier;
        }
    
        @Override
        public Response intercept(Chain chain) throws IOException {
            String token = tokenSupplier.get();
            if (token == null || token.trim().isEmpty()) {
                throw new IllegalStateException("Auth token unavailable");
            }
            String clean = sanitizeToken(token); // 复用前述清洗逻辑
            Request request = chain.request()
                .newBuilder()
                .removeHeader("Authorization") // 显式清除,防重复
                .addHeader("Authorization", "Bearer " + clean)
                .build();
            return chain.proceed(request);
        }
    }

    六、可观测层:Token 流转全链路埋点建议

    graph LR A[OAuth2 Token Endpoint] -->|Raw JSON| B[Token Parser] B --> C{Sanitize?} C -->|Yes| D[Trim + ZeroWidthStrip + PrefixCheck] C -->|No| E[Log WARN + Alert] D --> F[Cache/Store Clean Token] F --> G[HttpClient/OkHttp Interceptor] G --> H[Wire Log: Authorization: Bearer ***] H --> I[Service Mesh Tracing Tag: auth_token_hash]

    七、演进层:从手动拼接到声明式安全的跃迁

    长期建议升级至 Spring Security 6+ 的 @RegisteredOAuth2AuthorizedClient 或 Micrometer Tracing 的 SecurityContextPropagator,将认证头注入下沉至框架层,业务代码仅声明资源权限需求(如 @PreAuthorize("hasAuthority('SCOPE_read')")),彻底规避字符串拼接风险。

    ```
    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

问题事件

  • 已采纳回答 5月18日
  • 创建了问题 5月17日