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); } }
验证步骤
- 重启应用,将上述修改部署到容器
- 在Postman中选择Basic Auth,输入用户名
admin、密码admin - 发送请求,观察控制台输出:
- 会打印
Filter,说明Filter已被触发 - 会正确解码并输出
decodedUserAuthCredentials: admin:admin - 最终
authenticationStatus为true,请求会转发到目标Servlet
- 会打印
内容的提问来源于stack exchange,提问作者user080320
相关产品推荐
相关产品推荐

