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

如何在SpringBoot中实现Controller前的access token有效性校验

Spring Boot 前置校验Bearer Access Token(令牌内省模式)实现方案

核心实现思路:在请求进入Controller前的扩展点插入校验逻辑,提取请求头中的Bearer Token,调用你已调通的令牌内省接口校验有效性,校验失败直接返回401状态码,校验通过再放行到对应Controller。

方案一:无Spring Security依赖,基于Spring MVC拦截器实现(轻量无侵入)

适合没有引入Spring Security、仅需要做token合法性校验的场景。

    1. 封装令牌内省逻辑
      把你已实现的内省调用封装成独立服务,注意按你对接的授权服务器要求调整参数传参位置(部分服务要求clientId/clientSecret放Basic认证头,部分要求放表单参数):
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import lombok.Data;

@Service
public class TokenIntrospectionService {
    @Value("${oauth2.introspect-endpoint}")
    private String introspectEndpoint;
    @Value("${oauth2.client-id}")
    private String clientId;
    @Value("${oauth2.client-secret}")
    private String clientSecret;

    private final RestTemplate restTemplate = new RestTemplate();

    public IntrospectResult checkTokenValid(String accessToken) {
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
        // 此处按你实际调通的认证方式传client凭证即可
        headers.setBasicAuth(clientId, clientSecret);

        MultiValueMap<String, String> formParams = new LinkedMultiValueMap<>();
        formParams.add("token", accessToken);
        formParams.add("token_type_hint", "access_token");

        HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(formParams, headers);
        ResponseEntity<IntrospectResult> resp = restTemplate.postForEntity(
                introspectEndpoint,
                request,
                IntrospectResult.class
        );
        return resp.getBody();
    }

    // 内省返回结果实体,字段按授权服务器实际返回结构调整
    @Data
    public static class IntrospectResult {
        private Boolean active;
        private String sub;
        private Long exp;
        private String[] scope;
    }
}
    1. 自定义拦截器实现前置校验
      在preHandle阶段完成token提取、校验,校验不通过直接截断请求返回:
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

@Component
public class BearerTokenValidateInterceptor implements HandlerInterceptor {
    private static final String AUTH_HEADER = "Authorization";
    private static final String BEARER_PREFIX = "Bearer ";

    @Autowired
    private TokenIntrospectionService introspectionService;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 放行OPTIONS预检请求、非Controller方法请求(比如静态资源)
        if (HttpMethod.OPTIONS.matches(request.getMethod()) || !(handler instanceof HandlerMethod)) {
            return true;
        }

        String authValue = request.getHeader(AUTH_HEADER);
        if (authValue == null || !authValue.startsWith(BEARER_PREFIX)) {
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            response.getWriter().write("Missing valid Bearer token");
            return false;
        }

        String accessToken = authValue.substring(BEARER_PREFIX.length());
        try {
            TokenIntrospectionService.IntrospectResult result = introspectionService.checkTokenValid(accessToken);
            if (result == null || !Boolean.TRUE.equals(result.getActive())) {
                response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
                response.getWriter().write("Token expired or invalid");
                return false;
            }
            // 校验通过后把解析出的用户信息存入请求属性,Controller层可直接读取
            request.setAttribute("currentTokenInfo", result);
            return true;
        } catch (Exception e) {
            // 内省接口调用异常(如授权服务不可达),可按业务需求调整返回码
            response.setStatus(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
            response.getWriter().write("Token service unavailable");
            return false;
        }
    }
}
    1. 注册拦截器配置拦截规则
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebInterceptorConfig implements WebMvcConfigurer {
    @Autowired
    private BearerTokenValidateInterceptor tokenValidateInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(tokenValidateInterceptor)
                .addPathPatterns("/api/**") // 配置需要鉴权的接口路径
                .excludePathPatterns("/api/public/**", "/error"); // 配置公开放行路径
    }
}

方案二:已集成Spring Security场景,基于OncePerRequestFilter实现

如果项目已经引入Spring Security,直接自定义认证过滤器加入安全过滤器链即可,校验通过后把认证信息存入SecurityContextHolder,可直接复用Spring Security的权限控制、注解鉴权能力,核心校验逻辑和拦截器一致,仅适配Security上下文规则即可。

优化注意事项

  • 可增加短周期本地缓存(比如缓存token有效状态30s,key为token值),避免每个请求都调用内省接口,降低授权服务压力、减少接口耗时
  • 兼容前缀大小写问题,部分前端可能传小写bearer 开头,判断时统一转小写后比对即可
  • 拿到内省结果的exp过期时间字段后,可先做本地时间判断,已过期的token直接拦截,无需再调用远程内省接口
  • 放行规则一定要覆盖登录接口、静态资源、接口文档等不需要鉴权的路径,避免正常请求被拦截

内容的提问来源于stack exchange,提问作者Vasu Youth

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 12:06:20