@[toc]
实现类
ApiKeyAuthFilter认证过滤器ApiKeyAuthenticationToken认证令牌ApiKeyAuthenticationProviderAPI认证鉴权SecurityConfig安全配置类
ApiKeyAuthFilter
方法概要 OncePerRequestFilter 是 Spring Web 提供的一个抽象过滤器基类,核心作用是确保过滤器的 doFilterInternal 方法在一次HTTP 请求的整个处理流程中只执行一次。 @Component 自动注册为 Spring Bean。 注入 AuthenticationManager
AuthenticationManager是 Spring Security 的 认证总入口;- 调用它的
authenticate()方法会:- 遍历所有
AuthenticationProvider; - 找到支持
ApiKeyAuthenticationToken的 Provider(即你的ApiKeyAuthenticationProvider); - 执行验证逻辑。
- 遍历所有
java
@Component
public class ApiKeyAuthFilter extends OncePerRequestFilter {
@Autowired
private AuthenticationManager authenticationManager; // 注入 AuthenticationManager
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String apiKey = request.getHeader("X-API-Key");
// 只处理 /api/ 开头的接口(根据你的需求调整)
if (apiKey != null && request.getRequestURI().startsWith("/api/")) {
try {
// 1. 创建未认证的 Token
ApiKeyAuthenticationToken authToken = new ApiKeyAuthenticationToken(apiKey);
// 2. ⭐️ 关键:显式调用 AuthenticationManager 进行认证
Authentication authenticated = authenticationManager.authenticate(authToken);
// 3. 将认证成功的 Token 放入 SecurityContext
SecurityContextHolder.getContext().setAuthentication(authenticated);
} catch (AuthenticationException e) {
// 认证失败:清空上下文,并返回 401
SecurityContextHolder.clearContext();
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write("Invalid API Key");
return;
}
}
filterChain.doFilter(request, response);
}
}
ApiKeyAuthenticationToken
AbstractAuthenticationToken 是 Spring Security 提供的认证令牌抽象类 继承实现用户登录以及令牌校验的操作,当 setAuthenticated 等于true时直接放行
java
public class ApiKeyAuthenticationToken extends AbstractAuthenticationToken {
private final Object principal;
public ApiKeyAuthenticationToken(Object principal) {
// credentials 设为 null(API Key 已用于认证,无需再存)
super(null);
this.principal = principal;
// 初始为未认证(由 AuthenticationProvider 设置为 true)
setAuthenticated(false);
}
@Override
public Object getCredentials() {
return principal;
}
@Override
public Object getPrincipal() {
return principal;
}
}
ApiKeyAuthenticationProvider
Spring Security 中实现 API Key 认证的核心组件,它负责 验证客户端传入的 API Key 是否合法,并生成已认证的安全上下文(Authentication)。 核心关系:接口约定 + 方法实现(契约模式) AuthenticationProvider 是 Spring Security 定义的认证核心接口(契约),authenticate() 是这个接口强制要求实现的核心方法(契约内容)------ 简单说:
- implements AuthenticationProvider:表示你的类「承诺遵守 Spring Security 的认证规则」;
- authenticate():是你兑现这个承诺的「具体认证逻辑」(校验 API Key、用户名密码等)。
java
@Component
public class ApiKeyAuthenticationProvider implements AuthenticationProvider {
@Autowired
private ISysApiKeysService sysApiKeysService;
// 核心方法:执行具体的认证逻辑
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String providedKey = (String) authentication.getCredentials();
SysApiKeys sysApiKeys = sysApiKeysService.selectByApikey(providedKey);
if(sysApiKeys != null){
ApiKeyAuthenticationToken authenticatedToken =
new ApiKeyAuthenticationToken(sysApiKeys);
return authenticatedToken;
}
throw new BadCredentialsException("Invalid API Key");
}
// 辅助方法:判断当前 Provider 是否支持处理某个类型的 Token
@Override
public boolean supports(Class<?> authentication) {
return ApiKeyAuthenticationToken.class.isAssignableFrom(authentication);
}
}
SecurityConfig
@EnableMethodSecurity 注解(配合 @Configuration)是为了启用方法级别的权限控制,比如通过 @PreAuthorize、@Secured 等注解限制接口访问 addFilterBefore() 是 Spring Security 中用于自定义过滤器链顺序的核心方法,它的作用是: 在指定的内置(或已注册)Filter 之前插入你自己的 Filter。所以必须在 原有UsernamePasswordAuthenticationFilter 认证之前去先走apiKeyAuthFilter
arduino
// 添加 Api filter
.addFilterBefore(apiKeyAuthFilter,UsernamePasswordAuthenticationFilter.class)
protected SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception Spring Security 中配置 HTTP 层面安全规则的核心方法,它的核心作用是通过 HttpSecurity 对象定制请求的安全策略(如哪些请求需要认证、哪些放行、用什么方式认证、异常如何处理等)。下面我会从「核心作用、常用配置、实战示例、与方法级权限的区别」四个维度讲清楚这个方法的用处。
- 这个方法的本质是构建一个
SecurityFilterChain过滤器链,Spring Security 会将这个过滤器链应用到所有 - HTTP 请求上,实现: 控制哪些请求需要认证、哪些请求可以匿名访问(放行);
- 定义认证方式(如 HTTP Basic、表单登录、JWT 过滤器等);
- 配置跨域(CORS)、CSRF 防护、会话管理; 定制认证失败、权限不足的响应(如返回 JSON 而非默认页面);
- 整合自定义过滤器(如 JWT 校验过滤器)。
完整代码
java
/**
* spring security配置
*
* @author
*/
@EnableMethodSecurity(prePostEnabled = true, securedEnabled = true)
@Configuration
public class SecurityConfig
{
/**
* 自定义用户认证逻辑
*/
@Autowired
private UserDetailsService userDetailsService;
/**
* 认证失败处理类
*/
@Autowired
private AuthenticationEntryPointImpl unauthorizedHandler;
/**
* 退出处理类
*/
@Autowired
private LogoutSuccessHandlerImpl logoutSuccessHandler;
/**
* token认证过滤器
*/
@Autowired
private JwtAuthenticationTokenFilter authenticationTokenFilter;
@Autowired
private ApiKeyAuthFilter apiKeyAuthFilter;
/**
* 跨域过滤器
*/
@Autowired
private CorsFilter corsFilter;
/**
* 允许匿名访问的地址
*/
@Autowired
private PermitAllUrlProperties permitAllUrl;
@Autowired
ApiKeyAuthenticationProvider apiKeyAuthenticationProvider;
/**
* 身份验证实现
*/
@Bean
public AuthenticationManager authenticationManager()
{
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
daoAuthenticationProvider.setUserDetailsService(userDetailsService);
daoAuthenticationProvider.setPasswordEncoder(bCryptPasswordEncoder());
return new ProviderManager(Arrays.asList(daoAuthenticationProvider,
apiKeyAuthenticationProvider));
}
/**
* anyRequest | 匹配所有请求路径
* access | SpringEl表达式结果为true时可以访问
* anonymous | 匿名可以访问
* denyAll | 用户不能访问
* fullyAuthenticated | 用户完全认证可以访问(非remember-me下自动登录)
* hasAnyAuthority | 如果有参数,参数表示权限,则其中任何一个权限可以访问
* hasAnyRole | 如果有参数,参数表示角色,则其中任何一个角色可以访问
* hasAuthority | 如果有参数,参数表示权限,则其权限可以访问
* hasIpAddress | 如果有参数,参数表示IP地址,如果用户IP和参数匹配,则可以访问
* hasRole | 如果有参数,参数表示角色,则其角色可以访问
* permitAll | 用户可以任意访问
* rememberMe | 允许通过remember-me登录的用户访问
* authenticated | 用户登录后可访问
*/
@Bean
protected SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception
{
return httpSecurity
// CSRF禁用,因为不使用session
.csrf(csrf -> csrf.disable())
// 禁用HTTP响应标头
.headers((headersCustomizer) -> {
headersCustomizer.cacheControl(cache -> cache.disable()).frameOptions(options -> options.sameOrigin());
})
// 认证失败处理类
.exceptionHandling(exception -> exception.authenticationEntryPoint(unauthorizedHandler))
// 基于token,所以不需要session
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
// 注解标记允许匿名访问的url
.authorizeHttpRequests((requests) -> {
permitAllUrl.getUrls().forEach(url -> requests.antMatchers(url).permitAll());
// 对于登录login 注册register 验证码captchaImage 允许匿名访问
requests.antMatchers("/login", "/register", "/captchaImage").permitAll()
// 静态资源,可匿名访问
.antMatchers(HttpMethod.GET,
"/",
"/*.html",
"/**/*.html",
"/**/*.css",
"/**/*.js",
"/profile/**",
"/cadre/neo4j/**",
"/cadre/evaluation/rqcode/**")
.permitAll()
.antMatchers(HttpMethod.POST,
"/cadre/cadre/home/recalculate")
.permitAll()
.antMatchers(
"/swagger-ui.html",
"/swagger-resources/**",
"/webjars/**",
"/*/api-docs",
"/cadre/evaluation/qrcode/**",
"/cadre/evaluation/grade/rqcodeScore/**",
"/druid/**")
.permitAll()
// 除上面外的所有请求全部需要鉴权认证
.anyRequest().authenticated();
})
// 添加Logout filter
.logout(logout -> logout.logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler))
// 添加 Api filter
.addFilterBefore(apiKeyAuthFilter, UsernamePasswordAuthenticationFilter.class)
// 添加JWT filter
.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class)
// 添加CORS filter
.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class)
.addFilterBefore(corsFilter, LogoutFilter.class)
.build();
}
/**
* 强散列哈希加密实现
*/
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder()
{
return new BCryptPasswordEncoder();
}
}