1. 創建 HTTP Basic 認證 Filter

import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Base64;

@Component
public class HttpBasicAuthFilter implements Filter {
    
    // 配置用户名和密碼(實際項目中應該從配置文件或數據庫讀取)
    private static final String USERNAME = "admin";
    private static final String PASSWORD = "123456";
    
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        
        // 獲取 Authorization 頭
        String authHeader = httpRequest.getHeader("Authorization");
        
        if (authHeader == null || !authHeader.startsWith("Basic ")) {
            // 沒有認證信息,返回 401
            sendUnauthorizedResponse(httpResponse);
            return;
        }
        
        try {
            // 解析 Basic 認證信息
            String base64Credentials = authHeader.substring("Basic ".length());
            byte[] credDecoded = Base64.getDecoder().decode(base64Credentials);
            String credentials = new String(credDecoded, StandardCharsets.UTF_8);
            
            // credentials 格式為 "username:password"
            String[] values = credentials.split(":", 2);
            if (values.length != 2) {
                sendUnauthorizedResponse(httpResponse);
                return;
            }
            
            String username = values[0];
            String password = values[1];
            
            // 驗證用户名和密碼
            if (USERNAME.equals(username) && PASSWORD.equals(password)) {
                // 認證成功,繼續處理請求
                chain.doFilter(request, response);
            } else {
                // 認證失敗
                sendUnauthorizedResponse(httpResponse);
            }
            
        } catch (Exception e) {
            // 解析異常,返回 401
            sendUnauthorizedResponse(httpResponse);
        }
    }
    
    private void sendUnauthorizedResponse(HttpServletResponse response) throws IOException {
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        response.setHeader("WWW-Authenticate", "Basic realm=\"Protected Area\"");
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().write("{\"error\":\"Unauthorized\",\"message\":\"請提供有效的認證信息\"}");
    }
}

2. 配置 Filter 的攔截路徑

import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class FilterConfig {
    
    @Bean
    public FilterRegistrationBean<HttpBasicAuthFilter> httpBasicAuthFilterRegistration(
            HttpBasicAuthFilter httpBasicAuthFilter) {
        
        FilterRegistrationBean<HttpBasicAuthFilter> registration = new FilterRegistrationBean<>();
        registration.setFilter(httpBasicAuthFilter);
        
        // 配置需要認證的路徑
        registration.addUrlPatterns("/api/*", "/admin/*");
        
        // 設置過濾器順序
        registration.setOrder(1);
        
        return registration;
    }
}


3. 更完善的實現(支持配置文件)

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

@Slf4j
@Component
public class HttpBasicAuthFilter implements Filter {
    
    @Value("${app.auth.basic.enabled:true}")
    private boolean authEnabled;
    
    @Value("${app.auth.basic.username:admin}")
    private String username;
    
    @Value("${app.auth.basic.password:123456}")
    private String password;
    
    @Value("${app.auth.basic.realm:Protected Area}")
    private String realm;
    
    // 支持多用户(可選)
    private Map<String, String> users = new HashMap<>();
    
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // 初始化用户信息(可以從數據庫加載)
        users.put(username, password);
        users.put("user1", "pass1");
        users.put("user2", "pass2");
        
        log.info("HTTP Basic Auth Filter initialized, auth enabled: {}", authEnabled);
    }
    
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        
        if (!authEnabled) {
            chain.doFilter(request, response);
            return;
        }
        
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        
        String requestURI = httpRequest.getRequestURI();
        log.debug("Processing request: {}", requestURI);
        
        // 獲取 Authorization 頭
        String authHeader = httpRequest.getHeader("Authorization");
        
        if (!StringUtils.hasText(authHeader) || !authHeader.startsWith("Basic ")) {
            log.warn("Missing or invalid Authorization header for request: {}", requestURI);
            sendUnauthorizedResponse(httpResponse);
            return;
        }
        
        try {
            if (authenticate(authHeader)) {
                log.debug("Authentication successful for request: {}", requestURI);
                chain.doFilter(request, response);
            } else {
                log.warn("Authentication failed for request: {}", requestURI);
                sendUnauthorizedResponse(httpResponse);
            }
            
        } catch (Exception e) {
            log.error("Error during authentication for request: {}", requestURI, e);
            sendUnauthorizedResponse(httpResponse);
        }
    }
    
    private boolean authenticate(String authHeader) {
        try {
            String base64Credentials = authHeader.substring("Basic ".length());
            byte[] credDecoded = Base64.getDecoder().decode(base64Credentials);
            String credentials = new String(credDecoded, StandardCharsets.UTF_8);
            
            String[] values = credentials.split(":", 2);
            if (values.length != 2) {
                return false;
            }
            
            String inputUsername = values[0];
            String inputPassword = values[1];
            
            // 驗證用户名和密碼
            return users.containsKey(inputUsername) && 
                   users.get(inputUsername).equals(inputPassword);
                   
        } catch (Exception e) {
            log.error("Error parsing authentication credentials", e);
            return false;
        }
    }
    
    private void sendUnauthorizedResponse(HttpServletResponse response) throws IOException {
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        response.setHeader("WWW-Authenticate", "Basic realm=\"" + realm + "\"");
        response.setContentType("application/json;charset=UTF-8");
        
        String errorResponse = String.format(
            "{\"error\":\"Unauthorized\",\"message\":\"請提供有效的認證信息\",\"timestamp\":\"%s\"}",
            java.time.LocalDateTime.now()
        );
        
        response.getWriter().write(errorResponse);
    }
}

4. 配置文件 application.yml

app:
  auth:
    basic:
      enabled: true
      username: admin
      password: 123456
      realm: "API Protected Area"

# 或者使用環境變量
# BASIC_AUTH_USERNAME: admin
# BASIC_AUTH_PASSWORD: 123456

5. 測試控制器

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api")
public class TestController {
    
    @GetMapping("/hello")
    public String hello() {
        return "Hello, authenticated user!";
    }
    
    @GetMapping("/user/info")
    public String userInfo() {
        return "User information - protected resource";
    }
}

@RestController
@RequestMapping("/admin")
public class AdminController {
    
    @GetMapping("/dashboard")
    public String dashboard() {
        return "Admin Dashboard - highly protected!";
    }
}

6. 高級配置:排除特定路徑

import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class FilterConfig {
    
    @Bean
    public FilterRegistrationBean<HttpBasicAuthFilter> httpBasicAuthFilterRegistration(
            HttpBasicAuthFilter httpBasicAuthFilter) {
        
        FilterRegistrationBean<HttpBasicAuthFilter> registration = new FilterRegistrationBean<>();
        registration.setFilter(httpBasicAuthFilter);
        
        // 需要認證的路徑
        registration.addUrlPatterns("/api/*", "/admin/*");
        
        // 排除不需要認證的路徑(通過自定義 Filter 邏輯實現)
        registration.addInitParameter("excludePatterns", "/api/public/*,/api/health");
        
        registration.setOrder(1);
        registration.setName("httpBasicAuthFilter");
        
        return registration;
    }
}

7. 使用方式

命令行測試:

# 無認證訪問(會返回 401)
curl http://localhost:8080/api/hello

# 正確的認證訪問
curl -u admin:123456 http://localhost:8080/api/hello

# 或者使用 Authorization 頭
curl -H "Authorization: Basic YWRtaW46MTIzNDU2" http://localhost:8080/api/hello

瀏覽器訪問:

訪問 http://localhost:8080/api/hello 時,瀏覽器會彈出認證對話框。

8. 安全建議

  1. 使用 HTTPS:Basic 認證是明文傳輸的(只是 Base64 編碼),必須使用 HTTPS
  2. 密碼加密存儲:實際項目中密碼應該加密存儲
  3. 使用配置文件或環境變量:不要硬編碼用户名密碼
  4. 添加日誌記錄:記錄認證成功/失敗的日誌
  5. 考慮使用 JWT 或 OAuth2:對於複雜的認證需求

這個實現提供了一個簡單而完整的 HTTP Basic 認證方案,可以根據實際需求進行調整和擴展。