Spring Security自定义过滤器认证失败:请求用户名密码为空
问题分析与解决方案
核心问题
你通过http://username:password@localhost:8090/login访问时,这种格式属于HTTP Basic认证,凭证是通过请求头Authorization传递的(格式为Basic Base64编码的username:password),但你的自定义过滤器里用request.getParameter("username")和request.getParameter("password")去获取,这两种方式完全不匹配,导致拿不到凭证,最终触发403。
另外,过滤器逻辑存在漏洞:不管是否成功获取到用户名密码,都会直接调用authenticationManager.authenticate,空凭证会直接触发验证失败。
具体修复步骤
1. 修改自定义过滤器,正确解析HTTP Basic认证凭证
更新DtscCustomUsernamePasswordAuthenticationFilter的doFilterInternal方法,改为解析请求头中的Basic认证信息:
@Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { String username = null; String password = null; // 解析HTTP Basic认证的请求头 String authHeader = request.getHeader("Authorization"); if (authHeader != null && authHeader.startsWith("Basic ")) { // 提取Base64编码的部分并解码 String base64Credentials = authHeader.substring("Basic ".length()).trim(); byte[] credDecoded = Base64.getDecoder().decode(base64Credentials); String credentials = new String(credDecoded, StandardCharsets.UTF_8); // 分割用户名和密码(格式为username:password) final String[] values = credentials.split(":", 2); if (values.length == 2) { username = values[0]; password = values[1]; } } System.out.println("Username: " + username); System.out.println("Password: " + password); // 只有拿到有效凭证时才进行认证 if (username != null && password != null) { try { Authentication authRequest = new UsernamePasswordAuthenticationToken(username, password); Authentication authentication = authenticationManager.authenticate(authRequest); // 生成JWT并返回 SecretKey key = Keys.hmacShaKeyFor(signingKey.getBytes(StandardCharsets.UTF_8)); HashMap<String, Object> claims = new HashMap<>(); claims.put("username", username); String jwt = Jwts.builder() .setClaims(claims) .signWith(key) .compact(); response.setHeader("Authorization", jwt); } catch (AuthenticationException e) { // 认证失败时返回401,而不是让后续流程处理 response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); return; } } chain.doFilter(request, response); }
2. 优化过滤器的请求过滤规则
确保只过滤目标请求,避免无意义的资源消耗,修改shouldNotFilter方法:
@Override protected boolean shouldNotFilter(HttpServletRequest request) { String path = request.getServletPath(); String method = request.getMethod(); // 只处理POST方法的/login请求,可根据实际接口需求调整 return !("/login".equals(path) && "POST".equalsIgnoreCase(method)); }
3. 避免默认Basic认证过滤器冲突
在WebSecurityConfig的configure(HttpSecurity http)方法中禁用默认Basic认证过滤器,防止与自定义过滤器冲突:
@Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable(); http.httpBasic().disable(); // 禁用默认Basic认证过滤器 http.cors().and().authorizeRequests() .antMatchers("/login").permitAll() .antMatchers("/").permitAll() .anyRequest().authenticated(); http.addFilterAt(dtscCustomerUsernamePasswordAutenticationFilter, BasicAuthenticationFilter.class); }
4. 测试验证
- 浏览器访问:
http://username:password@localhost:8090/login(若接口为POST,建议用Postman测试) - Postman测试:选择Basic Auth认证方式,输入用户名密码,发送POST请求到
http://localhost:8090/login
额外注意事项
- 如果前端React是通过表单提交或JSON请求体传递用户名密码,需要修改过滤器逻辑,解析表单参数或请求体内容,当前修复仅针对URL中的Basic认证场景。
- 确保配置文件中
jwt.signing.key的长度符合要求(HMAC SHA算法要求密钥至少256位,即32个字符以上)。 - 认证失败时返回401状态码,便于前端准确识别认证失败场景。
内容的提问来源于stack exchange,提问作者Kalyanakumar R
相关产品推荐
相关产品推荐

