如何在SpringBoot中实现Controller前的access token有效性校验
Spring Boot 前置校验Bearer Access Token(令牌内省模式)实现方案
核心实现思路:在请求进入Controller前的扩展点插入校验逻辑,提取请求头中的Bearer Token,调用你已调通的令牌内省接口校验有效性,校验失败直接返回401状态码,校验通过再放行到对应Controller。
方案一:无Spring Security依赖,基于Spring MVC拦截器实现(轻量无侵入)
适合没有引入Spring Security、仅需要做token合法性校验的场景。
- 封装令牌内省逻辑
把你已实现的内省调用封装成独立服务,注意按你对接的授权服务器要求调整参数传参位置(部分服务要求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; } }
- 自定义拦截器实现前置校验
在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; } } }
- 注册拦截器配置拦截规则
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
相关产品推荐
相关产品推荐

