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

Servlet集成Basic Authentication异常:RestAuthenticationFilter未触发排查

问题描述

基于Servlet开发REST API,无前端,仅通过Postman测试接口:

  • Get、Delete接口映射正常
  • 使用Basic Auth授权类型时,自定义的RestAuthenticationFilter未被调用
  • 使用Bearer Token、API Key类型时,该Filter可正常触发

相关代码

RestAuthenticationFilter 代码

public class RestAuthenticationFilter implements Filter {
      public static final String AUTHENTICATION_HEADER = "Authorization";

      public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
          throws IOException, ServletException {
          System.out.println("Filter");
        if (request instanceof HttpServletRequest) {
          final HttpServletRequest httpServletRequest = (HttpServletRequest) request;
          final String authCredentials = httpServletRequest.getHeader(AUTHENTICATION_HEADER);
          System.out.println("authCredentials: "+authCredentials);

          final AuthService authService = new AuthService();
          final boolean authenticationStatus = authService.authenticate(authCredentials);
          System.out.println("authenticationStatus: "+authenticationStatus);
          if (authenticationStatus) {
            chain.doFilter(request, response);
          } else {
            if (response instanceof HttpServletResponse) {
              HttpServletResponse httpServletResponse = (HttpServletResponse) response;
              httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            }
          }
        }
      }

      public void init(FilterConfig filterConfig) throws ServletException {
        // No Implementation
      }

      public void destroy() {
        // No Implementation
      }
}

AuthService 代码

public class AuthService {
      public boolean authenticate(final String authCredentials) {
         System.out.println("autservice");
        if (null == authCredentials) {
          return false;
        }

        // Header value format for Basic authentication will be "Basic encodedstring"
        final String encodedUserAuthCredentials = authCredentials.replaceFirst("Basic ", "");
        System.out.println("encodedUserAuthCredentials: "+encodedUserAuthCredentials);
        String decodedUserAuthCredentials = null;
        try {
//          sun.misc.BASE64Decoder dec = new sun.misc.BASE64Decoder();
//          String userpassDecoded = new String(dec.decodeBuffer(encodedUserAuthCredentials));
//          String base64String = BaseEncoding.base64().encode(encodedUserAuthCredentials.getBytes("UTF-8"));
//          byte[] decodedBytes = Base64.getDecoder().decode(encodedUserAuthCredentials);
//          decodedUserAuthCredentials = base64String;
            decodedUserAuthCredentials = DatatypeConverter.printBase64Binary(("admin:admin").getBytes("UTF-8"));
//          System.out.println("base64String: "+base64String);
            System.out.println("decodedUserAuthCredentials: "+decodedUserAuthCredentials);
        } catch (IOException e) {
          System.out.println("LOGGER");
        }

        final StringTokenizer tokenizer = new StringTokenizer(decodedUserAuthCredentials, ":");
        final String userName = tokenizer.nextToken();
        final String userPassword = tokenizer.nextToken();

        return "admin".equals(userName) && "admin".equals(userPassword);
      }
}

web.xml 配置

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5">
  <display-name>test</display-name>
  <servlet>
    <servlet-name>
        AppContoller
    </servlet-name>
    <servlet-class>
        com.test.control.web.AppController
    </servlet-class>
  </servlet>
  
  <servlet-mapping>
    <servlet-name>
        AppController
    </servlet-name>
    <url-pattern>
        /control/*
    </url-pattern>
  </servlet-mapping>
   
  <welcome-file-list>
    <welcome-file>index.jsp</welcome-file>
  </welcome-file-list>
  
   <filter>
      <filter-name>AuthenticationFilter</filter-name>
      <filter-class>com.test.control.web.api.RestAuthenticationFilter</filter-class>
   </filter>
   <filter-mapping>
      <filter-name>AuthenticationFilter</filter-name>
      <url-pattern>/control/*</url-pattern>
   </filter-mapping>

</web-app>

排查分析与修复方案

核心问题1:Servlet容器提前拦截Basic Auth请求

Servlet容器(如Tomcat)默认会自动处理带有Authorization: Basic ...头的请求,触发自身的Basic认证流程。这会导致请求还未到达自定义的RestAuthenticationFilter,就被容器直接返回401响应,表现为Filter未被调用。

修复方式:在web.xml中添加安全约束,禁用容器对指定路径的自动认证:

<security-constraint>
    <web-resource-collection>
        <web-resource-name>Disable Container Auth</web-resource-name>
        <url-pattern>/control/*</url-pattern>
    </web-resource-collection>
    <auth-constraint>
        <!-- 空约束表示允许所有请求,容器不会触发自身认证 -->
    </auth-constraint>
</security-constraint>

核心问题2:AuthService解码逻辑完全错误

当前AuthService中,并未对前端传入的Base64编码串进行解码,反而将固定字符串admin:admin编码后赋值给decodedUserAuthCredentials,导致认证逻辑完全失效。

修复方式:替换为正确的Base64解码逻辑(Java 8+标准实现):

import java.util.StringTokenizer;
import java.nio.charset.StandardCharsets;
import java.util.Base64;

public class AuthService {
    public boolean authenticate(final String authCredentials) {
        System.out.println("autservice");
        // 校验头是否存在且为Basic类型
        if (null == authCredentials || !authCredentials.startsWith("Basic ")) {
            return false;
        }

        // 提取Base64编码部分
        final String encodedUserAuthCredentials = authCredentials.substring(6);
        System.out.println("encodedUserAuthCredentials: " + encodedUserAuthCredentials);
        String decodedUserAuthCredentials = null;
        
        try {
            // 解码Base64字符串
            byte[] decodedBytes = Base64.getDecoder().decode(encodedUserAuthCredentials);
            decodedUserAuthCredentials = new String(decodedBytes, StandardCharsets.UTF_8);
            System.out.println("decodedUserAuthCredentials: " + decodedUserAuthCredentials);
        } catch (IllegalArgumentException e) {
            // 处理Base64格式错误
            System.out.println("Base64解码失败: " + e.getMessage());
            return false;
        }

        // 分割用户名和密码,校验格式合法性
        final StringTokenizer tokenizer = new StringTokenizer(decodedUserAuthCredentials, ":");
        if (tokenizer.countTokens() != 2) {
            return false;
        }
        
        final String userName = tokenizer.nextToken();
        final String userPassword = tokenizer.nextToken();

        return "admin".equals(userName) && "admin".equals(userPassword);
    }
}

验证步骤

  1. 重启应用,将上述修改部署到容器
  2. 在Postman中选择Basic Auth,输入用户名admin、密码admin
  3. 发送请求,观察控制台输出:
    • 会打印Filter,说明Filter已被触发
    • 会正确解码并输出decodedUserAuthCredentials: admin:admin
    • 最终authenticationStatus为true,请求会转发到目标Servlet

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 22:07:03