五種方案!保護Spring Boot API接口安全
環境:SpringBoot3.4.2
1. 簡介
API是系統核心數據的出入口,必須防止未授權訪問、數據泄露、惡意攻擊等風險。例如:
- 防止游客查看敏感數據(如用戶信息)
- 阻止越權操作(如普通用戶刪除管理員數據)
- 抵御惡意刷接口或注入攻擊
合理的權限控制能確保數據安全、系統穩定,并符合合規要求。
接下來我將詳細的介紹 5 種簡單好用的保護方案,讓你能輕松搞定 API 接口安全。
以下是本篇文章要實現的5種方案:
- 通過Filter實現
- 使用Interceptor攔截器實現
- 基于AOP+Filter實現
- 使用Spring Security實現
- 整合OAuth2認證
2.實戰案例
準備如下Controller接口
@RestController
public class ApiController {
@GetMapping("/api/query")
public ResponseEntity<?> query() {
System.err.println(SecurityContext.get()) ;
return ResponseEntity.ok("query...") ;
}
@GetMapping("/create")
public ResponseEntity<?> create() {
return ResponseEntity.ok("create...") ;
}
}
這里提供了2個接口。其中,/api/query是需要認證的接口,/create無需認證,可直接訪問。
2.1 通過Filter實現
Filter 是 Java Servlet 規范中的一個組件,可以在請求到達 Servlet 之前或響應返回客戶端之前對請求和響應進行攔截和處理。通過在 Filter 中實現權限認證邏輯,可以有效地控制用戶對 API 接口的訪問權限。
代碼實現:
@WebFilter("/api/*")
public class JwtAuthFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain filterChain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req ;
HttpServletResponse response = (HttpServletResponse) resp ;
try {
// 從請求header中讀取token
String token = JwtUtil.extractToken(request) ;
// 驗證token & 將從token中解析出的數據保存到線程上下文中
if (token != null && JwtUtil.validateToken(token)) {
Map<String, Object> data = JwtUtil.parseToken(token) ;
SecurityContext.set(data) ;
filterChain.doFilter(request, resp) ;
} else {
// 認證失敗,則直接輸出錯誤內容
response.setContentType("application/json;charset=utf-8");
response.getWriter().println(new ObjectMapper()
.writeValueAsString(Map.of("code", -1, "msg", "無效token"))) ;
return ;
}
} finally {
SecurityContext.clear() ;
}
}
}
此過濾器將只攔截 /api 開頭的請求。
運行結果
圖片
圖片
提供正確的token再次訪問
圖片
2.2 使用Interceptor攔截器
HandlerInterceptor 是 Spring MVC 提供的一個接口,可以在請求處理前后進行攔截。通過實現其 preHandle 方法,可以在請求到達控制器之前進行權限驗證,決定是否允許請求繼續執行。
代碼實現:
@Component
public class AuthInterceptor implements HandlerInterceptor {
private final ObjectMapper objectMapper ;
public AuthInterceptor(ObjectMapper objectMapper) {
this.objectMapper = objectMapper ;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String token = JwtUtil.extractToken(request) ;
if (token != null && JwtUtil.validateToken(token)) {
Map<String, Object> data = JwtUtil.parseToken(token) ;
SecurityContext.set(data) ;
return true ;
} else {
response.setContentType("application/json;charset=utf-8");
response.getWriter().println(objectMapper
.writeValueAsString(Map.of("code", -1, "msg", "無效token"))) ;
return false ;
}
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
SecurityContext.clear() ;
}
}
注冊攔截器
@Component
public class WebConfig implements WebMvcConfigurer {
private final AuthInterceptor authInterceptor ;
public WebConfig(AuthInterceptor authInterceptor) {
this.authInterceptor = authInterceptor;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(this.authInterceptor).addPathPatterns("/api/**") ;
}
}
注意:不要忘記在攔截器的 afterCompletion 中進行上下文的清理。
2.3 使用AOP + Filter實現
AOP用于定義權限驗證的切面邏輯,Filter 則用于攔截請求并解析處理Token。這種組合方式能夠在請求處理的不同階段進行權限控制,既靈活又高效,適合在需要復雜權限管理。
代碼實現:
Filter實現
@WebFilter("/*")
public class TokenFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain filterChain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req ;
try {
String token = JwtUtil.extractToken(request) ;
if (token != null && JwtUtil.validateToken(token)) {
Map<String, Object> data = JwtUtil.parseToken(token) ;
SecurityContext.set(data) ;
}
filterChain.doFilter(request, resp) ;
} finally {
SecurityContext.clear() ;
}
}
}
AOP切面實現
// 自定義注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PermissionCheck {
String[] value() default {} ;
}
// 切面定義
@Aspect
@Component
public class PermissionAspect {
@SuppressWarnings("unchecked")
@Around("@annotation(permissionCheck)")
public Object checkPermission(ProceedingJoinPoint joinPoint, PermissionCheck permissionCheck) throws Throwable {
try {
Map<String, Object> data = SecurityContext.get() ;
if (data == null) {
throw new PermissionException("無權限訪問", 403) ;
}
// 獲取所需角色
String[] requiredRoles = permissionCheck.value() ;
// 用戶所擁有的角色
List<String> assignedRoles = (List<String>) data.get("roles") ;
// 判斷2個集合如果沒有共同元素則返回true
if (Collections.disjoint(List.of(requiredRoles), assignedRoles)) {
throw new PermissionException("無權限訪問", 403) ;
}
// 繼續執行方法
return joinPoint.proceed();
} catch (Exception e) {
e.printStackTrace() ;
throw new PermissionException("Token無效或過期", 401) ;
}
}
}
修改Controller接口
@GetMapping("/api/query")
@PermissionCheck({"api:query"})
public ResponseEntity<?> query() {
return ResponseEntity.ok("query...") ;
}
2.4 使用Spring Security
Spring Security 是 Spring 生態中的安全框架,提供了豐富的權限控制功能,包括身份認證、授權、密碼加密等。通過配置安全規則和過濾器鏈,可以輕松實現細粒度的權限控制。
代碼實現:
首先,引入依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
最后,安全配置
@Configuration
public class SecurityConfig {
@Bean
SecurityFilterChain apiSecurity(HttpSecurity http) throws Throwable {
http.csrf(csrf -> csrf.disable()) ;
http.securityMatcher("/api/**", "/login") ;
http.formLogin(Customizer.withDefaults()) ;
http.authorizeHttpRequests(registry -> {
registry.anyRequest().authenticated() ;
}) ;
return http.build() ;
}
// 實際我們應該從數據庫中查詢,這里為了簡單直接固定一個內存用戶
@Bean
UserDetailsService userDetailsService() {
UserDetails user = User.withUsername("pack")
.password("{noop}123123")
.roles("ADMIN")
.build() ;
return new InMemoryUserDetailsManager(user) ;
}
}
在上面的SecurityFilterChain配置中,我們將只會對/api/*,/login接口進行攔截。如果沒有權限將會自動定向到登錄頁面。
當訪問/api/query接口時,自動跳轉到登錄頁面:
圖片
輸入正確的用戶名密碼后,自動跳回之前的接口地址
圖片