You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Spring Security为不同端点配置专属Header Token验证

问题描述

我正在使用Spring Boot构建Web应用,在原型集成阶段不想花费过多时间在权限规则管理上,希望生成随机字符串Token,让知晓该Token的其他服务可调用我的服务。我清楚这种方式存在风险,但当前原型阶段确实需要如此。

我的问题是:如何配置Spring Security为特定端点验证专属Token(注意:我希望不同端点使用不同Token,如果是全局单Token,用简单过滤器即可实现)。

期望实现类似如下配置:

protected void configure(HttpSecurity http) throws Exception {
    http.cors().and()
                  .csrf().disable()
                  .authorizeRequests()
                  .antMatchers(AUTH_WHITELIST).permitAll()
                  .antMatchers(HttpMethod.POST, "/boards/").access("SOMETHING LIKE: httpRequest->headers->get('tokenHeader') == 'mytoken'")
    .anyRequest().authenticated() 
//......
}

我目前仅能通过如下方式实现,但这种写法不符合Spring模式,使用起来很繁琐:

@Value("${controller.plan.authorization}")
private String AUTHORIZATION_TOKEN;
 
@GetMapping
public List<Plan> getPlans(@RequestHeader(HttpHeaders.AUTHORIZATION) String authToken)  {
    if (AUTHORIZATION_TOKEN.equals(authToken))
        return planService.findAll();
    else
        throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "invalid authorization_token");
}

请问是否有办法通过Spring Security实现该需求?


解决方案

可以通过Spring Security的自定义安全表达式或者端点匹配过滤器两种方式实现,完全贴合Spring的配置风格,避免在控制器中重复校验逻辑:

方式一:自定义安全表达式(推荐)

这种方式能直接在HttpSecurity配置中通过表达式指定每个端点的Token校验规则,和你期望的写法风格一致。

步骤1:实现自定义表达式处理器

创建一个类扩展默认的Web安全表达式处理器,添加自定义的Token校验方法:

import org.springframework.security.access.expression.SecurityExpressionRoot;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler;
import org.springframework.security.web.access.expression.WebSecurityExpressionRoot;

public class CustomSecurityExpressionHandler extends DefaultWebSecurityExpressionHandler {
    @Override
    protected SecurityExpressionRoot createSecurityExpressionRoot(FilterInvocation fi) {
        WebSecurityExpressionRoot root = new CustomWebSecurityExpressionRoot(fi);
        root.setAuthentication(fi.getAuthentication());
        return root;
    }

    private static class CustomWebSecurityExpressionRoot extends WebSecurityExpressionRoot {
        public CustomWebSecurityExpressionRoot(FilterInvocation fi) {
            super(fi);
        }

        // 自定义表达式:校验请求头中的Token是否匹配指定值
        public boolean hasMatchingToken(String headerName, String expectedToken) {
            String actualToken = getRequest().getHeader(headerName);
            return expectedToken.equals(actualToken);
        }
    }
}

步骤2:在Security配置中注册并使用表达式

修改Spring Security配置类,注册自定义处理器,然后在access()方法中使用自定义表达式绑定不同端点的Token:

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    // 从配置文件读取不同端点的专属Token
    @Value("${controller.board.authorization}")
    private String BOARD_TOKEN;

    @Value("${controller.plan.authorization}")
    private String PLAN_TOKEN;

    private static final String[] AUTH_WHITELIST = {"/public/**", "/actuator/**"};

    @Bean
    public CustomSecurityExpressionHandler customSecurityExpressionHandler() {
        return new CustomSecurityExpressionHandler();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.cors().and()
                .csrf().disable()
                .authorizeRequests()
                .expressionHandler(customSecurityExpressionHandler()) // 注册自定义表达式处理器
                .antMatchers(AUTH_WHITELIST).permitAll()
                // 对/boards/的POST请求校验专属Token
                .antMatchers(HttpMethod.POST, "/boards/").access("hasMatchingToken('Authorization', '" + BOARD_TOKEN + "')")
                // 对/plans/的GET请求校验另一个Token
                .antMatchers(HttpMethod.GET, "/plans/").access("hasMatchingToken('Authorization', '" + PLAN_TOKEN + "')")
                .anyRequest().authenticated();
    }
}

步骤3:配置文件中添加Token

在application.properties或application.yml中配置各端点的Token:

# 示例配置
controller.board.authorization=board-prototype-token-xyz
controller.plan.authorization=plan-prototype-token-abc

方式二:自定义端点匹配Filter

如果觉得表达式方式不够直观,也可以自定义一个Filter,根据请求路径和方法匹配对应Token进行校验:

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component
public class PrototypeTokenFilter extends OncePerRequestFilter {

    @Value("${controller.board.authorization}")
    private String BOARD_TOKEN;

    @Value("${controller.plan.authorization}")
    private String PLAN_TOKEN;

    private static final String AUTHORIZATION_HEADER = "Authorization";

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String path = request.getRequestURI();
        String method = request.getMethod();
        String token = request.getHeader(AUTHORIZATION_HEADER);

        // 针对不同端点和HTTP方法校验Token
        if ("/boards/".equals(path) && "POST".equals(method)) {
            if (!BOARD_TOKEN.equals(token)) {
                response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Invalid board token");
                return;
            }
        } else if ("/plans/".equals(path) && "GET".equals(method)) {
            if (!PLAN_TOKEN.equals(token)) {
                response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Invalid plan token");
                return;
            }
        }

        filterChain.doFilter(request, response);
    }
}

然后在Security配置中添加这个Filter:

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.cors().and()
            .csrf().disable()
            .addFilterBefore(prototypeTokenFilter(), UsernamePasswordAuthenticationFilter.class)
            .authorizeRequests()
            .antMatchers(AUTH_WHITELIST).permitAll()
            .anyRequest().authenticated();
}

内容的提问来源于stack exchange,提问作者Rafael Lima

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 03:25:23