You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多
文档控制台
免费开始使用

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

火山引擎 最新活动