Spring Boot REST接口鉴权咨询:如何用拦截器实现前置权限校验
嘿,这个需求用Spring的HandlerInterceptor就能完美解决,完全不用在每个Controller里重复写鉴权逻辑,我给你一步步拆解实现步骤:
1. 编写自定义鉴权拦截器
我们需要实现HandlerInterceptor接口,重点重写preHandle方法——这个方法会在Controller执行前触发,刚好符合你的前置鉴权需求。在这个方法里调用外部鉴权服务,校验请求头里的用户ID,失败就重定向到错误页面。
@Component public class AuthCheckInterceptor implements HandlerInterceptor { private final RestTemplate restTemplate; // 替换成你实际的鉴权服务地址 private static final String AUTH_SERVICE_URL = "http://your-auth-service/api/verify-permission"; public AuthCheckInterceptor(RestTemplate restTemplate) { this.restTemplate = restTemplate; } @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 从请求头获取用户ID,替换成你实际的请求头key String userId = request.getHeader("X-User-ID"); // 先校验用户ID是否存在 if (userId == null || userId.trim().isEmpty()) { redirectToErrorPage(request, response, "请提供合法的用户ID"); return false; } try { // 调用外部鉴权服务,这里假设鉴权服务返回布尔值,实际根据对方接口调整 HttpHeaders headers = new HttpHeaders(); headers.set("X-User-ID", userId); HttpEntity<Void> requestEntity = new HttpEntity<>(headers); ResponseEntity<Boolean> authResp = restTemplate.exchange( AUTH_SERVICE_URL, HttpMethod.GET, requestEntity, Boolean.class ); Boolean hasPermission = authResp.getBody(); if (hasPermission == null || !hasPermission) { redirectToErrorPage(request, response, "您没有访问该接口的权限"); return false; } } catch (RestClientException e) { // 处理鉴权服务调用异常(比如超时、服务不可用) redirectToErrorPage(request, response, "鉴权服务暂时不可用,请稍后重试"); return false; } // 鉴权通过,放行到Controller return true; } private void redirectToErrorPage(HttpServletRequest request, HttpServletResponse response, String errorMsg) throws IOException { // 用FlashAttribute传递错误信息,重定向后依然能获取到 RequestAttributes attrs = RequestContextHolder.getRequestAttributes(); if (attrs instanceof ServletRequestAttributes) { RedirectAttributes redirectAttrs = new RedirectAttributesModelMap(); redirectAttrs.addFlashAttribute("errorMsg", errorMsg); ((ServletRequestAttributes) attrs).getRequest() .setAttribute(RedirectAttributesModelMap.REDIRECT_ATTRIBUTES_MODEL_KEY, redirectAttrs); } // 重定向到错误页的Controller路径 response.sendRedirect(request.getContextPath() + "/access-denied"); } }
2. 注册拦截器到Spring容器
创建一个配置类实现WebMvcConfigurer,把刚才的拦截器注册进去,同时可以指定拦截/排除的路径,避免不必要的鉴权。
@Configuration public class WebMvcConfig implements WebMvcConfigurer { private final AuthCheckInterceptor authCheckInterceptor; public WebMvcConfig(AuthCheckInterceptor authCheckInterceptor) { this.authCheckInterceptor = authCheckInterceptor; } @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(authCheckInterceptor) // 拦截所有需要鉴权的接口路径,根据你的实际业务调整 .addPathPatterns("/api/**") // 排除不需要鉴权的路径(比如错误页本身、静态资源) .excludePathPatterns("/access-denied", "/static/**"); } }
3. 编写错误页Controller和Freemarker模板
首先写一个简单的Controller来渲染错误页面:
@Controller public class ErrorPageController { @GetMapping("/access-denied") public String showAccessDeniedPage(@ModelAttribute("errorMsg") String errorMsg, Model model) { // 给模板传递错误信息,默认值兜底 model.addAttribute("errorMsg", errorMsg != null ? errorMsg : "您的访问被拒绝"); return "access-denied"; // 对应templates目录下的access-denied.ftl } }
然后在src/main/resources/templates下创建access-denied.ftl模板:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>访问被拒绝</title> <style> .container { margin: 100px auto; text-align: center; } .error-msg { color: #dc3545; font-size: 18px; margin-top: 20px; } </style> </head> <body> <div class="container"> <h1>😕 访问被拒绝</h1> <p class="error-msg">${errorMsg}</p> </div> </body> </html>
4. 补充RestTemplate配置
别忘了把RestTemplate注册到Spring容器里,这样拦截器才能注入使用:
@Configuration public class RestTemplateConfig { @Bean public RestTemplate restTemplate() { return new RestTemplate(); } }
一些注意事项
- 如果你的鉴权服务返回的不是布尔值,而是自定义响应体(比如包含code、message的JSON),可以创建对应的实体类,把
restTemplate.exchange的响应类型改成这个实体类,再根据code判断权限。 - 一定要排除错误页的路径,不然会陷入“鉴权失败→重定向错误页→又触发鉴权”的无限循环。
- 可以通过
order()方法设置拦截器的执行顺序,如果有多个拦截器,确保鉴权拦截器优先执行。
内容的提问来源于stack exchange,提问作者bpa.mdl




