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. 安全建議
- 使用 HTTPS:Basic 認證是明文傳輸的(只是 Base64 編碼),必須使用 HTTPS
- 密碼加密存儲:實際項目中密碼應該加密存儲
- 使用配置文件或環境變量:不要硬編碼用户名密碼
- 添加日誌記錄:記錄認證成功/失敗的日誌
- 考慮使用 JWT 或 OAuth2:對於複雜的認證需求
這個實現提供了一個簡單而完整的 HTTP Basic 認證方案,可以根據實際需求進行調整和擴展。