java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > SpringBoot静态资源访问权限控制

SpringBoot实现对静态资源的访问权限控制的三种方案

作者:风象南

在日常的 Spring Boot 开发中,我们通常会使用安全认证、授权手段来保护后端的 RESTful API,确保只有认证和授权的用户才能访问,所以本文就给大家介绍SpringBoot中如何实现对静态资源的访问权限控制,需要的朋友可以参考下

引言

在日常的 Spring Boot 开发中,我们通常会使用安全认证、授权手段来保护后端的 RESTful API,确保只有认证和授权的用户才能访问。但一个常常被忽略的角落是——静态资源

想象一个场景:你的应用允许用户上传个人头像、私密文档(如合同PDF、发票图片)等。这些文件通常存放在服务器的某个目录下,并通过 /uploads/contract-xxx.pdf 这样的URL直接访问。如果没有进行任何保护,任何人只要猜到了URL,就可以轻松下载这些敏感文件,后果不堪设想。

今天,我们就来深入探讨这个“灯下黑”问题:在 Spring Boot 中,如何像保护API一样,对静态资源实现精细的访问权限控制?

Spring Boot 静态资源的工作机制回顾

在深入解决方案之前,我们先快速回顾一下 Spring Boot 是如何处理静态资源的。默认情况下,Spring Boot 会从以下几个classpath路径下寻找并提供静态内容:

例如,你将一张图片 logo.png 放在 src/main/resources/static/images/ 目录下,应用启动后,就可以通过 http://localhost:8080/images/logo.png 访问到它。这个过程是 Spring MVC 的 ResourceHttpRequestHandler 在背后默默完成的,它绕过了大部分的 Controller 逻辑,直接将文件流响应给客户端。

正是这种“直接”的特性,导致了 Spring Security 的默认配置通常只拦截动态请求,而对静态资源“网开一面”。

方案一:Spring Security 的全局保护

最直接的方法,就是让 Spring Security 的安全规则“一视同仁”,覆盖静态资源。

1. 默认情况下的“放行”

如果你使用了 Spring Security,你的配置类可能长这样:

@Configuration
@EnableWebSecurity
public class WebSecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(authorize -> authorize
                .requestMatchers("/api/public/**").permitAll() // 公开API
                .requestMatchers("/api/**").authenticated()   // 其他API需要认证
                .anyRequest().permitAll() // <<-- 问题所在!
            )
            .formLogin(Customizer.withDefaults());
        return http.build();
    }
}

注意最后的 .anyRequest().permitAll(),或者更常见的对 /, /css/**, /js/** 等路径的 permitAll() 配置。这相当于明确告诉 Spring Security:“所有未明确匹配的请求,包括大部分静态资源,都直接放行。”

2. 收紧权限,按需开放

要保护静态资源,第一步就是收紧这个“口子”。我们将规则调整为:默认所有请求都需要认证,然后只对必要的公开资源进行放行。

@Configuration
@EnableWebSecurity
public class WebSecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(authorize -> authorize
                // 明确放行公开API和登录页等
                .requestMatchers("/api/public/**", "/login").permitAll() 
                // 明确放行公开的静态资源
                .requestMatchers("/css/**", "/js/**", "/images/logo.png").permitAll() 
                // 其他所有请求,包括所有未指定的静态资源,都需要认证
                .anyRequest().authenticated()
            )
            .formLogin(Customizer.withDefaults());
        return http.build();
    }
}

现在,除了 /css//js/ 目录和 logo.png 这张图片,其他所有位于 static 目录下的资源(比如 /uploads/ 目录)都无法再被公开访问了。访问受保护的资源时,用户会被自动重定向到登录页面。

优点

缺点

方案二:自定义控制器(Controller)代理访问

当我们需要的不仅仅是“登录才能访问”时,就需要更灵活的方案。我们可以将静态资源“动态化”,通过一个 Controller 来代理文件的访问请求。

1. 隐藏静态资源目录

首先,我们要让 Spring Boot 无法直接对外暴露我们的私有文件。一个简单的做法是,将它们存储在 static 目录之外。例如,存储在项目根目录下的 private-uploads 目录中。

2. 创建文件访问Controller

然后,我们创建一个 Controller,用一个特定的端点来处理文件请求。

@RestController
@RequestMapping("/files")
public class PrivateFileController {

    // 假设私有文件存储在项目根目录的 'private-uploads' 文件夹下
    private static final String PRIVATE_STORAGE_PATH = "private-uploads/";

    @GetMapping("/{filename:.+}")
    public ResponseEntity<Resource> serveFile(@PathVariable String filename) {
        // 1. 获取当前登录用户信息
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        String currentUsername = authentication.getName();

        // 2. 实现你的核心业务逻辑
        //    例如:从数据库查询文件元信息,判断当前用户是否有权访问该文件
        if (!hasPermission(currentUsername, filename)) {
            // 如果无权访问,可以返回403 Forbidden
            return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
        }

        try {
            // 3. 加载文件资源
            Path file = Paths.get(PRIVATE_STORAGE_PATH).resolve(filename);
            Resource resource = new UrlResource(file.toUri());

            if (resource.exists() || resource.isReadable()) {
                // 4. 设置响应头,让浏览器能正确处理文件
                return ResponseEntity.ok()
                        .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename="" + resource.getFilename() + """)
                        .body(resource);
            } else {
                // 文件不存在或无法读取
                return ResponseEntity.notFound().build();
            }
        } catch (MalformedURLException e) {
            return ResponseEntity.internalServerError().build();
        }
    }

    /**
     * 伪代码:检查用户权限
     * @param username 用户名
     * @param filename 文件名
     * @return 是否有权限
     */
    private boolean hasPermission(String username, String filename) {
        // 在这里实现你的复杂逻辑
        // 比如:
        // 1. 从数据库查询文件名对应的文件信息,包含所有者ID。
        // 2. 查询当前用户名对应的用户ID。
        // 3. 对比两者是否一致,或者用户是否具有特定角色(如管理员)。
        System.out.println("Checking permission for user '" + username + "' on file '" + filename + "'");
        // 示例:简单地假设只有admin用户可以下载所有文件
        return "admin".equals(username);
    }
}

通过这种方式,原本对 http://.../private-uploads/contract.pdf 的直接访问,变成了对 http://.../files/contract.pdf 的 API 请求。在这个请求中,我们可以:

  1. 获取用户信息:通过 SecurityContextHolder 拿到当前登录的用户。
  2. 执行业务校验:查询数据库,判断文件归属,校验用户角色等。
  3. 动态响应:校验通过,则读取文件流并返回;校验失败,则返回 403 Forbidden 或 404 Not Found。

优点

缺点

方案三:拦截器(Interceptor)动态校验

如果我们不想把文件移出 static 目录,也不想写一个完整的 Controller,有没有折中的办法?当然有,那就是使用 HandlerInterceptor

我们可以创建一个拦截器,它专门拦截指向私有静态资源目录的请求,然后执行权限校验。

1. 配置Web Mvc

首先,我们需要一个 WebMvcConfigurer 来注册我们的拦截器。

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Autowired
    private StaticResourceAuthInterceptor staticResourceAuthInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 拦截所有对 /uploads/ 路径下资源的请求
        registry.addInterceptor(staticResourceAuthInterceptor)
                .addPathPatterns("/uploads/**");
    }
}

2. 实现拦截器

拦截器的核心逻辑和 Controller 方案类似,都是获取用户信息,然后进行业务判断。

@Component
public class StaticResourceAuthInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        // 1. 获取用户信息
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (authentication == null || !authentication.isAuthenticated() || authentication instanceof AnonymousAuthenticationToken) {
            // 用户未登录,重定向到登录页或返回401
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            return false;
        }
        String currentUsername = authentication.getName();

        // 2. 从请求路径中解析出文件名
        String requestURI = request.getRequestURI(); // e.g., /uploads/private-file.txt
        String filename = requestURI.substring(requestURI.lastIndexOf("/") + 1);

        // 3. 执行权限检查
        if (!hasPermission(currentUsername, filename)) {
            // 无权限,返回403
            response.setStatus(HttpServletResponse.SC_FORBIDDEN);
            return false;
        }

        // 4. 有权限,放行
        // preHandle返回true后,请求会继续流转到Spring默认的ResourceHttpRequestHandler,
        // 由它来完成静态文件的读取和响应。
        return true;
    }

    /**
     * 伪代码:权限检查逻辑(同方案二)
     */
    private boolean hasPermission(String username, String filename) {
        System.out.println("Interceptor is checking permission for user '" + username + "' on file '" + filename + "'");
        return "admin".equals(username);
    }
}

这个方案巧妙地结合了 Spring MVC 的默认行为和自定义逻辑。我们的拦截器只负责“鉴权”,鉴权通过后,后续的文件读取和响应工作仍然交给 Spring Boot 高效的静态资源处理器去完成。

优点

缺点

总结与选择

我们探讨了三种保护 Spring Boot 静态资源的实用方案,让我们来总结一下:

方案优点缺点适用场景
方案一:Spring Security全局保护配置简单,统一管理灵活性差,只能控制“是否登录”简单的内部系统,所有登录用户可访问所有资源
方案二:自定义Controller代理极度灵活,安全性最高代码稍复杂,有一定性能开销需要复杂业务权限控制的场景(如网盘、订单附件)
方案三:拦截器动态校验关注点分离,改造方便URL路径暴露对现有项目增加权限控制,且性能要求较高

最终建议:

保护API固然重要,但对静态资源的权限控制同样是应用安全不可或缺的一环。

以上就是SpringBoot实现对静态资源的访问权限控制的三种方案的详细内容,更多关于SpringBoot静态资源访问权限控制的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文