You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

未使用Spring Security时AJAX调用Spring MVC接口遇403禁止错误

解决OPTIONS请求403错误 + 自定义权限控制方案

我来帮你一步步拆解问题:先搞定OPTIONS预请求的403坑,再实现不用Spring Security的自定义权限控制。

一、先解决OPTIONS请求403的问题

这个403大概率是跨域预检请求(OPTIONS)未被后端正确放行导致的。浏览器发送跨域的POST/GET这类请求前,会先发OPTIONS请求做预检,如果后端没配置允许OPTIONS通过,就会直接返回403。

1. 配置全局跨域(推荐)

写个Spring Boot配置类,实现WebMvcConfigurer,明确把OPTIONS加入允许的请求方法:

@Configuration
public class CorsConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**") // 对所有接口生效
                .allowedOriginPatterns("*") // 生产环境记得改成具体域名,别用通配符
                .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") // 必须包含OPTIONS
                .allowedHeaders("*")
                .allowCredentials(true) // 如果你需要带cookie或者token,开启这个
                .maxAge(3600); // 预检请求缓存1小时,减少重复OPTIONS请求
    }
}

2. 检查自定义拦截器/过滤器

如果你自己写了拦截器或者过滤器,一定要跳过OPTIONS请求的权限校验,直接放行:

比如拦截器里这么改:

public class AuthInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 是OPTIONS请求就直接过,别拦
        if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
            return true;
        }
        // 其他请求再走你的权限校验逻辑
        // ...你的自定义校验代码
    }
}

过滤器的逻辑类似,先判断请求方法是OPTIONS的话,直接调用filterChain.doFilter放行即可。

二、实现不用Spring Security的权限控制

不用Spring Security的话,我们可以用自定义注解+拦截器的组合来做细粒度权限控制,步骤很清晰:

1. 写个权限注解

先定义一个注解,用来标记接口需要哪些权限:

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequirePermission {
    String[] value(); // 比如{"admin", "user:edit"},支持多个权限
}

2. 实现权限校验拦截器

在拦截器里,拿到当前登录用户的权限,和接口注解要求的权限对比:

@Component
public class PermissionInterceptor implements HandlerInterceptor {
    // 假设你有个工具类能拿到当前登录用户的权限,比如UserContext
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 还是先跳过OPTIONS请求
        if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
            return true;
        }

        // 判断是不是Controller的方法
        if (handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            RequirePermission requirePermission = handlerMethod.getMethodAnnotation(RequirePermission.class);
            // 接口没加权限注解,直接放行
            if (requirePermission == null) {
                return true;
            }

            // 获取当前用户的权限列表(这里要你自己实现,比如从session、token里取)
            Set<String> userPermissions = UserContext.getCurrentUserPermissions();

            // 检查用户有没有注解要求的权限
            String[] requiredPermissions = requirePermission.value();
            for (String perm : requiredPermissions) {
                if (userPermissions.contains(perm)) {
                    return true;
                }
            }

            // 没权限的话,返回403和自定义提示
            response.setStatus(HttpServletResponse.SC_FORBIDDEN);
            response.setContentType("application/json;charset=utf-8");
            response.getWriter().write("{\"code\":403,\"msg\":\"抱歉,你没有访问权限\"}");
            return false;
        }
        return true;
    }
}

3. 注册拦截器

把刚才的权限拦截器加到Spring的拦截器链里,在之前的CorsConfig里加一段:

@Configuration
public class CorsConfig implements WebMvcConfigurer {
    @Autowired
    private PermissionInterceptor permissionInterceptor;

    // 跨域配置...

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(permissionInterceptor)
                .addPathPatterns("/**") // 对所有接口生效
                .excludePathPatterns("/login", "/register"); // 排除登录、注册这些不需要权限的接口
    }
}

4. 在接口上用注解

比如你的接口需要特定权限,就加上注解:

@RestController
@RequestMapping("/api/user")
public class UserController {
    @RequirePermission({"user:view"})
    @GetMapping("/list")
    public ResponseEntity<List<User>> getUserList() {
        // 业务逻辑
    }

    @RequirePermission({"user:edit"})
    @PutMapping("/{id}")
    public ResponseEntity<Void> updateUser(@PathVariable Long id, @RequestBody User user) {
        // 业务逻辑
    }
}

三、测试验证

  1. 先测OPTIONS请求:用Postman发个OPTIONS请求到你的接口,应该返回200,不会再403了。
  2. 测无权限场景:登录一个没有对应权限的用户,调用加了@RequirePermission的接口,应该返回你自定义的403响应,而不是预检的403。

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

火山引擎 最新活动