Spring Security api接口 认证放行

@[toc]

实现类

  • ApiKeyAuthFilter 认证过滤器
  • ApiKeyAuthenticationToken 认证令牌
  • ApiKeyAuthenticationProvider API认证鉴权
  • 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)。 核心关系:接口约定 + 方法实现(契约模式) AuthenticationProviderSpring 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();
    }
}
相关推荐
用户8356290780517 小时前
Python 实现 PPT 转 HTML
后端·python
0xDevNull7 小时前
MySQL索引进阶用法
后端·mysql
舒一笑7 小时前
程序员效率神器:一文掌握 tmux(服务器开发必备工具)
运维·后端·程序员
UIUV8 小时前
Splitter学习笔记(含RAG相关流程与代码实践)
后端·langchain·llm
cipher8 小时前
HAPI + 设备指纹认证:打造更安全的远程编程体验
前端·后端·ai编程
雨中飘荡的记忆8 小时前
保证金系统入门到实战
java·后端
秋水无痕8 小时前
从零搭建个人博客系统:Spring Boot 多模块实践详解
前端·javascript·后端
用户9003486133468 小时前
GO语言基础:反射
后端
用户1474853079749 小时前
Git-stash产生的冲突
后端